Merge github.com:web2py/web2py

This commit is contained in:
Michele Comitini
2012-07-27 23:10:36 +02:00
22 changed files with 549 additions and 233 deletions
+3 -3
View File
@@ -62,8 +62,8 @@ app:
echo 'did you uncomment import_all in gluon/main.py?'
python2.5 -c 'import compileall; compileall.compile_dir("gluon/")'
#python web2py.py -S welcome -R __exit__.py
#cd ../web2py_osx/site-packages/; unzip ../site-packages.zip
find gluon -path '*.pyc' -exec cp {} ../web2py_osx/site-packages/{} \;
cd ../web2py_osx/site-packages/; unzip ../site-packages.zip
cd ../web2py_osx/site-packages/; zip -r ../site-packages.zip *
mv ../web2py_osx/site-packages.zip ../web2py_osx/web2py/web2py.app/Contents/Resources/lib/python2.5
cp README.markdown ../web2py_osx/web2py/web2py.app/Contents/Resources
@@ -85,9 +85,9 @@ app:
mv ../web2py_osx/web2py_osx.zip .
win:
echo 'did you uncomment import_all in gluon/main.py?'
python2.5 -c 'import compileall; compileall.compile_dir("gluon/")'
python2.7 -c 'import compileall; compileall.compile_dir("gluon/")'
#cd ../web2py_win/library/; unzip ../library.zip
find gluon -path '*.pyc' -exec cp {} ../web2py_win/library/{} \;
cd ../web2py_win/library/; unzip ../library.zip
cd ../web2py_win/library/; zip -r ../library.zip *
mv ../web2py_win/library.zip ../web2py_win/web2py
cp README.markdown ../web2py_win/web2py/
+1 -1
View File
@@ -1 +1 @@
Version 2.00.0 (2012-07-20 17:37:48) dev
Version 2.00.0 (2012-07-27 09:32:24) dev
+141 -3
View File
@@ -14,15 +14,24 @@ from gluon.fileutils import abspath, read_file, write_file
from glob import glob
import shutil
import platform
try:
from git import *
have_git = True
except ImportError:
have_git = False
GIT_MISSING = 'requires python-git module, but not installed'
from gluon.languages import (regex_language, read_possible_languages,
read_possible_plurals, lang_sampling,
read_dict, write_dict, read_plural_dict,
write_plural_dict)
if DEMO_MODE and request.function in ['change_password','pack','pack_plugin','upgrade_web2py','uninstall','cleanup','compile_app','remove_compiled_app','delete','delete_plugin','create_file','upload_file','update_languages','reload_routes']:
if DEMO_MODE and request.function in ['change_password','pack','pack_plugin','upgrade_web2py','uninstall','cleanup','compile_app','remove_compiled_app','delete','delete_plugin','create_file','upload_file','update_languages','reload_routes','git_push','git_pull']:
session.flash = T('disabled in demo mode')
redirect(URL('site'))
if not is_manager() and request.function in ['change_password','upgrade_web2py']:
session.flash = T('disabled in multi user mode')
redirect(URL('site'))
@@ -188,6 +197,23 @@ def site():
msg = 'you must specify a name for the uploaded application'
response.flash = T(msg)
elif (request.vars.appurl or '').endswith('.git') and request.vars.filename:
if not have_git:
session.flash = GIT_MISSING
elif request.vars.filename:
target = os.path.join(apath(r=request),request.vars.filename)
if os.path.exists(target):
session.flash = 'Application by that name already exists.'
else:
try:
new_repo = Repo.clone_from(request.vars.appurl,target)
session.flash = T('new application "%s" imported',request.vars.filename)
except GitCommandError, err:
session.flash = T('Invalid git repository specified.')
else:
session.flash = 'Application Name required for git import.'
redirect(URL(r=request))
elif file_or_appurl and request.vars.filename:
# fetch an application via URL or file upload
f = None
@@ -377,6 +403,32 @@ def delete():
redirect(URL(sender, anchor=request.vars.id2))
return dict(filename=filename, sender=sender)
def delete():
""" Object delete handler """
app = get_app()
filename = '/'.join(request.args)
sender = request.vars.sender
if isinstance(sender, list): # ## fix a problem with Vista
sender = sender[0]
dialog = FORM.dialog(T('Delete'),
{T('Cancel'):URL(sender, anchor=request.vars.id)})
if dialog.accepted:
try:
full_path = apath(filename, r=request)
lineno = count_lines(open(full_path,'r').read())
os.unlink(full_path)
log_progress(app,'DELETE',filename,progress=-lineno)
session.flash = T('file "%(filename)s" deleted',
dict(filename=filename))
except Exception:
session.flash = T('unable to delete file "%(filename)s"',
dict(filename=filename))
redirect(URL(sender, anchor=request.vars.id2))
return dict(dialog=dialog,filename=filename)
def enable():
app = get_app()
filename = os.path.join(apath(app, r=request),'DISABLED')
@@ -854,6 +906,11 @@ def design():
modules = modules=[x.replace('\\','/') for x in modules]
modules.sort()
# Get all private files
privates = listdir(apath('%s/private/' % app, r=request), '[^\.#].*')
privates = [x.replace('\\','/') for x in privates]
privates.sort()
# Get all static files
statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*')
statics = [x.replace('\\','/') for x in statics]
@@ -908,6 +965,7 @@ def design():
modules=filter_plugins(modules,plugins),
extend=extend,
include=include,
privates=filter_plugins(privates,plugins),
statics=filter_plugins(statics,plugins),
languages=languages,
plurals=plurals,
@@ -924,7 +982,7 @@ def delete_plugin():
redirect(URL('design', args=app, anchor=request.vars.id))
elif 'delete' in request.vars:
try:
for folder in ['models','views','controllers','static','modules']:
for folder in ['models','views','controllers','static','modules', 'private']:
path=os.path.join(apath(app,r=request),folder)
for item in os.listdir(path):
if item.rsplit('.',1)[0] == plugin_name:
@@ -994,6 +1052,11 @@ def plugin():
modules = modules=[x.replace('\\','/') for x in modules]
modules.sort()
# Get all private files
privates = listdir(apath('%s/private/' % app, r=request), '[^\.#].*')
privates = [x.replace('\\','/') for x in privates]
privates.sort()
# Get all static files
statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*')
statics = [x.replace('\\','/') for x in statics]
@@ -1023,6 +1086,7 @@ def plugin():
modules=filter_plugins(modules),
extend=extend,
include=include,
privates=filter_plugins(privates),
statics=filter_plugins(statics),
languages=languages,
crontab=crontab)
@@ -1142,10 +1206,11 @@ def create_file():
# coding: utf8
from gluon import *\n""")[1:]
elif path[-8:] == '/static/':
elif (path[-8:] == '/static/') or (path[-9:] == '/private/'):
if request.vars.plugin and not filename.startswith('plugin_%s/' % request.vars.plugin):
filename = 'plugin_%s/%s' % (request.vars.plugin, filename)
text = ''
else:
redirect(request.vars.sender+anchor)
@@ -1545,3 +1610,76 @@ def bulk_register():
redirect(URL('site'))
return locals()
### Begin experimental stuff need fixes:
# 1) should run in its own process - cannot os.chdir
# 2) should not prompt user at console
# 3) should give option to force commit and not reuqire manual merge
def git_pull():
""" Git Pull handler """
app = get_app()
if not have_git:
session.flash = GIT_MISSING
redirect(URL('site'))
if 'pull' in request.vars:
try:
repo = Repo(os.path.join(apath(r=request),app))
origin = repo.remotes.origin
origin.fetch()
origin.pull()
session.flash = T("Application updated via git pull")
redirect(URL('site'))
except CheckoutError, message:
logging.error(message)
session.flash = T("Pull failed, certain files could not be checked out. Check logs for details.")
redirect(URL('site'))
except UnmergedEntriesError:
session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.")
redirect(URL('site'))
except AssertionError:
session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.")
redirect(URL('site'))
except GitCommandError, status:
logging.error(str(status))
session.flash = T("Pull failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
except Exception,e:
logging.error("Unexpected error:", sys.exc_info()[0])
session.flash = T("Pull failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
elif 'cancel' in request.vars:
redirect(URL('site'))
return dict(app=app)
def git_push():
""" Git Push handler """
app = get_app()
if not have_git:
session.flash = GIT_MISSING
redirect(URL('site'))
form = SQLFORM.factory(Field('changelog',requires=IS_NOT_EMPTY()))
form.element('input[type=submit]')['_value']=T('Push')
form.add_button(T('Cancel'),URL('site'))
form.process()
if form.accepted:
try:
repo = Repo(os.path.join(apath(r=request),app))
index = repo.index
os.chdir(os.path.join(apath(r=request),app))
index.add('*')
new_commit = index.commit(form.vars.changelog)
origin = repo.remotes.origin
origin.push()
session.flash = T("Git repo updated with latest application changes.")
redirect(URL('site'))
except UnmergedEntriesError:
session.flash = T("Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.")
redirect(URL('site'))
except Exception, e:
logging.error("Unexpected error:", sys.exc_info()[0])
session.flash = T("Push failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
os.chdir(apath(r=request))
return dict(app=app,form=form)
+1 -1
View File
@@ -34,7 +34,7 @@ def deploy():
assert repo.bare == False
for i in form.vars.applications:
appsrc = os.path.join(os.getcwd(),'applications',i)
appsrc = os.path.join(apath(r=request),i)
appdest = os.path.join(osrepo,'wsgi',osname,'applications',i)
dir_util.copy_tree(appsrc,appdest)
#shutil.copytree(appsrc,appdest)
+1 -1
View File
@@ -4,6 +4,6 @@
<div class="center">
<h2>{{=T('Are you sure you want to delete file "%s"?', filename)}}</h2>
<p>{{=FORM(INPUT(_type='submit',_name='nodelete',_value=T('Abort')),INPUT(_type='hidden',_name='sender',_value=sender), _class="inline")}}{{=FORM(INPUT(_type='submit',_name='delete',_value=T('Delete')),INPUT(_type='hidden',_name='sender',_value=sender), _class="inline")}}</p>
<p>{{=dialog}}</p>
</div>
@@ -57,6 +57,7 @@ def deletefile(arglist, vars={}):
{{=button('#languages', T("languages"))}}
{{=button('#static', T("static"))}}
{{=button('#modules', T("modules"))}}
{{=button('#private', T("private files"))}}
{{=button('#plugins', T("plugins"))}}
</span>
</p>
@@ -318,6 +319,57 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
{{=file_upload_form('%s/modules/' % app, 'modules')}}</div>
</div>
<!-- PRIVATE -->
<h3 id="private" onclick="collapse('private_inner');" class="component">
{{=T("Private files")}}
<span class="tooltip">{{=helpicon()}} <span>{{=T("These files are not served, they are only available from within your app")}}</span></span>
</h3>
<div id="private_inner" class="component_contents">
<div class="controls comptools">
</div>
{{if not privates:}}<p><strong>{{=T("There are no private files")}}</strong></p>{{pass}}
<ul>
{{
path=[]
for file in privates+['']:
items=file.split('/')
file_path=items[:-1]
filename=items[-1]
while path!=file_path:
if len(file_path)>=len(path) and all([v==file_path[k] for k,v in enumerate(path)]):
path.append(file_path[len(path)])
thispath='private__'+'__'.join(path)
}}
<li class="folder">
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
<ul id="{{=thispath}}" style="display: none;" class="sublist">{{
else:
path = path[:-1]
}}
</ul></li>
{{
pass
pass
if filename:
}}<li>
<span class="filetools controls">
{{=editfile('private',file, dict(id="private"))}} {{=deletefile([app,'private',file], dict(id="private",id2="private"))}}
</span>
<span class="file">
{{=filename}}
</span>
</li>{{
pass
pass
}}
{{pass}}
</ul>
<div class="controls formfield">{{=file_create_form('%s/private/' % app, 'private')}}
{{=file_upload_form('%s/private/' % app, 'private')}}</div>
</div>
<!-- PLUGINS -->
<h3 id="plugins" onclick="collapse('plugins_inner');" class="component">
@@ -0,0 +1,9 @@
{{extend 'layout.html'}}
<center>
<h2>{{=T('This will pull changes from the remote repo for application "%s"?', app)}}</h2>
<table><tr>
<td>{{=FORM(INPUT(_type='submit',_name='cancel',_value=T('Cancel')))}}</td>
<td>{{=FORM(INPUT(_type='submit',_name='pull',_value=T('Pull')))}}</td>
</tr></table>
</center>
@@ -0,0 +1,6 @@
{{extend 'layout.html'}}
<h2>{{=T('This will push changes to the remote repo for application "%s".', app)}}</h2>
<center>
{{=form}}
</center>
+16 -12
View File
@@ -34,6 +34,10 @@
{{=button(URL('remove_compiled_app',args=a), T("Remove compiled"))}}
{{pass}}
{{pass}}
{{if os.path.exists(os.path.join(apath(r=request),a,'.git')): }}
{{=button(URL('git_pull',args=a), T("Git Pull"))}}
{{=button(URL('git_push',args=a), T("Git Push"))}}
{{pass}}
{{if a!=request.application:}}
{{=button(URL('uninstall',args=a), T("Uninstall"))}}
{{=button_enable(URL('enable',args=a), a)}}
@@ -117,16 +121,18 @@
</tr>
<tr>
<td>
{{=LABEL(T("Get from URL:"), _for='upload_url')}}
{{=LABEL(T("Get from URL:"), _for='upload_url')}}
</td>
<td>
<input id="appurl" name="appurl" type="text" id="upload_url"/>
<input id="appurl" name="appurl" type="text" id="upload_url"/><br/>
</td>
</tr>
<tr>
<td></td>
<td>
<input type="checkbox" name="overwrite_check" id="upload_overwrite" />
<td>
({{=T('can be a git repo')}})
</td>
<td>
<input type="checkbox" name="overwrite_check" id="upload_overwrite" />
{{=LABEL(T("Overwrite installed app"), _for='upload_overwrite')}}
</td>
</tr>
@@ -142,13 +148,11 @@
</div>
<!-- DEPLOY ON GAE -->
<div class="box">
<h3>{{=T("Deploy on Google App Engine")}}</h3>
<p>{{=button(URL('gae','deploy'), T('Deploy'))}}</p>
</div><br/>
<!-- DEPLOY ON OPENSHIFT -->
<div class="box">
<h3>{{=T("Deploy to OpenShift")}}</h3>
<p>{{=button(URL('openshift','deploy'),T('Deploy'))}}</p>
<h3>{{=T("Deploy")}}</h3>
<p>
{{=button(URL('gae','deploy'), T('Deploy on Google App Engine'))}}
{{=button(URL('openshift','deploy'),T('Deploy to OpenShift'))}}
</p>
</div><br/>
{{if TWITTER_HASH:}}
<div class="box">
+38 -37
View File
@@ -3,14 +3,18 @@
'!langcode!': 'it',
'!langname!': 'Italiano',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" è un\'espressione opzionale come "campo1=\'nuovo valore\'". Non si può fare "update" o "delete" dei risultati di un JOIN ',
'%Y-%m-%d': '%d/%m/%Y',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
'%d seconds ago': '%d seconds ago',
'%s %%{row} deleted': '%s righe ("record") cancellate',
'%s %%{row} updated': '%s righe ("record") modificate',
'%s selected': '%s selezionato',
'%Y-%m-%d': '%d/%m/%Y',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
'Administrative interface': 'Interfaccia amministrativa',
'appadmin is disabled because insecure channel': 'Amministrazione (appadmin) disabilitata: comunicazione non sicura',
'Available databases and tables': 'Database e tabelle disponibili',
'cache': 'cache',
'Cannot be empty': 'Non può essere vuoto',
'change password': 'Cambia password',
'Check to delete': 'Seleziona per cancellare',
'Client IP': 'Client IP',
'Controller': 'Controller',
@@ -18,50 +22,79 @@
'Current request': 'Richiesta (request) corrente',
'Current response': 'Risposta (response) corrente',
'Current session': 'Sessione (session) corrente',
'DB Model': 'Modello di DB',
'customize me!': 'Personalizzami!',
'data uploaded': 'dati caricati',
'Database': 'Database',
'database': 'database',
'database %s select': 'database %s select',
'db': 'db',
'DB Model': 'Modello di DB',
'Delete': 'Delete',
'Delete:': 'Cancella:',
'Description': 'Descrizione',
'design': 'progetta',
'Documentation': 'Documentazione',
'done!': 'fatto!',
'E-mail': 'E-mail',
'Edit': 'Modifica',
'Edit This App': 'Modifica questa applicazione',
'Edit current record': 'Modifica record corrente',
'edit profile': 'modifica profilo',
'Edit This App': 'Modifica questa applicazione',
'export as csv file': 'esporta come file CSV',
'First name': 'Nome',
'Group ID': 'ID Gruppo',
'hello': 'hello',
'hello world': 'salve mondo',
'Hello World': 'Salve Mondo',
'Hello World in a flash!': 'Salve Mondo in un flash!',
'Import/Export': 'Importa/Esporta',
'Index': 'Indice',
'insert new': 'inserisci nuovo',
'insert new %s': 'inserisci nuovo %s',
'Internal State': 'Stato interno',
'Invalid Query': 'Richiesta (query) non valida',
'Invalid email': 'Email non valida',
'Invalid Query': 'Richiesta (query) non valida',
'invalid request': 'richiesta non valida',
'Last name': 'Cognome',
'Layout': 'Layout',
'login': 'accesso',
'logout': 'uscita',
'lost password?': 'dimenticato la password?',
'Main Menu': 'Menu principale',
'Menu Model': 'Menu Modelli',
'Name': 'Nome',
'New Record': 'Nuovo elemento (record)',
'new record inserted': 'nuovo record inserito',
'next 100 rows': 'prossime 100 righe',
'No databases in this application': 'Nessun database presente in questa applicazione',
'not authorized': 'non autorizzato',
'Online examples': 'Vedere gli esempi',
'or import from csv file': 'oppure importa da file CSV',
'Origin': 'Origine',
'Password': 'Password',
'Powered by': 'Powered by',
'previous 100 rows': '100 righe precedenti',
'Query:': 'Richiesta (query):',
'record': 'record',
'record does not exist': 'il record non esiste',
'record id': 'record id',
'Record ID': 'Record ID',
'register': 'registrazione',
'Registration key': 'Chiave di Registazione',
'Reset Password key': 'Resetta chiave Password ',
'Role': 'Ruolo',
'Rows in table': 'Righe nella tabella',
'Rows selected': 'Righe selezionate',
'state': 'stato',
'Stylesheet': 'Foglio di stile (stylesheet)',
'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?',
'table': 'tabella',
'Table name': 'Nome tabella',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La richiesta (query) è una condizione come ad esempio "db.tabella1.campo1==\'valore\'". Una condizione come "db.tabella1.campo1==db.tabella2.campo2" produce un "JOIN" SQL.',
'The output of the file is a dictionary that was rendered by the view %s': 'L\'output del file è un "dictionary" che è stato visualizzato dalla vista %s',
'This is a copy of the scaffolding application': "Questa è una copia dell'applicazione di base (scaffold)",
'Timestamp': 'Ora (timestamp)',
'unable to parse csv file': 'non riesco a decodificare questo file CSV',
'Update': 'Update',
'Update:': 'Aggiorna:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).',
@@ -73,36 +106,4 @@
'You are successfully running web2py': 'Stai eseguendo web2py con successo',
'You can modify this application and adapt it to your needs': 'Puoi modificare questa applicazione adattandola alle tue necessità',
'You visited the url %s': "Hai visitato l'URL %s",
'appadmin is disabled because insecure channel': 'Amministrazione (appadmin) disabilitata: comunicazione non sicura',
'cache': 'cache',
'change password': 'Cambia password',
'customize me!': 'Personalizzami!',
'data uploaded': 'dati caricati',
'database': 'database',
'database %s select': 'database %s select',
'db': 'db',
'design': 'progetta',
'done!': 'fatto!',
'edit profile': 'modifica profilo',
'export as csv file': 'esporta come file CSV',
'hello': 'hello',
'hello world': 'salve mondo',
'insert new': 'inserisci nuovo',
'insert new %s': 'inserisci nuovo %s',
'invalid request': 'richiesta non valida',
'login': 'accesso',
'logout': 'uscita',
'lost password?': 'dimenticato la password?',
'new record inserted': 'nuovo record inserito',
'next 100 rows': 'prossime 100 righe',
'not authorized': 'non autorizzato',
'or import from csv file': 'oppure importa da file CSV',
'previous 100 rows': '100 righe precedenti',
'record': 'record',
'record does not exist': 'il record non esiste',
'record id': 'record id',
'register': 'registrazione',
'state': 'stato',
'table': 'tabella',
'unable to parse csv file': 'non riesco a decodificare questo file CSV',
}
+145 -141
View File
@@ -5,12 +5,16 @@
'!langname!': 'Română',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" (actualizează) este o expresie opțională precum "câmp1=\'valoare_nouă\'". Nu puteți actualiza sau șterge rezultatele unui JOIN',
'%(nrows)s records found': '%(nrows)s înregistrări găsite',
'%Y-%m-%d': '%Y-%m-%d',
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
'%d days ago': '%d days ago',
'%d weeks ago': '%d weeks ago',
'%s %%{row} deleted': '%s linii șterse',
'%s %%{row} updated': '%s linii actualizate',
'%s selected': '%s selectat(e)',
'%Y-%m-%d': '%Y-%m-%d',
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
'(something like "it-it")': '(ceva ce seamănă cu "it-it")',
'1 day ago': '1 day ago',
'1 week ago': '1 week ago',
'<': '<',
'<=': '<=',
'=': '=',
@@ -18,13 +22,15 @@
'>=': '>=',
'A new version of web2py is available': 'O nouă versiune de web2py este disponibilă',
'A new version of web2py is available: %s': 'O nouă versiune de web2py este disponibilă: %s',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENȚIE: Nu vă puteți conecta decât utilizând o conexiune securizată (HTTPS) sau rulând aplicația pe computerul local.',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENȚIE: Nu puteți efectua mai multe teste o dată deoarece lansarea în execuție a mai multor subpocese nu este sigură.',
'ATTENTION: you cannot edit the running application!': 'ATENȚIE: nu puteți edita o aplicație în curs de execuție!',
'About': 'Despre',
'about': 'despre',
'About application': 'Despre aplicație',
'Access Control': 'Control acces',
'Add': 'Adaugă',
'additional code for your application': 'cod suplimentar pentru aplicația dvs.',
'admin disabled because no admin password': 'administrare dezactivată deoarece parola de administrator nu a fost furnizată',
'admin disabled because not supported on google app engine': 'administrare dezactivată deoarece funcționalitatea nu e suportat pe Google App Engine',
'admin disabled because unable to access password file': 'administrare dezactivată deoarece nu există acces la fișierul cu parole',
'Admin is disabled because insecure channel': 'Adminstrarea este dezactivată deoarece conexiunea nu este sigură',
'Admin is disabled because unsecure channel': 'Administrarea este dezactivată deoarece conexiunea nu este securizată',
'Administration': 'Administrare',
@@ -32,62 +38,118 @@
'Administrator Password:': 'Parolă administrator:',
'Ajax Recipes': 'Rețete Ajax',
'And': 'Și',
'and rename it (required):': 'și renumiți (obligatoriu):',
'and rename it:': ' și renumiți:',
'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'appadmin dezactivat deoarece conexiunea nu e sigură',
'application "%s" uninstalled': 'aplicația "%s" a fost dezinstalată',
'application compiled': 'aplicația a fost compilată',
'application is compiled and cannot be designed': 'aplicația este compilată și nu poate fi editată',
'Are you sure you want to delete file "%s"?': 'Sigur ștergeți fișierul "%s"?',
'Are you sure you want to delete this object?': 'Sigur ștergeți acest obiect?',
'Are you sure you want to uninstall application "%s"': 'Sigur dezinstalați aplicația "%s"',
'Are you sure you want to uninstall application "%s"?': 'Sigur dezinstalați aplicația "%s"?',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENȚIE: Nu vă puteți conecta decât utilizând o conexiune securizată (HTTPS) sau rulând aplicația pe computerul local.',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENȚIE: Nu puteți efectua mai multe teste o dată deoarece lansarea în execuție a mai multor subpocese nu este sigură.',
'ATTENTION: you cannot edit the running application!': 'ATENȚIE: nu puteți edita o aplicație în curs de execuție!',
'Authentication': 'Autentificare',
'Available databases and tables': 'Baze de date și tabele disponibile',
'Back': 'Înapoi',
'Buy this book': 'Cumpără această carte',
'cache': 'cache',
'Cache Keys': 'Chei cache',
'cache, errors and sessions cleaned': 'cache, erori și sesiuni golite',
'Cannot be empty': 'Nu poate fi vid',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Compilare imposibilă: aplicația conține erori. Debogați aplicația și încercați din nou.',
'cannot create file': 'fișier imposibil de creat',
'cannot upload file "%(filename)s"': 'imposibil de încărcat fișierul "%(filename)s"',
'Change Password': 'Schimbare parolă',
'Change password': 'Schimbare parolă',
'change password': 'schimbare parolă',
'check all': 'coșați tot',
'Check to delete': 'Coșați pentru a șterge',
'clean': 'golire',
'Clear': 'Golește',
'click to check for upgrades': 'Clic pentru a verifica dacă există upgrade-uri',
'Client IP': 'IP client',
'Community': 'Comunitate',
'compile': 'compilare',
'compiled application removed': 'aplicația compilată a fost ștearsă',
'Components and Plugins': 'Componente și plugin-uri',
'contains': 'conține',
'Controller': 'Controlor',
'Controllers': 'Controlori',
'controllers': 'controlori',
'Copyright': 'Drepturi de autor',
'create file with filename:': 'crează fișier cu numele:',
'Create new application': 'Creați aplicație nouă',
'create new application:': 'crează aplicație nouă:',
'crontab': 'crontab',
'Current request': 'Cerere curentă',
'Current response': 'Răspuns curent',
'Current session': 'Sesiune curentă',
'DB Model': 'Model bază de date',
'DESIGN': 'DESIGN',
'currently saved or': 'în prezent salvat sau',
'customize me!': 'Personalizează-mă!',
'data uploaded': 'date încărcate',
'Database': 'Baza de date',
'database': 'bază de date',
'database %s select': 'selectare bază de date %s',
'database administration': 'administrare bază de date',
'Date and Time': 'Data și ora',
'db': 'db',
'DB Model': 'Model bază de date',
'defines tables': 'definire tabele',
'Delete': 'Șterge',
'delete': 'șterge',
'delete all checked': 'șterge tot ce e coșat',
'Delete:': 'Șterge:',
'Demo': 'Demo',
'Deploy on Google App Engine': 'Instalare pe Google App Engine',
'Deployment Recipes': 'Rețete de instalare',
'Description': 'Descriere',
'design': 'design',
'DESIGN': 'DESIGN',
'Design for': 'Design pentru',
'Disk Cache Keys': 'Chei cache de disc',
'Documentation': 'Documentație',
"Don't know what to do?": 'Nu știți ce să faceți?',
'done!': 'gata!',
'Download': 'Descărcare',
'E-mail': 'E-mail',
'E-mail invalid': 'E-mail invalid',
'edit': 'editare',
'EDIT': 'EDITARE',
'Edit': 'Editare',
'Edit Profile': 'Editare profil',
'Edit This App': 'Editați această aplicație',
'Edit application': 'Editare aplicație',
'edit controller': 'editare controlor',
'Edit current record': 'Editare înregistrare curentă',
'Edit Profile': 'Editare profil',
'edit profile': 'editare profil',
'Edit This App': 'Editați această aplicație',
'Editing file': 'Editare fișier',
'Editing file "%s"': 'Editare fișier "%s"',
'Email and SMS': 'E-mail și SMS',
'enter a number between %(min)g and %(max)g': 'introduceți un număr între %(min)g și %(max)g',
'enter an integer between %(min)g and %(max)g': 'introduceți un întreg între %(min)g și %(max)g',
'Error logs for "%(app)s"': 'Log erori pentru "%(app)s"',
'errors': 'erori',
'Errors': 'Erori',
'Export': 'Export',
'FAQ': 'Întrebări frecvente',
'export as csv file': 'exportă ca fișier csv',
'exposes': 'expune',
'extends': 'extinde',
'failed to reload module': 'reîncarcare modul nereușită',
'False': 'Neadevărat',
'FAQ': 'Întrebări frecvente',
'file "%(filename)s" created': 'fișier "%(filename)s" creat',
'file "%(filename)s" deleted': 'fișier "%(filename)s" șters',
'file "%(filename)s" uploaded': 'fișier "%(filename)s" încărcat',
'file "%(filename)s" was not deleted': 'fișierul "%(filename)s" n-a fost șters',
'file "%s" of %s restored': 'fișier "%s" de %s restaurat',
'file changed on disk': 'fișier modificat pe disc',
'file does not exist': 'fișier inexistent',
'file saved on %(time)s': 'fișier salvat %(time)s',
'file saved on %s': 'fișier salvat pe %s',
'First name': 'Prenume',
'Forbidden': 'Interzis',
'Forms and Validators': 'Formulare și validatori',
@@ -98,19 +160,31 @@
'Group uniquely assigned to user %(id)s': 'Grup asociat în mod unic utilizatorului %(id)s',
'Groups': 'Grupuri',
'Hello World': 'Salutare lume',
'help': 'ajutor',
'Home': 'Acasă',
'How did you get here?': 'Cum ați ajuns aici?',
'htmledit': 'editare html',
'Import/Export': 'Import/Export',
'includes': 'include',
'Index': 'Index',
'insert new': 'adaugă nou',
'insert new %s': 'adaugă nou %s',
'Installed applications': 'Aplicații instalate',
'internal error': 'eroare internă',
'Internal State': 'Stare internă',
'Introduction': 'Introducere',
'Invalid Query': 'Interogare invalidă',
'Invalid action': 'Acțiune invalidă',
'Invalid email': 'E-mail invalid',
'invalid password': 'parolă invalidă',
'Invalid password': 'Parolă invalidă',
'Invalid Query': 'Interogare invalidă',
'invalid request': 'cerere invalidă',
'invalid ticket': 'tichet invalid',
'language file "%(filename)s" created/updated': 'fișier de limbă "%(filename)s" creat/actualizat',
'Language files (static strings) updated': 'Fișierele de limbă (șirurile statice de caractere) actualizate',
'languages': 'limbi',
'Languages': 'Limbi',
'languages updated': 'limbi actualizate',
'Last name': 'Nume',
'Last saved on:': 'Ultima salvare:',
'Layout': 'Șablon',
@@ -118,39 +192,54 @@
'Layouts': 'Șabloane',
'License for': 'Licență pentru',
'Live Chat': 'Chat live',
'loading...': 'încarc...',
'Logged in': 'Logat',
'Logged out': 'Delogat',
'Login': 'Autentificare',
'login': 'autentificare',
'Login to the Administrative Interface': 'Logare interfață de administrare',
'logout': 'ieșire',
'Logout': 'Ieșire',
'Lost Password': 'Parolă pierdută',
'Lost password?': 'Parolă pierdută?',
'Main Menu': 'Meniu principal',
'Menu Model': 'Model meniu',
'merge': 'unește',
'Models': 'Modele',
'models': 'modele',
'Modules': 'Module',
'modules': 'module',
'My Sites': 'Site-urile mele',
'NO': 'NU',
'Name': 'Nume',
'New': 'Nou',
'New Record': 'Înregistrare nouă',
'new application "%s" created': 'aplicația nouă "%s" a fost creată',
'New password': 'Parola nouă',
'New Record': 'Înregistrare nouă',
'new record inserted': 'înregistrare nouă adăugată',
'next 100 rows': 'următoarele 100 de linii',
'NO': 'NU',
'No databases in this application': 'Aplicație fără bază de date',
'Object or table name': 'Obiect sau nume de tabel',
'Old password': 'Parola veche',
'Online examples': 'Exemple online',
'Or': 'Sau',
'or import from csv file': 'sau importă din fișier csv',
'or provide application url:': 'sau furnizează adresă url:',
'Origin': 'Origine',
'Original/Translation': 'Original/Traducere',
'Other Plugins': 'Alte plugin-uri',
'Other Recipes': 'Alte rețete',
'Overview': 'Prezentare de ansamblu',
'pack all': 'împachetează toate',
'pack compiled': 'pachet compilat',
'Password': 'Parola',
"Password fields don't match": 'Câmpurile de parolă nu se potrivesc',
'Peeking at file': 'Vizualizare fișier',
'please input your password again': 'introduceți parola din nou',
'Plugins': 'Plugin-uri',
'Powered by': 'Pus în mișcare de',
'Preface': 'Prefață',
'previous 100 rows': '100 de linii anterioare',
'Profile': 'Profil',
'Python': 'Python',
'Query': 'Interogare',
@@ -158,52 +247,88 @@
'Quick Examples': 'Exemple rapide',
'RAM Cache Keys': 'Chei cache RAM',
'Recipes': 'Rețete',
'record': 'înregistrare',
'record does not exist': 'înregistrare inexistentă',
'record id': 'id înregistrare',
'Record ID': 'ID înregistrare',
'register': 'înregistrare',
'Register': 'Înregistrare',
'Registration identifier': 'Identificator de autentificare',
'Registration key': 'Cheie înregistrare',
'Registration successful': 'Autentificare reușită',
'Remember me (for 30 days)': 'Ține-mă minte (timp de 30 de zile)',
'remove compiled': 'șterge compilate',
'Request reset password': 'Cerere resetare parolă',
'Reset Password key': 'Cheie restare parolă',
'Resolve Conflict file': 'Fișier rezolvare conflict',
'restore': 'restaurare',
'revert': 'revenire',
'Role': 'Rol',
'Rows in table': 'Linii în tabel',
'Rows selected': 'Linii selectate',
'save': 'salvare',
'Save profile': 'Salvează profil',
'Saved file hash:': 'Hash fișier salvat:',
'Search': 'Căutare',
'Semantic': 'Semantică',
'Services': 'Servicii',
'session expired': 'sesiune expirată',
'shell': 'line de commandă',
'site': 'site',
'some files could not be removed': 'anumite fișiere n-au putut fi șterse',
'starts with': 'începe cu',
'state': 'stare',
'static': 'static',
'Static files': 'Fișiere statice',
'Stylesheet': 'Foaie de stiluri',
'Submit': 'Înregistrează',
'Support': 'Suport',
'Sure you want to delete this object?': 'Sigur ștergeți acest obiect?',
'table': 'tabel',
'Table name': 'Nume tabel',
'test': 'test',
'Testing application': 'Testare aplicație',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Interogarea (query)" este o condiție de tipul "db.tabel1.câmp1==\'valoare\'". Ceva de genul "db.tabel1.câmp1==db.tabel2.câmp2" generează un JOIN SQL.',
'the application logic, each URL path is mapped in one exposed function in the controller': 'logica aplicației, fiecare rută URL este mapată într-o funcție expusă de controlor',
'The Core': 'Nucleul',
'The Views': 'Vederile',
'the data representation, define database tables and sets': 'reprezentarea datelor, definește tabelele bazei de date și seturile (de date)',
'The output of the file is a dictionary that was rendered by the view %s': 'Fișierul produce un dicționar care a fost prelucrat de vederea %s',
'the presentations layer, views are also known as templates': 'nivelul de prezentare, vederile sunt de asemenea numite și șabloane',
'The Views': 'Vederile',
'There are no controllers': 'Nu există controlori',
'There are no models': 'Nu există modele',
'There are no modules': 'Nu există module',
'There are no static files': 'Nu există fișiere statice',
'There are no translators, only default language is supported': 'Nu există traduceri, doar limba implicită este suportată',
'There are no views': 'Nu există vederi',
'these files are served without processing, your images go here': 'aceste fișiere sunt servite fără procesare, imaginea se plasează acolo',
'This App': 'Această aplicație',
'This is a copy of the scaffolding application': 'Aceasta este o copie a aplicației schelet',
'This is the %(filename)s template': 'Aceasta este șablonul fișierului %(filename)s',
'Ticket': 'Tichet',
'Timestamp': 'Moment în timp (timestamp)',
'to previous version.': 'la versiunea anterioară.',
'too short': 'prea scurt',
'translation strings for the application': 'șiruri de caractere folosite la traducerea aplicației',
'True': 'Adevărat',
'try': 'încearcă',
'try something like': 'încearcă ceva de genul',
'Twitter': 'Twitter',
'Unable to check for upgrades': 'Imposibil de verificat dacă există actualizări',
'unable to create application "%s"': 'imposibil de creat aplicația "%s"',
'unable to delete file "%(filename)s"': 'imposibil de șters fișierul "%(filename)s"',
'Unable to download': 'Imposibil de descărcat',
'Unable to download app': 'Imposibil de descărcat aplicația',
'unable to parse csv file': 'imposibil de analizat fișierul csv',
'unable to uninstall "%s"': 'imposibil de dezinstalat "%s"',
'uncheck all': 'decoșează tot',
'uninstall': 'dezinstalează',
'update': 'actualizează',
'update all languages': 'actualizează toate limbile',
'Update:': 'Actualizare:',
'upload application:': 'incarcă aplicația:',
'Upload existing application': 'Încarcă aplicația existentă',
'upload file:': 'încarcă fișier:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Folosiți (...)&(...) pentru AND, (...)|(...) pentru OR, și ~(...) pentru NOT, pentru a crea interogări complexe.',
'User %(id)s Logged-in': 'Utilizator %(id)s autentificat',
'User %(id)s Logged-out': 'Utilizator %(id)s delogat',
@@ -212,10 +337,16 @@
'User %(id)s Profile updated': 'Profil utilizator %(id)s actualizat',
'User %(id)s Registered': 'Utilizator %(id)s înregistrat',
'User ID': 'ID utilizator',
'value already in database or empty': 'Valoare existentă în baza de date sau vidă',
'Verify Password': 'Verifică parola',
'versioning': 'versiuni',
'Videos': 'Video-uri',
'View': 'Vedere',
'view': 'vedere',
'Views': 'Vederi',
'views': 'vederi',
'web2py is up to date': 'web2py este la zi',
'web2py Recent Tweets': 'Ultimele tweet-uri web2py',
'Welcome': 'Bine ați venit',
'Welcome %s': 'Bine ați venit %s',
'Welcome to web2py': 'Bun venit la web2py',
@@ -225,131 +356,4 @@
'You are successfully running web2py': 'Rulați cu succes web2py',
'You can modify this application and adapt it to your needs': 'Puteți modifica și adapta aplicația nevoilor dvs.',
'You visited the url %s': 'Ați vizitat adresa %s',
'about': 'despre',
'additional code for your application': 'cod suplimentar pentru aplicația dvs.',
'admin disabled because no admin password': 'administrare dezactivată deoarece parola de administrator nu a fost furnizată',
'admin disabled because not supported on google app engine': 'administrare dezactivată deoarece funcționalitatea nu e suportat pe Google App Engine',
'admin disabled because unable to access password file': 'administrare dezactivată deoarece nu există acces la fișierul cu parole',
'and rename it (required):': 'și renumiți (obligatoriu):',
'and rename it:': ' și renumiți:',
'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'appadmin dezactivat deoarece conexiunea nu e sigură',
'application "%s" uninstalled': 'aplicația "%s" a fost dezinstalată',
'application compiled': 'aplicația a fost compilată',
'application is compiled and cannot be designed': 'aplicația este compilată și nu poate fi editată',
'cache': 'cache',
'cache, errors and sessions cleaned': 'cache, erori și sesiuni golite',
'cannot create file': 'fișier imposibil de creat',
'cannot upload file "%(filename)s"': 'imposibil de încărcat fișierul "%(filename)s"',
'change password': 'schimbare parolă',
'check all': 'coșați tot',
'clean': 'golire',
'click to check for upgrades': 'Clic pentru a verifica dacă există upgrade-uri',
'compile': 'compilare',
'compiled application removed': 'aplicația compilată a fost ștearsă',
'contains': 'conține',
'controllers': 'controlori',
'create file with filename:': 'crează fișier cu numele:',
'create new application:': 'crează aplicație nouă:',
'crontab': 'crontab',
'currently saved or': 'în prezent salvat sau',
'customize me!': 'Personalizează-mă!',
'data uploaded': 'date încărcate',
'database': 'bază de date',
'database %s select': 'selectare bază de date %s',
'database administration': 'administrare bază de date',
'db': 'db',
'defines tables': 'definire tabele',
'delete': 'șterge',
'delete all checked': 'șterge tot ce e coșat',
'design': 'design',
'done!': 'gata!',
'edit': 'editare',
'edit controller': 'editare controlor',
'edit profile': 'editare profil',
'enter a number between %(min)g and %(max)g': 'introduceți un număr între %(min)g și %(max)g',
'enter an integer between %(min)g and %(max)g': 'introduceți un întreg între %(min)g și %(max)g',
'errors': 'erori',
'export as csv file': 'exportă ca fișier csv',
'exposes': 'expune',
'extends': 'extinde',
'failed to reload module': 'reîncarcare modul nereușită',
'file "%(filename)s" created': 'fișier "%(filename)s" creat',
'file "%(filename)s" deleted': 'fișier "%(filename)s" șters',
'file "%(filename)s" uploaded': 'fișier "%(filename)s" încărcat',
'file "%(filename)s" was not deleted': 'fișierul "%(filename)s" n-a fost șters',
'file "%s" of %s restored': 'fișier "%s" de %s restaurat',
'file changed on disk': 'fișier modificat pe disc',
'file does not exist': 'fișier inexistent',
'file saved on %(time)s': 'fișier salvat %(time)s',
'file saved on %s': 'fișier salvat pe %s',
'help': 'ajutor',
'htmledit': 'editare html',
'includes': 'include',
'insert new': 'adaugă nou',
'insert new %s': 'adaugă nou %s',
'internal error': 'eroare internă',
'invalid password': 'parolă invalidă',
'invalid request': 'cerere invalidă',
'invalid ticket': 'tichet invalid',
'language file "%(filename)s" created/updated': 'fișier de limbă "%(filename)s" creat/actualizat',
'languages': 'limbi',
'languages updated': 'limbi actualizate',
'loading...': 'încarc...',
'login': 'autentificare',
'logout': 'ieșire',
'merge': 'unește',
'models': 'modele',
'modules': 'module',
'new application "%s" created': 'aplicația nouă "%s" a fost creată',
'new record inserted': 'înregistrare nouă adăugată',
'next 100 rows': 'următoarele 100 de linii',
'or import from csv file': 'sau importă din fișier csv',
'or provide application url:': 'sau furnizează adresă url:',
'pack all': 'împachetează toate',
'pack compiled': 'pachet compilat',
'please input your password again': 'introduceți parola din nou',
'previous 100 rows': '100 de linii anterioare',
'record': 'înregistrare',
'record does not exist': 'înregistrare inexistentă',
'record id': 'id înregistrare',
'register': 'înregistrare',
'remove compiled': 'șterge compilate',
'restore': 'restaurare',
'revert': 'revenire',
'save': 'salvare',
'session expired': 'sesiune expirată',
'shell': 'line de commandă',
'site': 'site',
'some files could not be removed': 'anumite fișiere n-au putut fi șterse',
'starts with': 'începe cu',
'state': 'stare',
'static': 'static',
'table': 'tabel',
'test': 'test',
'the application logic, each URL path is mapped in one exposed function in the controller': 'logica aplicației, fiecare rută URL este mapată într-o funcție expusă de controlor',
'the data representation, define database tables and sets': 'reprezentarea datelor, definește tabelele bazei de date și seturile (de date)',
'the presentations layer, views are also known as templates': 'nivelul de prezentare, vederile sunt de asemenea numite și șabloane',
'these files are served without processing, your images go here': 'aceste fișiere sunt servite fără procesare, imaginea se plasează acolo',
'to previous version.': 'la versiunea anterioară.',
'too short': 'prea scurt',
'translation strings for the application': 'șiruri de caractere folosite la traducerea aplicației',
'try': 'încearcă',
'try something like': 'încearcă ceva de genul',
'unable to create application "%s"': 'imposibil de creat aplicația "%s"',
'unable to delete file "%(filename)s"': 'imposibil de șters fișierul "%(filename)s"',
'unable to parse csv file': 'imposibil de analizat fișierul csv',
'unable to uninstall "%s"': 'imposibil de dezinstalat "%s"',
'uncheck all': 'decoșează tot',
'uninstall': 'dezinstalează',
'update': 'actualizează',
'update all languages': 'actualizează toate limbile',
'upload application:': 'incarcă aplicația:',
'upload file:': 'încarcă fișier:',
'value already in database or empty': 'Valoare existentă în baza de date sau vidă',
'versioning': 'versiuni',
'view': 'vedere',
'views': 'vederi',
'web2py Recent Tweets': 'Ultimele tweet-uri web2py',
'web2py is up to date': 'web2py este la zi',
}
+9 -2
View File
@@ -35,7 +35,7 @@ except ImportError:
logger = logging.getLogger("web2py.cache")
__all__ = ['Cache', 'lazy_lazy_cache']
__all__ = ['Cache', 'lazy_cache']
DEFAULT_TIME_EXPIRE = 300
@@ -478,7 +478,14 @@ class Cache(object):
return CacheAction(func,key,time_expire,self,cache_model)
return tmp
def lazy_lazy_cache(key=None,time_expire=None,cache_model='ram'):
def lazy_cache(key=None,time_expire=None,cache_model='ram'):
"""
can be used to cache any function including in modules,
as long as the cached function is only called within a web2py request
if a key is not provided, one is generated from the function name
the time_expire defaults to None (no cache expiration)
if cache_model is "ram" then the model is current.cache.ram, etc.
"""
def decorator(f,key=key,time_expire=time_expire,cache_model=cache_model):
key = key or repr(f)
def g(*c,**d):
+4 -1
View File
@@ -33,7 +33,10 @@ def getcfs(key, filename, filter=None):
This is used on Google App Engine since pyc files cannot be saved.
"""
t = os.stat(filename).st_mtime
try:
t = os.stat(filename).st_mtime
except OSError:
return filter()
cfs_lock.acquire()
item = cfs.get(key, None)
cfs_lock.release()
+3 -3
View File
@@ -855,9 +855,9 @@ class Sheet:
sorted_headers = [TH(),] + \
[TH(header[1]) for header in sorted(unsorted_headers)]
table.insert(0, TR(*sorted_headers, _class="%s_fieldnames" % \
attributes["_class"]))
table.insert(0, TR(*sorted_headers,
**{_class:"%s_fieldnames" % \
attributes["_class"]}))
else:
data = SCRIPT(""" // web2py Spreadsheets: no db data.""")
+6
View File
@@ -3785,6 +3785,9 @@ class GoogleSQLAdapter(UseDatabaseStoredFile,MySQLAdapter):
self.execute("SET FOREIGN_KEY_CHECKS=1;")
self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';")
def execute(self,a):
return self.log_execute(a.decode('utf8'))
class NoSQLAdapter(BaseAdapter):
can_select_for_update = False
@@ -7109,6 +7112,9 @@ class Table(dict):
"primarykey must be a list of fields from table '%s'" \
% tablename
self._primarykey = primarykey
if len(primarykey)==1:
self._id = [f for f in fields if isinstance(f,Field) \
and f.name==primarykey[0]][0]
elif not [f for f in fields if isinstance(f,Field) and f.type=='id']:
field = Field('id', 'id')
newfields.append(field)
+33 -2
View File
@@ -472,7 +472,18 @@ class XmlComponent(object):
def xml(self):
raise NotImplementedError
def __mul__(self,n):
return CAT(*[self for i in range(n)])
def __add__(self,other):
if isinstance(self,CAT):
components = self.components
else:
components = [self]
if isinstance(other,CAT):
components += other.components
else:
components += [other]
return CAT(*components)
class XML(XmlComponent):
"""
@@ -2023,8 +2034,28 @@ class FORM(DIV):
self.validate(**kwargs)
return self
REDIRECT_JS = "window.location='%s';return false"
def add_button(self,value,url,_class=None):
self[0][-1][1].append(INPUT(_type="button",_value=value,_onclick="window.location='%s';return false" % url,_class=_class))
self[0][-1][1].append(INPUT(_type="button",_value=value,_class=_class,
_onclick=self.REDIRECT_JS % url))
@staticmethod
def dialog(text='OK',buttons=None,hidden=None):
if not buttons: buttons = {}
if not hidden: hidden={}
inputs = [INPUT(_type='button',
_value=name,
_onclick=FORM.REDIRECT_JS % link) \
for name,link in buttons.items()]
inputs += [INPUT(_type='hidden',
_name=name,
_value=value)
for name,value in hidden.items()]
form = FORM(INPUT(_type='submit',_value=text),*inputs)
form.process()
return form
class BEAUTIFY(DIV):
+3
View File
@@ -87,6 +87,7 @@ from settings import global_settings
from validators import CRYPT
from cache import Cache
from html import URL as Url
from utils import is_valid_ip_address
import newcron
import rewrite
@@ -402,6 +403,8 @@ def wsgibase(environ, responder):
try: local_hosts.append(socket.gethostbyname(http_host))
except socket.gaierror: pass
request.client = get_client(request.env)
if not is_valid_ip_address(request.client):
raise HTTP(400,"Bad Request")
request.folder = abspath('applications',
request.application) + os.sep
x_req_with = str(request.env.http_x_requested_with).lower()
+7 -4
View File
@@ -410,8 +410,11 @@ class CheckboxesWidget(OptionsWidget):
LABEL(v,_for='%s%s' % (field.name,k))))
opts.append(child(tds))
if opts:
opts[-1][0][0]['hideerror'] = False
opts.append(INPUT(_class="hidden", requires=attr.get('requires', None),
_disabled="disabled", _name=field.name,
hideerror=False))
return parent(*opts, **attr)
@@ -1173,8 +1176,7 @@ class SQLFORM(FORM):
row_id = '%s_%s%s' % (self.table, fieldname, SQLFORM.ID_ROW_SUFFIX)
widget = field.widget(field, value)
self.field_parent[row_id].components = [ widget ]
if not field.type.startswith('list:'):
self.field_parent[row_id]._traverse(False, hideerror)
self.field_parent[row_id]._traverse(False, hideerror)
self.custom.widget[ fieldname ] = widget
self.accepted = ret
return ret
@@ -1241,7 +1243,8 @@ class SQLFORM(FORM):
### do not know why this happens, it should not
(source_file, original_filename) = \
(cStringIO.StringIO(f), 'file.txt')
newfilename = field.store(source_file, original_filename, field.uploadfolder)
newfilename = field.store(source_file, original_filename,
field.uploadfolder)
# this line is for backward compatibility only
self.vars['%s_newfilename' % fieldname] = newfilename
fields[fieldname] = newfilename
+11 -4
View File
@@ -881,7 +881,8 @@ class Auth(object):
return URL(args=current.request.args,vars=current.request.vars)
def __init__(self, environment=None, db=None, mailer=True,
hmac_key=None, controller='default', function='user', cas_provider=None):
hmac_key=None, controller='default', function='user',
cas_provider=None, signature=True):
"""
auth=Auth(db)
@@ -1133,7 +1134,10 @@ class Auth(object):
# when user wants to be logged in for longer
response.cookies[response.session_id_name]["expires"] = \
auth.expiration
if signature:
self.define_signature()
else:
self.signature = None
def _get_user_id(self):
"accessor for auth.user_id"
@@ -1332,7 +1336,8 @@ class Auth(object):
db = self.db
settings = self.settings
self.define_signature()
if not self.signature:
self.define_signature()
if signature==True:
signature_list = [self.signature]
elif not signature:
@@ -1622,7 +1627,7 @@ class Auth(object):
userfield = 'email'
passfield = self.settings.password_field
user = self.db(table_user[userfield] == username).select().first()
if user:
if user and user.get(passfield,False):
password = table_user[passfield].validate(password)[0]
if not user.registration_key and password == user[passfield]:
user = Storage(table_user._filter_fields(user, id=True))
@@ -3392,6 +3397,8 @@ class Crud(object):
query = table.id > 0
if not fields:
fields = [field for field in table if field.readable]
else:
fields = [table[f] if isinstance(f,str) else f for f in fields]
rows = self.db(query).select(*fields,**dict(orderby=orderby,
limitby=limitby))
return rows
+21 -1
View File
@@ -16,6 +16,7 @@ import random
import time
import os
import logging
import socket
from contrib.pbkdf2 import pbkdf2_hex
logger = logging.getLogger("web2py")
@@ -69,7 +70,7 @@ def get_digest(value):
elif value == "sha512":
return hashlib.sha512
else:
raise ValueError("Invalid digest algorithm: %s" % value)
raise ValueError("Invalid digest algorithm: %s" % value)
DIGEST_ALG_BY_SIZE = {
128/4: 'md5',
@@ -146,6 +147,25 @@ def web2py_uuid():
bytes = ''.join(chr(c ^ ctokens[i]) for i,c in enumerate(bytes))
return str(uuid.UUID(bytes=bytes, version=4))
def is_valid_ip_address(address):
"""
>>> is_valid_ip_address('127.0')
False
>>> is_valid_ip_address('127.0.0.1')
True
>>> is_valid_ip_address('2001:660::1')
True
"""
try:
if address.count('.')==3:
addr = socket.inet_aton(address)
else:
addr = socket.inet_pton(socket.AF_INET6, address)
except AttributeError: # no socket.inet_pton
return False
except socket.error:
return False
return True
+30 -13
View File
@@ -220,44 +220,54 @@ class web2pyDialog(object):
justify=Tkinter.LEFT).grid(row=0,
column=0,
sticky=sticky)
self.ip = Tkinter.Entry(self.root)
self.ip.insert(Tkinter.END, self.options.ip)
self.ip.grid(row=0, column=1, sticky=sticky)
self.ips = {}
self.selected_ip = Tkinter.StringVar()
row=0
ips = [('127.0.0.1','Local')] + \
[(ip,'Public') for ip in options.ips] + \
[('0.0.0.0','Public')]
for ip,legend in ips:
self.ips[ip] = Tkinter.Radiobutton(
self.root,text='%s (%s)' % (legend,ip),
variable=self.selected_ip, value=ip)
self.ips[ip].grid(row=row, column=1, sticky=sticky)
if row==0: self.ips[ip].select()
row+=1
shift = row
# Port
Tkinter.Label(self.root,
text='Server Port:',
justify=Tkinter.LEFT).grid(row=1,
justify=Tkinter.LEFT).grid(row=shift,
column=0,
sticky=sticky)
self.port_number = Tkinter.Entry(self.root)
self.port_number.insert(Tkinter.END, self.options.port)
self.port_number.grid(row=1, column=1, sticky=sticky)
self.port_number.grid(row=shift, column=1, sticky=sticky)
# Password
Tkinter.Label(self.root,
text='Choose Password:',
justify=Tkinter.LEFT).grid(row=2,
justify=Tkinter.LEFT).grid(row=shift+1,
column=0,
sticky=sticky)
self.password = Tkinter.Entry(self.root, show='*')
self.password.bind('<Return>', lambda e: self.start())
self.password.focus_force()
self.password.grid(row=2, column=1, sticky=sticky)
self.password.grid(row=shift+1, column=1, sticky=sticky)
# Prepare the canvas
self.canvas = Tkinter.Canvas(self.root,
width=300,
height=100,
bg='black')
self.canvas.grid(row=3, column=0, columnspan=2)
self.canvas.grid(row=shift+2, column=0, columnspan=2)
self.canvas.after(1000, self.update_canvas)
# Prepare the frame
frame = Tkinter.Frame(self.root)
frame.grid(row=4, column=0, columnspan=2)
frame.grid(row=shift+3, column=0, columnspan=2)
# Start button
self.button_start = Tkinter.Button(frame,
@@ -359,7 +369,7 @@ class web2pyDialog(object):
if not password:
self.error('no password, no web admin interface')
ip = self.ip.get()
ip = self.selected_ip.get()
regexp = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
if ip and not re.compile(regexp).match(ip):
@@ -416,7 +426,7 @@ class web2pyDialog(object):
thread.start_new_thread(start_browser, (proto, ip, port))
self.password.configure(state='readonly')
self.ip.configure(state='readonly')
[ip.configure(state='disabled') for ip in self.ips.values()]
self.port_number.configure(state='readonly')
if self.tb:
@@ -435,7 +445,7 @@ class web2pyDialog(object):
self.button_start.configure(state='normal')
self.button_stop.configure(state='disabled')
self.password.configure(state='normal')
self.ip.configure(state='normal')
[ip.configure(state='normal') for ip in self.ips.values()]
self.port_number.configure(state='normal')
self.server.stop()
@@ -790,6 +800,13 @@ def console():
global_settings.cmd_options = options
global_settings.cmd_args = args
try:
options.ips = [
ip for ip in socket.gethostbyname_ex(socket.getfqdn())[2]
if ip!='127.0.0.1']
except socket.gaierror:
options.ips = []
if options.run_system_tests:
run_system_tests()
+9 -4
View File
@@ -90,8 +90,12 @@ class Web2pyService(Service):
cls = _winreg.QueryValue(h, 'PythonClass')
finally:
_winreg.CloseKey(h)
dir = os.path.dirname(cls)
dir = os.path.dirname(cls)
os.chdir(dir)
from gluon.settings import global_settings
global_settings.gluon_parent = dir
from gluon.custom_import import custom_import_install
custom_import_install(dir)
return True
except:
self.log("Can't change to web2py working path; server is stopped")
@@ -149,8 +153,10 @@ class Web2pyService(Service):
def web2py_windows_service_handler(argv=None, opt_file='options'):
path = os.path.dirname(__file__)
classstring = os.path.normpath(os.path.join(up(path),
'gluon.winservice.Web2pyService'))
web2py_path = up(path)
os.chdir(web2py_path)
classstring = os.path.normpath(
os.path.join(web2py_path,'gluon.winservice.Web2pyService'))
if opt_file:
Web2pyService._exe_args_ = opt_file
win32serviceutil.HandleCommandLine(Web2pyService,
@@ -158,7 +164,6 @@ def web2py_windows_service_handler(argv=None, opt_file='options'):
win32serviceutil.HandleCommandLine(Web2pyService,
serviceClassString=classstring, argv=argv)
if __name__ == '__main__':
web2py_windows_service_handler()