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/*/uploads/*
applications/*/*.py[oc]
applications/*/static/temp
applications/*/progress.log
applications/examples/static/epydoc
applications/examples/static/sphinx
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 [[ $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 extension postgis;' -U postgres -d test_w2p; fi
# 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
+7
View File
@@ -24,6 +24,13 @@ epydoc:
cp applications/examples/static/title.png applications/examples/static/epydoc
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:
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
+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']:
ram['oldest'] = value[0]
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
folder = os.path.join(request.folder,'cache')
if not os.path.exists(folder):
os.mkdir(folder)
locker = open(os.path.join(folder, 'cache.lock'), 'a')
portalocker.lock(locker, portalocker.LOCK_EX)
disk_storage = shelve.open(
os.path.join(folder, 'cache.shelve'))
try:
for key, value in disk_storage.items():
if isinstance(value, dict):
disk['hits'] = value['hit_total'] - value['misses']
disk['misses'] = value['misses']
try:
disk['ratio'] = disk['hits'] * 100 / value['hit_total']
except (KeyError, ZeroDivisionError):
disk['ratio'] = 0
else:
if hp:
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()
for key in cache.disk.storage:
value = cache.disk.storage[key]
if isinstance(value, dict):
disk['hits'] = value['hit_total'] - value['misses']
disk['misses'] = value['misses']
try:
disk['ratio'] = disk['hits'] * 100 / value['hit_total']
except (KeyError, ZeroDivisionError):
disk['ratio'] = 0
else:
if hp:
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])))
total['entries'] = ram['entries'] + disk['entries']
total['bytes'] = ram['bytes'] + disk['bytes']
+64 -12
View File
@@ -7,8 +7,10 @@
'%s %%{row} updated': '%s registros atualizados',
'%Y-%m-%d': '%d/%m/%Y',
'%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")',
'@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\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
'A new version of web2py is available': 'Está disponível uma nova versão do web2py',
@@ -16,7 +18,7 @@
'About': 'sobre',
'About application': 'Sobre a 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 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',
@@ -33,6 +35,8 @@
'application compiled': 'aplicação compilada',
'application is compiled and cannot be designed': 'A aplicação está compilada e não pode ser modificada',
'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 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?',
@@ -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: 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!',
'Autocomplete Python Code': 'Autocompletar Código Python',
'Available databases and tables': 'Bancos de dados e tabelas disponíveis',
'back': 'voltar',
'browse': 'buscar',
'cache': 'cache',
'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 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 create file': 'Não é possível criar o arquivo',
'cannot upload file "%(filename)s"': 'não é possível fazer upload do arquivo "%(filename)s"',
'Change admin password': 'mudar senha de administrador',
'change editor settings': 'mudar definições do editor',
'Change Password': 'Trocar Senha',
'check all': 'marcar todos',
'Check for upgrades': 'checar por atualizações',
@@ -67,7 +74,7 @@
'click to open': 'clique para abrir',
'Client IP': 'IP do cliente',
'code': 'código',
'collapse/expand all': 'collapse/expand all',
'collapse/expand all': 'colapsar/expandir tudo',
'commit (mercurial)': 'commit (mercurial)',
'Compile': 'compilar',
'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:': 'nome da nova aplicação:',
'Create new simple application': 'Crie uma nova aplicação',
'Create/Upload': 'Create/Upload',
'created by': 'criado por',
'crontab': 'crontab',
'Current request': 'Requisição atual',
@@ -99,18 +107,24 @@
'delete': 'apagar',
'delete all checked': 'apagar marcados',
'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:',
'Deploy': 'publicar',
'Deploy on Google App Engine': 'Publicar no Google App Engine',
'Deploy to OpenShift': 'Deploy to OpenShift',
'Description': 'Descrição',
'DESIGN': 'Projeto',
'design': 'modificar',
'DESIGN': 'Projeto',
'Design for': 'Projeto de',
'Detailed traceback description': 'Detailed traceback description',
'direction: ltr': 'direção: ltr',
'Disable': 'Disable',
'docs': 'docs',
'done!': 'feito!',
'download layouts': 'download layouts',
'Download layouts from repository': 'Download layouts from repository',
'download plugins': 'download plugins',
'Download plugins from repository': 'Download plugins from repository',
'E-mail': 'E-mail',
'EDIT': 'EDITAR',
'Edit': 'editar',
@@ -119,6 +133,7 @@
'Edit current record': 'Editar o registro atual',
'Edit Profile': 'Editar Perfil',
'edit views:': 'editar visões:',
'Editing %s': 'A Editar %s',
'Editing file': 'Editando arquivo',
'Editing file "%s"': 'Editando arquivo "%s"',
'Editing Language file': 'Editando arquivo de linguagem',
@@ -129,6 +144,8 @@
'Error ticket': 'Error ticket',
'Errors': 'erros',
'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',
'exposes': 'expõe',
'extends': 'estende',
@@ -144,20 +161,24 @@
'file does not exist': 'arquivo não existe',
'file saved on %(time)s': 'arquivo salvo em %(time)s',
'file saved on %s': 'arquivo salvo em %s',
'filter': 'filter',
'filter': 'filtro',
'Find Next': 'Localizar Seguinte',
'Find Previous': 'Localizar Anterior',
'First name': 'Nome',
'Frames': 'Frames',
'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',
'Hello World': 'Olá Mundo',
'Help': 'ajuda',
'Hide/Show Translated strings': '',
'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',
'includes': 'inclui',
'insert new': 'inserir novo',
'insert new %s': 'inserir novo %s',
'inspect attributes': 'inspect attributes',
'inspect attributes': 'inspecionar atributos',
'Install': 'instalar',
'Installed applications': 'Aplicações instaladas',
'internal error': 'erro interno',
@@ -168,6 +189,7 @@
'Invalid Query': 'Consulta inválida',
'invalid request': 'solicitação inválida',
'invalid ticket': 'ticket inválido',
'Keyboard shortcuts': 'Atalhos de teclado',
'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',
'languages': 'linguagens',
@@ -178,11 +200,12 @@
'License for': 'Licença para',
'loading...': 'carregando...',
'locals': 'locals',
'login': 'inicio de sessão',
'Login': 'Entrar',
'login': 'inicio de sessão',
'Login to the Administrative Interface': 'Entrar na interface adminitrativa',
'Logout': 'finalizar sessão',
'Lost Password': 'Senha perdida',
'Manage': 'Manage',
'manage': 'gerenciar',
'merge': 'juntar',
'Models': 'Modelos',
@@ -200,7 +223,10 @@
'NO': 'NÃO',
'No databases in this application': 'Não existem bancos de dados nesta aplicação',
'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 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:',
@@ -209,6 +235,7 @@
'Overwrite installed app': 'sobrescrever aplicação instalada',
'Pack all': 'criar pacote',
'Pack compiled': 'criar pacote compilado',
'Pack custom': 'Pack custom',
'pack plugin': 'empacotar plugin',
'PAM authenticated user, cannot change password here': 'usuario autenticado por PAM, não pode alterar a senha por aqui',
'Password': 'Senha',
@@ -218,16 +245,23 @@
'Plugin "%s" in application': 'Plugin "%s" na aplicação',
'plugins': 'plugins',
'Plugins': 'Plugins',
'Plural-Forms:': 'Plural-Forms:',
'Powered by': 'Este site utiliza',
'previous 100 rows': '100 registros anteriores',
'Private files': 'Private files',
'private files': 'private files',
'Query:': 'Consulta:',
'Rapid Search': 'Rapid Search',
'record': 'registro',
'record does not exist': 'o registro não existe',
'record id': 'id do registro',
'Record ID': 'ID do Registro',
'Register': 'Registrar-se',
'Registration key': 'Chave de registro',
'Reload routes': 'Reload routes',
'Remove compiled': 'eliminar compilados',
'Replace': 'Substituir',
'Replace All': 'Substituir Tudo',
'request': 'request',
'Resolve Conflict file': 'Arquivo de resolução de conflito',
'response': 'response',
@@ -236,7 +270,14 @@
'Role': 'Papel',
'Rows in table': 'Registros na tabela',
'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 file:': 'Gravar ficheiro:',
'Save file: %s': 'Gravar ficheiro: %s',
'Save via Ajax': 'Gravar via Ajax',
'Saved file hash:': 'Hash do arquivo salvo:',
'selected': 'selecionado(s)',
'session': 'session',
@@ -244,10 +285,13 @@
'shell': 'Terminal',
'Site': 'site',
'some files could not be removed': 'alguns arquicos não puderam ser removidos',
'Start searching': 'Start searching',
'Start wizard': 'iniciar assistente',
'state': 'estado',
'Static': 'Static',
'static': 'estáticos',
'Static files': 'Arquivos estáticos',
'Submit': 'Submit',
'submit': 'enviar',
'Sure you want to delete this object?': 'Tem certeza que deseja apaagr este objeto?',
'table': 'tabela',
@@ -255,21 +299,23 @@
'test': 'testar',
'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 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 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': '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': '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 models': 'Não existem modelos',
'There are no modules': 'Não existem módulos',
'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 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',
'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': 'Estes arquivos são servidos sem processamento, suas imagens ficam aqui',
'This is the %(filename)s template': 'Este é o template %(filename)s',
'Ticket': 'Ticket',
'Ticket ID': 'Ticket ID',
@@ -277,11 +323,15 @@
'TM': 'MR',
'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]',
'toggle breakpoint': 'toggle breakpoint',
'Toggle comment': 'Toggle comment',
'Toggle Fullscreen': 'Toggle Fullscreen',
'Traceback': 'Traceback',
'translation strings for the application': 'textos traduzidos para a aplicação',
'Translation strings for the application': 'Translation strings for the application',
'try': 'tente',
'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 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"',
@@ -300,8 +350,10 @@
'Update:': 'Atualizar:',
'upgrade web2py now': 'atualize o web2py agora',
'upload': 'upload',
'Upload': 'Upload',
'Upload & install packed application': 'Faça upload e instale uma aplicação empacotada',
'Upload a package:': 'Faça upload de um pacote:',
'Upload and install packed application': 'Upload and install packed application',
'upload application:': 'Fazer upload de uma aplicação:',
'Upload existing application': 'Faça upload de uma aplicação existente',
'upload file:': 'Enviar arquivo:',
+12 -10
View File
@@ -141,7 +141,7 @@
},
/* manage errors in forms */
manage_errors: function(target) {
$('.error', target).hide().slideDown('slow');
$('div.error', target).hide().slideDown('slow');
},
after_ajax: function(xhr) {
/* called whenever an ajax request completes */
@@ -684,19 +684,21 @@
}
});
$.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 */
/*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();
$(function () {
web2py.main_hook();
});
})(jQuery);
@@ -4,12 +4,10 @@ frm = form
smt_button = frm.element(_type="submit")
smt_button['_class'] = 'btn'
smt_button['_style'] = 'margin-right:4px;'
ccl_button = frm.element(_type="button")
ccl_button['_class'] = 'btn'
}}
<!-- begin "git_push" block -->
<h2>{{=T('This will push changes to the remote repo for application "%s".', app)}}</h2>
<center>
{{=form}}
</center>
<!-- end "git_push" block -->
<!-- end "git_push" block -->
+12 -10
View File
@@ -141,7 +141,7 @@
},
/* manage errors in forms */
manage_errors: function(target) {
$('.error', target).hide().slideDown('slow');
$('div.error', target).hide().slideDown('slow');
},
after_ajax: function(xhr) {
/* called whenever an ajax request completes */
@@ -684,19 +684,21 @@
}
});
$.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 */
/*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();
$(function () {
web2py.main_hook();
});
})(jQuery);
+18 -28
View File
@@ -461,34 +461,24 @@ def ccache():
if value[0] < ram['oldest']:
ram['oldest'] = value[0]
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
folder = os.path.join(request.folder,'cache')
if not os.path.exists(folder):
os.mkdir(folder)
locker = open(os.path.join(folder, 'cache.lock'), 'a')
portalocker.lock(locker, portalocker.LOCK_EX)
disk_storage = shelve.open(
os.path.join(folder, 'cache.shelve'))
try:
for key, value in disk_storage.items():
if isinstance(value, dict):
disk['hits'] = value['hit_total'] - value['misses']
disk['misses'] = value['misses']
try:
disk['ratio'] = disk['hits'] * 100 / value['hit_total']
except (KeyError, ZeroDivisionError):
disk['ratio'] = 0
else:
if hp:
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()
for key in cache.disk.storage:
value = cache.disk.storage[key]
if isinstance(value, dict):
disk['hits'] = value['hit_total'] - value['misses']
disk['misses'] = value['misses']
try:
disk['ratio'] = disk['hits'] * 100 / value['hit_total']
except (KeyError, ZeroDivisionError):
disk['ratio'] = 0
else:
if hp:
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])))
total['entries'] = ram['entries'] + disk['entries']
total['bytes'] = ram['bytes'] + disk['bytes']
+12 -10
View File
@@ -141,7 +141,7 @@
},
/* manage errors in forms */
manage_errors: function(target) {
$('.error', target).hide().slideDown('slow');
$('div.error', target).hide().slideDown('slow');
},
after_ajax: function(xhr) {
/* called whenever an ajax request completes */
@@ -684,19 +684,21 @@
}
});
$.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 */
/*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();
$(function () {
web2py.main_hook();
});
})(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
: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')
return form
https is possible too using 'https://127.0.0.1:8888' instead of 'http://127.0.0.1:8888',
but needs to be started with
https is possible too using 'https://127.0.0.1:8888' instead of 'http://127.0.0.1:8888', but need to
be started with
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):
if hmac_key and not 'signature' in self.request.arguments:
return None
self.send_error(401)
if 'message' in self.request.arguments:
message = self.request.arguments['message'][0]
group = self.request.arguments.get('group', ['default'])[0]
@@ -121,11 +121,9 @@ class PostHandler(tornado.web.RequestHandler):
if hmac_key:
signature = self.request.arguments['signature'][0]
if not hmac.new(hmac_key, message).hexdigest() == signature:
return None
self.send_error(401)
for client in listeners.get(group, []):
client.write_message(message)
return None
return 'false'
class TokenHandler(tornado.web.RequestHandler):
@@ -136,15 +134,14 @@ class TokenHandler(tornado.web.RequestHandler):
"""
def post(self):
if hmac_key and not 'message' in self.request.arguments:
return None
self.send_error(401)
if 'message' in self.request.arguments:
message = self.request.arguments['message'][0]
if hmac_key:
signature = self.request.arguments['signature'][0]
if not hmac.new(hmac_key, message).hexdigest() == signature:
return None
self.send_error(401)
tokens[message] = None
return None
class DistributeHandler(tornado.websocket.WebSocketHandler):
@@ -180,6 +177,12 @@ class DistributeHandler(tornado.websocket.WebSocketHandler):
client.write_message('-' + self.name)
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__":
usage = __doc__
version = ""
@@ -205,6 +208,16 @@ if __name__ == "__main__":
default=False,
dest='tokens',
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()
hmac_key = options.hmac_key
DistributeHandler.tokens = options.tokens
@@ -213,6 +226,10 @@ if __name__ == "__main__":
(r'/token', TokenHandler),
(r'/realtime/(.*)', DistributeHandler)]
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)
tornado.ioloop.IOLoop.instance().start()
+1 -1
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from .sqlite import SQLiteAdapter, SpatiaLiteAdapter, JDBCSQLiteAdapter
from .mysql import MySQLAdapter
from .postgre import PostgreSQLAdapter, NewPostgreSQLAdapter, JDBCPostgreSQLAdapter
from .postgres import PostgreSQLAdapter, NewPostgreSQLAdapter, JDBCPostgreSQLAdapter
from .oracle import OracleAdapter
from .mssql import MSSQLAdapter, MSSQL2Adapter, MSSQL3Adapter, MSSQL4Adapter, \
VerticaAdapter, SybaseAdapter
+16 -14
View File
@@ -515,24 +515,26 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
"""
This is the GAE version of select. Some notes to consider:
- 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
- optional attribute 'projection' when set to True will trigger
use of the GAE projection queries. note that there are rules for
what is accepted imposed by GAE: each field must be indexed,
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
use of the GAE projection queries. note that there are rules for
what is accepted imposed by GAE: each field must be indexed,
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
- optional attribute 'filterfields' when set to True web2py will only
parse the explicitly listed fields into the Rows object, even though
all fields are returned in the query. This can be used to reduce
memory usage in cases where true projection queries are not
usable.
parse the explicitly listed fields into the Rows object, even though
all fields are returned in the query. This can be used to reduce
memory usage in cases where true projection queries are not
usable.
- optional attribute 'reusecursor' allows use of cursor with queries
that have the limitby attribute. Set the attribute to True for the
first query, set it to the value of db['_lastcursor'] to continue
a previous query. The user must save the cursor value between
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
that have the limitby attribute. Set the attribute to True for the
first query, set it to the value of db['_lastcursor'] to continue
a previous query. The user must save the cursor value between
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
"""
(items, tablename, fields) = self.select_raw(query,fields,attributes)
+26 -25
View File
@@ -12,7 +12,6 @@ from .base import NoSQLAdapter
class IMAPAdapter(NoSQLAdapter):
drivers = ('imaplib',)
""" IMAP server adapter
@@ -44,29 +43,31 @@ class IMAPAdapter(NoSQLAdapter):
Here is a list of supported fields:
Field Type Description
################################################################
uid string
answered boolean Flag
created date
content list:string A list of dict text or html parts
to string
cc string
bcc string
size integer the amount of octets of the message*
deleted boolean Flag
draft boolean Flag
flagged boolean Flag
sender string
recent boolean Flag
seen boolean Flag
subject string
mime string The mime header declaration
email string The complete RFC822 message**
attachments <type 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
=========== ============== ===========
Field Type Description
=========== ============== ===========
uid string
answered boolean Flag
created date
content list:string A list of dict text or html parts
to string
cc string
bcc string
size integer the amount of octets of the message*
deleted boolean Flag
draft boolean Flag
flagged boolean Flag
sender string
recent boolean Flag
seen boolean Flag
subject string
mime string The mime header declaration
email string The complete RFC822 message (*)
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
message string
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)
"""
drivers = ('imaplib',)
types = {
'string': str,
'text': str,
@@ -173,9 +173,12 @@ class PostgreSQLAdapter(BaseAdapter):
def try_json(self):
# check JSON data type support
# (to be added to after_connection)
if self.driver_name == "pg8000":
supports_json = self.connection.server_version >= "9.2.0"
elif (self.driver_name == "psycopg2" and
# until pg8000 supports json, leave this commented
#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"):
supports_json = self.connection.server_version >= 90200
elif self.driver_name == "zxJDBC":
@@ -211,14 +214,6 @@ class PostgreSQLAdapter(BaseAdapter):
return '(%s ~ %s)' % (self.expand(first),
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
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):
if not hasattr(THREAD_LOCAL,'db_instances'):
+1 -1
View File
@@ -1208,7 +1208,7 @@ class Expression(object):
return Query(db, op, self, value)
def ilike(self, value):
return self.like(case_sensitive=False)
return self.like(value, case_sensitive=False)
def regexp(self, value):
db = self.db
+5 -14
View File
@@ -10,8 +10,7 @@ Cross-site scripting (XSS) defense
-----------------------------------
"""
from htmllib import HTMLParser
from HTMLParser import HTMLParser
from cgi import escape
from urlparse import urlparse
from formatter import AbstractFormatter
@@ -48,11 +47,10 @@ class XssCleaner(HTMLParser):
],
allowed_attributes={'a': ['href', 'title'], 'img': ['src', 'alt'
], 'blockquote': ['type']},
fmt=AbstractFormatter,
strip_disallowed=False
):
HTMLParser.__init__(self, fmt)
HTMLParser.__init__(self)
self.result = ''
self.open_tags = []
self.permitted_tags = [i for i in permitted_tags if i[-1] != '/']
@@ -77,7 +75,7 @@ class XssCleaner(HTMLParser):
def handle_charref(self, ref):
if self.in_disallowed:
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
else:
self.result += xssescape('&#%s' % ref)
@@ -99,8 +97,7 @@ class XssCleaner(HTMLParser):
def handle_starttag(
self,
tag,
method,
attrs,
attrs
):
if tag not in self.permitted_tags:
if self.strip_disallowed:
@@ -130,7 +127,7 @@ class XssCleaner(HTMLParser):
self.result += bt
self.open_tags.insert(0, tag)
def handle_endtag(self, tag, attrs):
def handle_endtag(self, tag):
bracketed = '</%s>' % tag
if tag not in self.permitted_tags:
if self.strip_disallowed:
@@ -141,12 +138,6 @@ class XssCleaner(HTMLParser):
self.result += bracketed
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):
"""
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
from gluon.http import HTTP
from gluon.html import XmlComponent
from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG, SCRIPT, P
from gluon.http import HTTP, redirect
from gluon.html import XmlComponent, truncate_string
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 TABLE, THEAD, TBODY, TR, TD, TH, STYLE, DEFAULT_PASSWORD_DISPLAY
from gluon.html import URL, truncate_string, FIELDSET
from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, SCRIPT
from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
from gluon.dal import DAL, Field
from gluon.dal.base import DEFAULT
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.storage import Storage
from gluon.utils import md5_hash
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_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE
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 datetime
import urllib
import re
import cStringIO
from gluon.globals import current
from gluon.http import redirect
try:
import gluon.settings as settings
@@ -49,7 +50,7 @@ widget_class = re.compile('^\w*')
def add_class(a, b):
return a+' '+b if a else b
return a + ' ' + b if a else b
def represent(field, value, record):
@@ -85,11 +86,11 @@ def show_if(cond):
if not cond:
return None
base = "%s_%s" % (cond.first.tablename, cond.first.name)
if ((cond.op.__name__ == 'EQ' and cond.second == True) or
(cond.op.__name__ == 'NE' and cond.second == False)):
if ((cond.op.__name__ == 'EQ' and cond.second is True) or
(cond.op.__name__ == 'NE' and cond.second is False)):
return base, ":checked"
if ((cond.op.__name__ == 'EQ' and cond.second == False) or
(cond.op.__name__ == 'NE' and cond.second == True)):
if ((cond.op.__name__ == 'EQ' and cond.second is False) or
(cond.op.__name__ == 'NE' and cond.second is True)):
return base, ":not(:checked)"
if cond.op.__name__ == 'EQ':
return base, "[value='%s']" % cond.second
@@ -305,7 +306,8 @@ class ListWidget(StringWidget):
_class = 'string'
requires = field.requires if isinstance(
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 ['']
items = [LI(INPUT(_id=_id, _class=_class, _name=_name,
value=v, hideerror=k < len(nvalue) - 1,
@@ -351,7 +353,6 @@ class RadioWidget(OptionsWidget):
else:
value = str(value)
attr = cls._attributes(field, {}, **attributes)
attr['_class'] = add_class(attr.get('_class'), 'web2py_radiowidget')
@@ -626,7 +627,7 @@ class AutocompleteWidget(object):
self.help_fields = help_fields or []
self.help_string = 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)
self.request = request
@@ -900,7 +901,7 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
label['_for'] = None
label.insert(0, controls)
_controls = DIV(DIV(label, _help, _class="checkbox"),
_class="%s %s" % (offset_class, col_class))
_class="%s %s" % (offset_class, col_class))
label = ''
elif isinstance(controls, SELECT):
controls.add_class('form-control')
@@ -1132,7 +1133,7 @@ class SQLFORM(FORM):
if fieldname.find('.') >= 0:
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])
comment = None
@@ -1184,7 +1185,7 @@ class SQLFORM(FORM):
cond = readonly or \
(not ignore_rw and not field.writable and field.readable)
if default is not None and not cond:
if default is not None and not cond:
default = field.formatter(default)
dspval = default
@@ -1192,10 +1193,6 @@ class SQLFORM(FORM):
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:
inp = represent(field, default, record)
elif field.type in ['blob']:
@@ -1272,7 +1269,7 @@ class SQLFORM(FORM):
(olname.replace('.', '__') + SQLFORM.ID_ROW_SUFFIX,
'', widget, col3.get(olname, '')))
self.custom.linkto[olname.replace('.', '__')] = widget
# </block>
# </block>
# when deletable, add delete? checkbox
self.custom.delete = self.custom.deletable = ''
@@ -1291,16 +1288,15 @@ class SQLFORM(FORM):
)
xfields.append(
(self.FIELDKEY_DELETE_RECORD + SQLFORM.ID_ROW_SUFFIX,
LABEL(
LABEL(
T(delete_label), sep,
_for=self.FIELDKEY_DELETE_RECORD,
_id=self.FIELDKEY_DELETE_RECORD + \
SQLFORM.ID_LABEL_SUFFIX),
_id=self.FIELDKEY_DELETE_RECORD +
SQLFORM.ID_LABEL_SUFFIX),
widget,
col3.get(self.FIELDKEY_DELETE_RECORD, '')))
self.custom.delete = self.custom.deletable = widget
# when writable, add submit button
self.custom.submit = ''
if not readonly:
@@ -1480,7 +1476,7 @@ class SQLFORM(FORM):
# that does not pass validation, yet it should be deleted
for fieldname in self.fields:
field = (self.table[fieldname]
field = (self.table[fieldname]
if fieldname in self.table.fields
else self.extra_fields[fieldname])
### this is a workaround! widgets should always have default not None!
@@ -1995,6 +1991,16 @@ class SQLFORM(FORM):
details = details and not groupby
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):
##FIXME for google:datastore cache_count is ignored
## if it's not an integer
@@ -2051,7 +2057,7 @@ class SQLFORM(FORM):
'/'.join(str(a) for a in args) == '/'.join(request.args) or
URL.verify(request, user_signature=user_signature,
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')
redirect(referrer)
@@ -2096,8 +2102,8 @@ class SQLFORM(FORM):
else:
fields = []
columns = []
filter1 = lambda f:isinstance(f, Field)
filter2 = lambda f:isinstance(f, Field) and f.readable
filter1 = lambda f: isinstance(f, Field)
filter2 = lambda f: isinstance(f, Field) and f.readable
for table in tables:
fields += filter(filter1, table)
columns += filter(filter2, table)
@@ -2111,15 +2117,18 @@ class SQLFORM(FORM):
if groupby is None:
field_id = tables[0]._id
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):
field_id = groupby.first #take the first groupby field
while not(isinstance(field_id, Field)): # Navigate to the first Field of the expression
#take the first groupby field
field_id = groupby.first
while not(isinstance(field_id, Field)):
# Navigate to the first Field of the expression
field_id = field_id.first
table = field_id.table
tablename = table._tablename
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>':
upload = lambda filename: url(args=['download', filename])
if request.args(-2) == 'download':
@@ -2283,8 +2292,10 @@ class SQLFORM(FORM):
expcolumns = [str(f) for f in columns]
selectable_columns = [str(f) for f in columns if not isinstance(f, Field.Virtual)]
if export_type.endswith('with_hidden_cols'):
#expcolumns = [] start with the visible columns, which includes visible virtual fields
selectable_columns = [] #like expcolumns but excluding virtual
# expcolumns = [] start with the visible columns, which
# includes visible virtual fields
selectable_columns = []
#like expcolumns but excluding virtual
for table in tables:
for field in table:
if field.readable and field.tablename in tablenames:
@@ -2292,19 +2303,21 @@ class SQLFORM(FORM):
expcolumns.append(str(field))
if not(isinstance(field, Field.Virtual)):
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():
if isinstance(field, Field.Virtual) and not str(field) in expcolumns:
expcolumns.append(str(field))
if export_type in exportManager and exportManager[export_type]:
if request.vars.keywords:
if keywords:
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,
[[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables])
dbset = dbset(SQLFORM.build_query(
sfields, request.vars.get('keywords', '')))
sfields, keywords))
rows = dbset.select(left=left, orderby=orderby,
cacheable=True, *selectable_columns)
except Exception, e:
@@ -2316,7 +2329,8 @@ class SQLFORM(FORM):
value = exportManager[export_type]
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)
filename = '.'.join(('rows', oExp.file_ext))
response.headers['Content-Type'] = oExp.content_type
@@ -2330,8 +2344,7 @@ class SQLFORM(FORM):
elif not request.vars.records:
request.vars.records = []
session['_web2py_grid_referrer_' + formname] = \
url2(vars=request.get_vars)
session['_web2py_grid_referrer_' + formname] = url2(vars=request.get_vars)
console = DIV(_class='web2py_console %(header)s %(cornertop)s' % ui)
error = None
if create:
@@ -2357,7 +2370,7 @@ class SQLFORM(FORM):
sfields_id = '%s_query_panel' % prefix
skeywords_id = '%s_keywords' % prefix
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',
_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 ''
console.append(add)
console.append(form)
keywords = request.vars.get('keywords', '')
try:
if callable(searchable):
subquery = searchable(sfields, keywords)
@@ -2428,7 +2440,7 @@ class SQLFORM(FORM):
elif key == ordermatch[1:]:
marker = sorter_icons[1]
header = A(header, marker, _href=url(vars=dict(
keywords=request.vars.keywords or '',
keywords=keywords,
order=key)), cid=request.cid)
headcols.append(TH(header, _class=ui.get('default')))
@@ -2466,17 +2478,22 @@ class SQLFORM(FORM):
if paginate and dbset._db._adapter.dbengine == 'google:datastore':
cursor = request.vars.cursor or True
limitby = (0, paginate)
try: page = int(request.vars.page or 1)-1
except ValueError: page = 0
elif paginate and paginate<nrows:
try: page = int(request.vars.page or 1)-1
except ValueError: page = 0
limitby = (paginate*page, paginate*(page+1))
try:
page = int(request.vars.page or 1) - 1
except ValueError:
page = 0
elif paginate and paginate < nrows:
try:
page = int(request.vars.page or 1) - 1
except ValueError:
page = 0
limitby = (paginate * page, paginate * (page + 1))
else:
limitby = None
try:
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':
rows = dbset.select(left=left, orderby=orderby,
groupby=groupby, limitby=limitby,
@@ -2514,8 +2531,15 @@ class SQLFORM(FORM):
paginator.append(LI('page %s' % (page+1)))
if next_cursor:
d = dict(page=page+2, cursor=next_cursor)
if order: d['order'] = order
if request.vars.keywords: d['keywords'] = request.vars.keywords
if order:
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(
A('next', _href=url(vars=d), cid=request.cid)))
elif paginate and paginate < nrows:
@@ -2531,8 +2555,13 @@ class SQLFORM(FORM):
d = dict(page=p + 1)
if order:
d['order'] = order
if request.vars.keywords:
d['keywords'] = request.vars.keywords
# 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
return A(name, _href=url(vars=d), cid=request.cid)
NPAGES = 5 # window is 2*NPAGES
if page > NPAGES + 1:
@@ -2660,7 +2689,8 @@ class SQLFORM(FORM):
_style='width:100%;overflow-x:auto;-ms-overflow-x:scroll')
if 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 = []
for i, submit_info in enumerate(selectable):
submit_text = submit_info[0]
@@ -2708,7 +2738,7 @@ class SQLFORM(FORM):
link = url2(vars=dict(
order=request.vars.order or '',
_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_menu = \
DIV(T('Export:'), _class="w2p_export_menu", *export_links)
@@ -2797,7 +2827,7 @@ class SQLFORM(FORM):
elif callable(table._format):
return table._format(row)
else:
return '#'+str(row.id)
return '#' + str(row.id)
try:
nargs = len(args) + 1
previous_tablename, previous_fieldname, previous_id = \
@@ -2876,7 +2906,7 @@ class SQLFORM(FORM):
opts = [OPTION(T('References')+':', _value='')]
linked = []
if linked_tables:
for item in linked_tables:
for item in linked_tables:
tb = None
if isinstance(item, Table) and item._tablename in check:
tablename = item._tablename
@@ -3001,12 +3031,16 @@ class SQLTABLE(TABLE):
return
REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD
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':
headers = {}
for c in columns:
(t, f) = REGEX_TABLE_DOT_FIELD.match(c).groups()
headers[t + '.' + f] = f.replace('_', ' ').title()
tfmatch=REGEX_TABLE_DOT_FIELD.match(c)
if tfmatch:
(t, f) = REGEX_TABLE_DOT_FIELD.match(c).groups()
headers[t + '.' + f] = f.replace('_', ' ').title()
else:
headers[c]=c
elif headers == 'labels':
headers = {}
for c in columns:
@@ -3025,7 +3059,7 @@ class SQLTABLE(TABLE):
headers = {}
else:
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):
coldict = headers.get(c, c)
attrcol = dict()
+46
View File
@@ -44,6 +44,9 @@ ALLOWED_DATATYPES = [
'json',
]
IS_POSTGRESQL = 'postgres' in DEFAULT_URI
def setUpModule():
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('%B%')).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.define_table('tt', Field('aa', 'integer'))
self.assertEqual(db.tt.insert(aa=1111111111), 1)
@@ -1528,6 +1538,42 @@ class TestQuotesByDefault(unittest.TestCase):
def testme(self):
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__':
unittest.main()
tearDownModule()
+4
View File
@@ -35,6 +35,10 @@ try:
#due to http://bugs.python.org/issue10845, testing multiprocessing in python is impossible
if sys.platform.startswith('win'):
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:
pass
+2 -4
View File
@@ -3011,15 +3011,13 @@ class Auth(object):
if self.settings.prevent_password_reset_attacks:
key = request.vars.key
if not key and len(request.args)>1:
key = request.args[-1]
if key:
session._reset_password_key = key
redirect(self.url(args='reset_password'))
else:
key = session._reset_password_key
else:
key = request.vars.key or getarg(-1)
key = request.vars.key
try:
t0 = int(key.split('-')[0])
if time.time() - t0 > 60 * 60 * 24:
@@ -3138,7 +3136,7 @@ class Auth(object):
def email_reset_password(self, user):
reset_password_key = str(int(time.time())) + '-' + web2py_uuid()
link = self.url(self.settings.function,
args=('reset_password', reset_password_key),
args=('reset_password',), vars={'key': reset_password_key},
scheme=True)
d = dict(user)
d.update(dict(key=reset_password_key, link=link))
+3 -3
View File
@@ -5,10 +5,10 @@ import sys
import os
import optparse
if '__file__' in globals():
path = os.path.dirname(os.path.abspath(__file__))
elif hasattr(sys, 'frozen'):
if hasattr(sys, 'frozen'):
path = os.path.dirname(os.path.abspath(sys.executable))
elif '__file__' in globals():
path = os.path.dirname(os.path.abspath(__file__))
else:
path = os.getcwd()
os.chdir(path)
-1
View File
@@ -32,7 +32,6 @@ from __future__ import with_statement
import sys
import os
print 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
+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 sys
if '__file__' in globals():
path = os.path.dirname(os.path.abspath(__file__))
elif hasattr(sys, 'frozen'):
if hasattr(sys, 'frozen'):
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
path = os.getcwd()
os.chdir(path)