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?' echo 'did you uncomment import_all in gluon/main.py?'
python2.5 -c 'import compileall; compileall.compile_dir("gluon/")' python2.5 -c 'import compileall; compileall.compile_dir("gluon/")'
#python web2py.py -S welcome -R __exit__.py #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/{} \; 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 * 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 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 cp README.markdown ../web2py_osx/web2py/web2py.app/Contents/Resources
@@ -85,9 +85,9 @@ app:
mv ../web2py_osx/web2py_osx.zip . mv ../web2py_osx/web2py_osx.zip .
win: win:
echo 'did you uncomment import_all in gluon/main.py?' 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/{} \; find gluon -path '*.pyc' -exec cp {} ../web2py_win/library/{} \;
cd ../web2py_win/library/; unzip ../library.zip
cd ../web2py_win/library/; zip -r ../library.zip * cd ../web2py_win/library/; zip -r ../library.zip *
mv ../web2py_win/library.zip ../web2py_win/web2py mv ../web2py_win/library.zip ../web2py_win/web2py
cp README.markdown ../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 from glob import glob
import shutil import shutil
import platform 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, from gluon.languages import (regex_language, read_possible_languages,
read_possible_plurals, lang_sampling, read_possible_plurals, lang_sampling,
read_dict, write_dict, read_plural_dict, read_dict, write_dict, read_plural_dict,
write_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') session.flash = T('disabled in demo mode')
redirect(URL('site')) redirect(URL('site'))
if not is_manager() and request.function in ['change_password','upgrade_web2py']: if not is_manager() and request.function in ['change_password','upgrade_web2py']:
session.flash = T('disabled in multi user mode') session.flash = T('disabled in multi user mode')
redirect(URL('site')) redirect(URL('site'))
@@ -188,6 +197,23 @@ def site():
msg = 'you must specify a name for the uploaded application' msg = 'you must specify a name for the uploaded application'
response.flash = T(msg) 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: elif file_or_appurl and request.vars.filename:
# fetch an application via URL or file upload # fetch an application via URL or file upload
f = None f = None
@@ -377,6 +403,32 @@ def delete():
redirect(URL(sender, anchor=request.vars.id2)) redirect(URL(sender, anchor=request.vars.id2))
return dict(filename=filename, sender=sender) 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(): def enable():
app = get_app() app = get_app()
filename = os.path.join(apath(app, r=request),'DISABLED') filename = os.path.join(apath(app, r=request),'DISABLED')
@@ -854,6 +906,11 @@ def design():
modules = modules=[x.replace('\\','/') for x in modules] modules = modules=[x.replace('\\','/') for x in modules]
modules.sort() 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 # Get all static files
statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*') statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*')
statics = [x.replace('\\','/') for x in statics] statics = [x.replace('\\','/') for x in statics]
@@ -908,6 +965,7 @@ def design():
modules=filter_plugins(modules,plugins), modules=filter_plugins(modules,plugins),
extend=extend, extend=extend,
include=include, include=include,
privates=filter_plugins(privates,plugins),
statics=filter_plugins(statics,plugins), statics=filter_plugins(statics,plugins),
languages=languages, languages=languages,
plurals=plurals, plurals=plurals,
@@ -924,7 +982,7 @@ def delete_plugin():
redirect(URL('design', args=app, anchor=request.vars.id)) redirect(URL('design', args=app, anchor=request.vars.id))
elif 'delete' in request.vars: elif 'delete' in request.vars:
try: 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) path=os.path.join(apath(app,r=request),folder)
for item in os.listdir(path): for item in os.listdir(path):
if item.rsplit('.',1)[0] == plugin_name: if item.rsplit('.',1)[0] == plugin_name:
@@ -994,6 +1052,11 @@ def plugin():
modules = modules=[x.replace('\\','/') for x in modules] modules = modules=[x.replace('\\','/') for x in modules]
modules.sort() 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 # Get all static files
statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*') statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*')
statics = [x.replace('\\','/') for x in statics] statics = [x.replace('\\','/') for x in statics]
@@ -1023,6 +1086,7 @@ def plugin():
modules=filter_plugins(modules), modules=filter_plugins(modules),
extend=extend, extend=extend,
include=include, include=include,
privates=filter_plugins(privates),
statics=filter_plugins(statics), statics=filter_plugins(statics),
languages=languages, languages=languages,
crontab=crontab) crontab=crontab)
@@ -1142,10 +1206,11 @@ def create_file():
# coding: utf8 # coding: utf8
from gluon import *\n""")[1:] 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): if request.vars.plugin and not filename.startswith('plugin_%s/' % request.vars.plugin):
filename = 'plugin_%s/%s' % (request.vars.plugin, filename) filename = 'plugin_%s/%s' % (request.vars.plugin, filename)
text = '' text = ''
else: else:
redirect(request.vars.sender+anchor) redirect(request.vars.sender+anchor)
@@ -1545,3 +1610,76 @@ def bulk_register():
redirect(URL('site')) redirect(URL('site'))
return locals() 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 assert repo.bare == False
for i in form.vars.applications: 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) appdest = os.path.join(osrepo,'wsgi',osname,'applications',i)
dir_util.copy_tree(appsrc,appdest) dir_util.copy_tree(appsrc,appdest)
#shutil.copytree(appsrc,appdest) #shutil.copytree(appsrc,appdest)
+1 -1
View File
@@ -4,6 +4,6 @@
<div class="center"> <div class="center">
<h2>{{=T('Are you sure you want to delete file "%s"?', filename)}}</h2> <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> </div>
@@ -57,6 +57,7 @@ def deletefile(arglist, vars={}):
{{=button('#languages', T("languages"))}} {{=button('#languages', T("languages"))}}
{{=button('#static', T("static"))}} {{=button('#static', T("static"))}}
{{=button('#modules', T("modules"))}} {{=button('#modules', T("modules"))}}
{{=button('#private', T("private files"))}}
{{=button('#plugins', T("plugins"))}} {{=button('#plugins', T("plugins"))}}
</span> </span>
</p> </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> {{=file_upload_form('%s/modules/' % app, 'modules')}}</div>
</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 --> <!-- PLUGINS -->
<h3 id="plugins" onclick="collapse('plugins_inner');" class="component"> <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"))}} {{=button(URL('remove_compiled_app',args=a), T("Remove compiled"))}}
{{pass}} {{pass}}
{{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:}} {{if a!=request.application:}}
{{=button(URL('uninstall',args=a), T("Uninstall"))}} {{=button(URL('uninstall',args=a), T("Uninstall"))}}
{{=button_enable(URL('enable',args=a), a)}} {{=button_enable(URL('enable',args=a), a)}}
@@ -117,16 +121,18 @@
</tr> </tr>
<tr> <tr>
<td> <td>
{{=LABEL(T("Get from URL:"), _for='upload_url')}} {{=LABEL(T("Get from URL:"), _for='upload_url')}}
</td> </td>
<td> <td>
<input id="appurl" name="appurl" type="text" id="upload_url"/> <input id="appurl" name="appurl" type="text" id="upload_url"/><br/>
</td> </td>
</tr> </tr>
<tr> <tr>
<td></td> <td>
<td> ({{=T('can be a git repo')}})
<input type="checkbox" name="overwrite_check" id="upload_overwrite" /> </td>
<td>
<input type="checkbox" name="overwrite_check" id="upload_overwrite" />
{{=LABEL(T("Overwrite installed app"), _for='upload_overwrite')}} {{=LABEL(T("Overwrite installed app"), _for='upload_overwrite')}}
</td> </td>
</tr> </tr>
@@ -142,13 +148,11 @@
</div> </div>
<!-- DEPLOY ON GAE --> <!-- DEPLOY ON GAE -->
<div class="box"> <div class="box">
<h3>{{=T("Deploy on Google App Engine")}}</h3> <h3>{{=T("Deploy")}}</h3>
<p>{{=button(URL('gae','deploy'), T('Deploy'))}}</p> <p>
</div><br/> {{=button(URL('gae','deploy'), T('Deploy on Google App Engine'))}}
<!-- DEPLOY ON OPENSHIFT --> {{=button(URL('openshift','deploy'),T('Deploy to OpenShift'))}}
<div class="box"> </p>
<h3>{{=T("Deploy to OpenShift")}}</h3>
<p>{{=button(URL('openshift','deploy'),T('Deploy'))}}</p>
</div><br/> </div><br/>
{{if TWITTER_HASH:}} {{if TWITTER_HASH:}}
<div class="box"> <div class="box">
+38 -37
View File
@@ -3,14 +3,18 @@
'!langcode!': 'it', '!langcode!': 'it',
'!langname!': 'Italiano', '!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 ', '"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', '%d seconds ago': '%d seconds ago',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
'%s %%{row} deleted': '%s righe ("record") cancellate', '%s %%{row} deleted': '%s righe ("record") cancellate',
'%s %%{row} updated': '%s righe ("record") modificate', '%s %%{row} updated': '%s righe ("record") modificate',
'%s selected': '%s selezionato', '%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', 'Administrative interface': 'Interfaccia amministrativa',
'appadmin is disabled because insecure channel': 'Amministrazione (appadmin) disabilitata: comunicazione non sicura',
'Available databases and tables': 'Database e tabelle disponibili', 'Available databases and tables': 'Database e tabelle disponibili',
'cache': 'cache',
'Cannot be empty': 'Non può essere vuoto', 'Cannot be empty': 'Non può essere vuoto',
'change password': 'Cambia password',
'Check to delete': 'Seleziona per cancellare', 'Check to delete': 'Seleziona per cancellare',
'Client IP': 'Client IP', 'Client IP': 'Client IP',
'Controller': 'Controller', 'Controller': 'Controller',
@@ -18,50 +22,79 @@
'Current request': 'Richiesta (request) corrente', 'Current request': 'Richiesta (request) corrente',
'Current response': 'Risposta (response) corrente', 'Current response': 'Risposta (response) corrente',
'Current session': 'Sessione (session) corrente', 'Current session': 'Sessione (session) corrente',
'DB Model': 'Modello di DB', 'customize me!': 'Personalizzami!',
'data uploaded': 'dati caricati',
'Database': 'Database', 'Database': 'Database',
'database': 'database',
'database %s select': 'database %s select',
'db': 'db',
'DB Model': 'Modello di DB',
'Delete': 'Delete', 'Delete': 'Delete',
'Delete:': 'Cancella:', 'Delete:': 'Cancella:',
'Description': 'Descrizione', 'Description': 'Descrizione',
'design': 'progetta',
'Documentation': 'Documentazione', 'Documentation': 'Documentazione',
'done!': 'fatto!',
'E-mail': 'E-mail', 'E-mail': 'E-mail',
'Edit': 'Modifica', 'Edit': 'Modifica',
'Edit This App': 'Modifica questa applicazione',
'Edit current record': 'Modifica record corrente', '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', 'First name': 'Nome',
'Group ID': 'ID Gruppo', 'Group ID': 'ID Gruppo',
'hello': 'hello',
'hello world': 'salve mondo',
'Hello World': 'Salve Mondo', 'Hello World': 'Salve Mondo',
'Hello World in a flash!': 'Salve Mondo in un flash!', 'Hello World in a flash!': 'Salve Mondo in un flash!',
'Import/Export': 'Importa/Esporta', 'Import/Export': 'Importa/Esporta',
'Index': 'Indice', 'Index': 'Indice',
'insert new': 'inserisci nuovo',
'insert new %s': 'inserisci nuovo %s',
'Internal State': 'Stato interno', 'Internal State': 'Stato interno',
'Invalid Query': 'Richiesta (query) non valida',
'Invalid email': 'Email non valida', 'Invalid email': 'Email non valida',
'Invalid Query': 'Richiesta (query) non valida',
'invalid request': 'richiesta non valida',
'Last name': 'Cognome', 'Last name': 'Cognome',
'Layout': 'Layout', 'Layout': 'Layout',
'login': 'accesso',
'logout': 'uscita',
'lost password?': 'dimenticato la password?',
'Main Menu': 'Menu principale', 'Main Menu': 'Menu principale',
'Menu Model': 'Menu Modelli', 'Menu Model': 'Menu Modelli',
'Name': 'Nome', 'Name': 'Nome',
'New Record': 'Nuovo elemento (record)', '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', 'No databases in this application': 'Nessun database presente in questa applicazione',
'not authorized': 'non autorizzato',
'Online examples': 'Vedere gli esempi', 'Online examples': 'Vedere gli esempi',
'or import from csv file': 'oppure importa da file CSV',
'Origin': 'Origine', 'Origin': 'Origine',
'Password': 'Password', 'Password': 'Password',
'Powered by': 'Powered by', 'Powered by': 'Powered by',
'previous 100 rows': '100 righe precedenti',
'Query:': 'Richiesta (query):', 'Query:': 'Richiesta (query):',
'record': 'record',
'record does not exist': 'il record non esiste',
'record id': 'record id',
'Record ID': 'Record ID', 'Record ID': 'Record ID',
'register': 'registrazione',
'Registration key': 'Chiave di Registazione', 'Registration key': 'Chiave di Registazione',
'Reset Password key': 'Resetta chiave Password ', 'Reset Password key': 'Resetta chiave Password ',
'Role': 'Ruolo', 'Role': 'Ruolo',
'Rows in table': 'Righe nella tabella', 'Rows in table': 'Righe nella tabella',
'Rows selected': 'Righe selezionate', 'Rows selected': 'Righe selezionate',
'state': 'stato',
'Stylesheet': 'Foglio di stile (stylesheet)', 'Stylesheet': 'Foglio di stile (stylesheet)',
'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?', 'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?',
'table': 'tabella',
'Table name': 'Nome 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 "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', '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)", 'This is a copy of the scaffolding application': "Questa è una copia dell'applicazione di base (scaffold)",
'Timestamp': 'Ora (timestamp)', 'Timestamp': 'Ora (timestamp)',
'unable to parse csv file': 'non riesco a decodificare questo file CSV',
'Update': 'Update', 'Update': 'Update',
'Update:': 'Aggiorna:', '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).', '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 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 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", '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ă', '!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', '"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', '%(nrows)s records found': '%(nrows)s înregistrări găsite',
'%Y-%m-%d': '%Y-%m-%d', '%d days ago': '%d days ago',
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', '%d weeks ago': '%d weeks ago',
'%s %%{row} deleted': '%s linii șterse', '%s %%{row} deleted': '%s linii șterse',
'%s %%{row} updated': '%s linii actualizate', '%s %%{row} updated': '%s linii actualizate',
'%s selected': '%s selectat(e)', '%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")', '(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': 'O nouă versiune de web2py este disponibilă',
'A new version of web2py is available: %s': 'O nouă versiune de web2py este disponibilă: %s', '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': 'despre',
'About application': 'Despre aplicație', 'About application': 'Despre aplicație',
'Access Control': 'Control acces', 'Access Control': 'Control acces',
'Add': 'Adaugă', '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 insecure channel': 'Adminstrarea este dezactivată deoarece conexiunea nu este sigură',
'Admin is disabled because unsecure channel': 'Administrarea este dezactivată deoarece conexiunea nu este securizată', 'Admin is disabled because unsecure channel': 'Administrarea este dezactivată deoarece conexiunea nu este securizată',
'Administration': 'Administrare', 'Administration': 'Administrare',
@@ -32,62 +38,118 @@
'Administrator Password:': 'Parolă administrator:', 'Administrator Password:': 'Parolă administrator:',
'Ajax Recipes': 'Rețete Ajax', 'Ajax Recipes': 'Rețete Ajax',
'And': 'Și', '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 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 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"',
'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', 'Authentication': 'Autentificare',
'Available databases and tables': 'Baze de date și tabele disponibile', 'Available databases and tables': 'Baze de date și tabele disponibile',
'Back': 'Înapoi', 'Back': 'Înapoi',
'Buy this book': 'Cumpără această carte', 'Buy this book': 'Cumpără această carte',
'cache': 'cache',
'Cache Keys': 'Chei cache', 'Cache Keys': 'Chei cache',
'cache, errors and sessions cleaned': 'cache, erori și sesiuni golite',
'Cannot be empty': 'Nu poate fi vid', '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 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ă', 'Change password': 'Schimbare parolă',
'change password': 'schimbare parolă',
'check all': 'coșați tot',
'Check to delete': 'Coșați pentru a șterge', 'Check to delete': 'Coșați pentru a șterge',
'clean': 'golire',
'Clear': 'Golește', 'Clear': 'Golește',
'click to check for upgrades': 'Clic pentru a verifica dacă există upgrade-uri',
'Client IP': 'IP client', 'Client IP': 'IP client',
'Community': 'Comunitate', 'Community': 'Comunitate',
'compile': 'compilare',
'compiled application removed': 'aplicația compilată a fost ștearsă',
'Components and Plugins': 'Componente și plugin-uri', 'Components and Plugins': 'Componente și plugin-uri',
'contains': 'conține',
'Controller': 'Controlor', 'Controller': 'Controlor',
'Controllers': 'Controlori', 'Controllers': 'Controlori',
'controllers': 'controlori',
'Copyright': 'Drepturi de autor', 'Copyright': 'Drepturi de autor',
'create file with filename:': 'crează fișier cu numele:',
'Create new application': 'Creați aplicație nouă', 'Create new application': 'Creați aplicație nouă',
'create new application:': 'crează aplicație nouă:',
'crontab': 'crontab',
'Current request': 'Cerere curentă', 'Current request': 'Cerere curentă',
'Current response': 'Răspuns curent', 'Current response': 'Răspuns curent',
'Current session': 'Sesiune curentă', 'Current session': 'Sesiune curentă',
'DB Model': 'Model bază de date', 'currently saved or': 'în prezent salvat sau',
'DESIGN': 'DESIGN', 'customize me!': 'Personalizează-mă!',
'data uploaded': 'date încărcate',
'Database': 'Baza de date', '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', 'Date and Time': 'Data și ora',
'db': 'db',
'DB Model': 'Model bază de date',
'defines tables': 'definire tabele',
'Delete': 'Șterge', 'Delete': 'Șterge',
'delete': 'șterge',
'delete all checked': 'șterge tot ce e coșat',
'Delete:': 'Șterge:', 'Delete:': 'Șterge:',
'Demo': 'Demo', 'Demo': 'Demo',
'Deploy on Google App Engine': 'Instalare pe Google App Engine', 'Deploy on Google App Engine': 'Instalare pe Google App Engine',
'Deployment Recipes': 'Rețete de instalare', 'Deployment Recipes': 'Rețete de instalare',
'Description': 'Descriere', 'Description': 'Descriere',
'design': 'design',
'DESIGN': 'DESIGN',
'Design for': 'Design pentru', 'Design for': 'Design pentru',
'Disk Cache Keys': 'Chei cache de disc', 'Disk Cache Keys': 'Chei cache de disc',
'Documentation': 'Documentație', 'Documentation': 'Documentație',
"Don't know what to do?": 'Nu știți ce să faceți?', "Don't know what to do?": 'Nu știți ce să faceți?',
'done!': 'gata!',
'Download': 'Descărcare', 'Download': 'Descărcare',
'E-mail': 'E-mail', 'E-mail': 'E-mail',
'E-mail invalid': 'E-mail invalid', 'E-mail invalid': 'E-mail invalid',
'edit': 'editare',
'EDIT': 'EDITARE', 'EDIT': 'EDITARE',
'Edit': 'Editare', 'Edit': 'Editare',
'Edit Profile': 'Editare profil',
'Edit This App': 'Editați această aplicație',
'Edit application': 'Editare aplicație', 'Edit application': 'Editare aplicație',
'edit controller': 'editare controlor',
'Edit current record': 'Editare înregistrare curentă', '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': 'Editare fișier',
'Editing file "%s"': 'Editare fișier "%s"', 'Editing file "%s"': 'Editare fișier "%s"',
'Email and SMS': 'E-mail și SMS', '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"', 'Error logs for "%(app)s"': 'Log erori pentru "%(app)s"',
'errors': 'erori',
'Errors': 'Erori', 'Errors': 'Erori',
'Export': 'Export', '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', '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', 'First name': 'Prenume',
'Forbidden': 'Interzis', 'Forbidden': 'Interzis',
'Forms and Validators': 'Formulare și validatori', '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', 'Group uniquely assigned to user %(id)s': 'Grup asociat în mod unic utilizatorului %(id)s',
'Groups': 'Grupuri', 'Groups': 'Grupuri',
'Hello World': 'Salutare lume', 'Hello World': 'Salutare lume',
'help': 'ajutor',
'Home': 'Acasă', 'Home': 'Acasă',
'How did you get here?': 'Cum ați ajuns aici?', 'How did you get here?': 'Cum ați ajuns aici?',
'htmledit': 'editare html',
'Import/Export': 'Import/Export', 'Import/Export': 'Import/Export',
'includes': 'include',
'Index': 'Index', 'Index': 'Index',
'insert new': 'adaugă nou',
'insert new %s': 'adaugă nou %s',
'Installed applications': 'Aplicații instalate', 'Installed applications': 'Aplicații instalate',
'internal error': 'eroare internă',
'Internal State': 'Stare internă', 'Internal State': 'Stare internă',
'Introduction': 'Introducere', 'Introduction': 'Introducere',
'Invalid Query': 'Interogare invalidă',
'Invalid action': 'Acțiune invalidă', 'Invalid action': 'Acțiune invalidă',
'Invalid email': 'E-mail invalid', 'Invalid email': 'E-mail invalid',
'invalid password': 'parolă 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', 'Language files (static strings) updated': 'Fișierele de limbă (șirurile statice de caractere) actualizate',
'languages': 'limbi',
'Languages': 'Limbi', 'Languages': 'Limbi',
'languages updated': 'limbi actualizate',
'Last name': 'Nume', 'Last name': 'Nume',
'Last saved on:': 'Ultima salvare:', 'Last saved on:': 'Ultima salvare:',
'Layout': 'Șablon', 'Layout': 'Șablon',
@@ -118,39 +192,54 @@
'Layouts': 'Șabloane', 'Layouts': 'Șabloane',
'License for': 'Licență pentru', 'License for': 'Licență pentru',
'Live Chat': 'Chat live', 'Live Chat': 'Chat live',
'loading...': 'încarc...',
'Logged in': 'Logat', 'Logged in': 'Logat',
'Logged out': 'Delogat', 'Logged out': 'Delogat',
'Login': 'Autentificare', 'Login': 'Autentificare',
'login': 'autentificare',
'Login to the Administrative Interface': 'Logare interfață de administrare', 'Login to the Administrative Interface': 'Logare interfață de administrare',
'logout': 'ieșire',
'Logout': 'Ieșire', 'Logout': 'Ieșire',
'Lost Password': 'Parolă pierdută', 'Lost Password': 'Parolă pierdută',
'Lost password?': 'Parolă pierdută?', 'Lost password?': 'Parolă pierdută?',
'Main Menu': 'Meniu principal', 'Main Menu': 'Meniu principal',
'Menu Model': 'Model meniu', 'Menu Model': 'Model meniu',
'merge': 'unește',
'Models': 'Modele', 'Models': 'Modele',
'models': 'modele',
'Modules': 'Module', 'Modules': 'Module',
'modules': 'module',
'My Sites': 'Site-urile mele', 'My Sites': 'Site-urile mele',
'NO': 'NU',
'Name': 'Nume', 'Name': 'Nume',
'New': 'Nou', 'New': 'Nou',
'New Record': 'Înregistrare nouă', 'new application "%s" created': 'aplicația nouă "%s" a fost creată',
'New password': 'Parola nouă', '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', 'No databases in this application': 'Aplicație fără bază de date',
'Object or table name': 'Obiect sau nume de tabel', 'Object or table name': 'Obiect sau nume de tabel',
'Old password': 'Parola veche', 'Old password': 'Parola veche',
'Online examples': 'Exemple online', 'Online examples': 'Exemple online',
'Or': 'Sau', 'Or': 'Sau',
'or import from csv file': 'sau importă din fișier csv',
'or provide application url:': 'sau furnizează adresă url:',
'Origin': 'Origine', 'Origin': 'Origine',
'Original/Translation': 'Original/Traducere', 'Original/Translation': 'Original/Traducere',
'Other Plugins': 'Alte plugin-uri', 'Other Plugins': 'Alte plugin-uri',
'Other Recipes': 'Alte rețete', 'Other Recipes': 'Alte rețete',
'Overview': 'Prezentare de ansamblu', 'Overview': 'Prezentare de ansamblu',
'pack all': 'împachetează toate',
'pack compiled': 'pachet compilat',
'Password': 'Parola', 'Password': 'Parola',
"Password fields don't match": 'Câmpurile de parolă nu se potrivesc', "Password fields don't match": 'Câmpurile de parolă nu se potrivesc',
'Peeking at file': 'Vizualizare fișier', 'Peeking at file': 'Vizualizare fișier',
'please input your password again': 'introduceți parola din nou',
'Plugins': 'Plugin-uri', 'Plugins': 'Plugin-uri',
'Powered by': 'Pus în mișcare de', 'Powered by': 'Pus în mișcare de',
'Preface': 'Prefață', 'Preface': 'Prefață',
'previous 100 rows': '100 de linii anterioare',
'Profile': 'Profil', 'Profile': 'Profil',
'Python': 'Python', 'Python': 'Python',
'Query': 'Interogare', 'Query': 'Interogare',
@@ -158,52 +247,88 @@
'Quick Examples': 'Exemple rapide', 'Quick Examples': 'Exemple rapide',
'RAM Cache Keys': 'Chei cache RAM', 'RAM Cache Keys': 'Chei cache RAM',
'Recipes': 'Rețete', 'Recipes': 'Rețete',
'record': 'înregistrare',
'record does not exist': 'înregistrare inexistentă',
'record id': 'id înregistrare',
'Record ID': 'ID înregistrare', 'Record ID': 'ID înregistrare',
'register': 'înregistrare',
'Register': 'Înregistrare', 'Register': 'Înregistrare',
'Registration identifier': 'Identificator de autentificare', 'Registration identifier': 'Identificator de autentificare',
'Registration key': 'Cheie înregistrare', 'Registration key': 'Cheie înregistrare',
'Registration successful': 'Autentificare reușită', 'Registration successful': 'Autentificare reușită',
'Remember me (for 30 days)': 'Ține-mă minte (timp de 30 de zile)', 'Remember me (for 30 days)': 'Ține-mă minte (timp de 30 de zile)',
'remove compiled': 'șterge compilate',
'Request reset password': 'Cerere resetare parolă', 'Request reset password': 'Cerere resetare parolă',
'Reset Password key': 'Cheie restare parolă', 'Reset Password key': 'Cheie restare parolă',
'Resolve Conflict file': 'Fișier rezolvare conflict', 'Resolve Conflict file': 'Fișier rezolvare conflict',
'restore': 'restaurare',
'revert': 'revenire',
'Role': 'Rol', 'Role': 'Rol',
'Rows in table': 'Linii în tabel', 'Rows in table': 'Linii în tabel',
'Rows selected': 'Linii selectate', 'Rows selected': 'Linii selectate',
'save': 'salvare',
'Save profile': 'Salvează profil', 'Save profile': 'Salvează profil',
'Saved file hash:': 'Hash fișier salvat:', 'Saved file hash:': 'Hash fișier salvat:',
'Search': 'Căutare', 'Search': 'Căutare',
'Semantic': 'Semantică', 'Semantic': 'Semantică',
'Services': 'Servicii', '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', 'Static files': 'Fișiere statice',
'Stylesheet': 'Foaie de stiluri', 'Stylesheet': 'Foaie de stiluri',
'Submit': 'Înregistrează', 'Submit': 'Înregistrează',
'Support': 'Suport', 'Support': 'Suport',
'Sure you want to delete this object?': 'Sigur ștergeți acest obiect?', 'Sure you want to delete this object?': 'Sigur ștergeți acest obiect?',
'table': 'tabel',
'Table name': 'Nume tabel', 'Table name': 'Nume tabel',
'test': 'test',
'Testing application': 'Testare aplicație', '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 "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 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 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 controllers': 'Nu există controlori',
'There are no models': 'Nu există modele', 'There are no models': 'Nu există modele',
'There are no modules': 'Nu există module', 'There are no modules': 'Nu există module',
'There are no static files': 'Nu există fișiere statice', '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 translators, only default language is supported': 'Nu există traduceri, doar limba implicită este suportată',
'There are no views': 'Nu există vederi', '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 App': 'Această aplicație',
'This is a copy of the scaffolding application': 'Aceasta este o copie a aplicației schelet', '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', 'This is the %(filename)s template': 'Aceasta este șablonul fișierului %(filename)s',
'Ticket': 'Tichet', 'Ticket': 'Tichet',
'Timestamp': 'Moment în timp (timestamp)', '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', 'True': 'Adevărat',
'try': 'încearcă',
'try something like': 'încearcă ceva de genul',
'Twitter': 'Twitter', 'Twitter': 'Twitter',
'Unable to check for upgrades': 'Imposibil de verificat dacă există actualizări', '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': 'Imposibil de descărcat',
'Unable to download app': 'Imposibil de descărcat aplicația', '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:', 'Update:': 'Actualizare:',
'upload application:': 'incarcă aplicația:',
'Upload existing application': 'Încarcă aplicația existentă', '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.', '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-in': 'Utilizator %(id)s autentificat',
'User %(id)s Logged-out': 'Utilizator %(id)s delogat', '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 Profile updated': 'Profil utilizator %(id)s actualizat',
'User %(id)s Registered': 'Utilizator %(id)s înregistrat', 'User %(id)s Registered': 'Utilizator %(id)s înregistrat',
'User ID': 'ID utilizator', 'User ID': 'ID utilizator',
'value already in database or empty': 'Valoare existentă în baza de date sau vidă',
'Verify Password': 'Verifică parola', 'Verify Password': 'Verifică parola',
'versioning': 'versiuni',
'Videos': 'Video-uri', 'Videos': 'Video-uri',
'View': 'Vedere', 'View': 'Vedere',
'view': 'vedere',
'Views': 'Vederi', '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': 'Bine ați venit',
'Welcome %s': 'Bine ați venit %s', 'Welcome %s': 'Bine ați venit %s',
'Welcome to web2py': 'Bun venit la web2py', 'Welcome to web2py': 'Bun venit la web2py',
@@ -225,131 +356,4 @@
'You are successfully running web2py': 'Rulați cu succes web2py', '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 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', '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") logger = logging.getLogger("web2py.cache")
__all__ = ['Cache', 'lazy_lazy_cache'] __all__ = ['Cache', 'lazy_cache']
DEFAULT_TIME_EXPIRE = 300 DEFAULT_TIME_EXPIRE = 300
@@ -478,7 +478,14 @@ class Cache(object):
return CacheAction(func,key,time_expire,self,cache_model) return CacheAction(func,key,time_expire,self,cache_model)
return tmp 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): def decorator(f,key=key,time_expire=time_expire,cache_model=cache_model):
key = key or repr(f) key = key or repr(f)
def g(*c,**d): 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. 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() cfs_lock.acquire()
item = cfs.get(key, None) item = cfs.get(key, None)
cfs_lock.release() cfs_lock.release()
+3 -3
View File
@@ -855,9 +855,9 @@ class Sheet:
sorted_headers = [TH(),] + \ sorted_headers = [TH(),] + \
[TH(header[1]) for header in sorted(unsorted_headers)] [TH(header[1]) for header in sorted(unsorted_headers)]
table.insert(0, TR(*sorted_headers, _class="%s_fieldnames" % \ table.insert(0, TR(*sorted_headers,
attributes["_class"])) **{_class:"%s_fieldnames" % \
attributes["_class"]}))
else: else:
data = SCRIPT(""" // web2py Spreadsheets: no db data.""") 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 FOREIGN_KEY_CHECKS=1;")
self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';") self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';")
def execute(self,a):
return self.log_execute(a.decode('utf8'))
class NoSQLAdapter(BaseAdapter): class NoSQLAdapter(BaseAdapter):
can_select_for_update = False can_select_for_update = False
@@ -7109,6 +7112,9 @@ class Table(dict):
"primarykey must be a list of fields from table '%s'" \ "primarykey must be a list of fields from table '%s'" \
% tablename % tablename
self._primarykey = primarykey 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']: elif not [f for f in fields if isinstance(f,Field) and f.type=='id']:
field = Field('id', 'id') field = Field('id', 'id')
newfields.append(field) newfields.append(field)
+33 -2
View File
@@ -472,7 +472,18 @@ class XmlComponent(object):
def xml(self): def xml(self):
raise NotImplementedError 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): class XML(XmlComponent):
""" """
@@ -2023,8 +2034,28 @@ class FORM(DIV):
self.validate(**kwargs) self.validate(**kwargs)
return self return self
REDIRECT_JS = "window.location='%s';return false"
def add_button(self,value,url,_class=None): 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): class BEAUTIFY(DIV):
+3
View File
@@ -87,6 +87,7 @@ from settings import global_settings
from validators import CRYPT from validators import CRYPT
from cache import Cache from cache import Cache
from html import URL as Url from html import URL as Url
from utils import is_valid_ip_address
import newcron import newcron
import rewrite import rewrite
@@ -402,6 +403,8 @@ def wsgibase(environ, responder):
try: local_hosts.append(socket.gethostbyname(http_host)) try: local_hosts.append(socket.gethostbyname(http_host))
except socket.gaierror: pass except socket.gaierror: pass
request.client = get_client(request.env) request.client = get_client(request.env)
if not is_valid_ip_address(request.client):
raise HTTP(400,"Bad Request")
request.folder = abspath('applications', request.folder = abspath('applications',
request.application) + os.sep request.application) + os.sep
x_req_with = str(request.env.http_x_requested_with).lower() 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)))) LABEL(v,_for='%s%s' % (field.name,k))))
opts.append(child(tds)) opts.append(child(tds))
if opts: 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) return parent(*opts, **attr)
@@ -1173,8 +1176,7 @@ class SQLFORM(FORM):
row_id = '%s_%s%s' % (self.table, fieldname, SQLFORM.ID_ROW_SUFFIX) row_id = '%s_%s%s' % (self.table, fieldname, SQLFORM.ID_ROW_SUFFIX)
widget = field.widget(field, value) widget = field.widget(field, value)
self.field_parent[row_id].components = [ widget ] 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.custom.widget[ fieldname ] = widget
self.accepted = ret self.accepted = ret
return ret return ret
@@ -1241,7 +1243,8 @@ class SQLFORM(FORM):
### do not know why this happens, it should not ### do not know why this happens, it should not
(source_file, original_filename) = \ (source_file, original_filename) = \
(cStringIO.StringIO(f), 'file.txt') (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 # this line is for backward compatibility only
self.vars['%s_newfilename' % fieldname] = newfilename self.vars['%s_newfilename' % fieldname] = newfilename
fields[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) return URL(args=current.request.args,vars=current.request.vars)
def __init__(self, environment=None, db=None, mailer=True, 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) auth=Auth(db)
@@ -1133,7 +1134,10 @@ class Auth(object):
# when user wants to be logged in for longer # when user wants to be logged in for longer
response.cookies[response.session_id_name]["expires"] = \ response.cookies[response.session_id_name]["expires"] = \
auth.expiration auth.expiration
if signature:
self.define_signature()
else:
self.signature = None
def _get_user_id(self): def _get_user_id(self):
"accessor for auth.user_id" "accessor for auth.user_id"
@@ -1332,7 +1336,8 @@ class Auth(object):
db = self.db db = self.db
settings = self.settings settings = self.settings
self.define_signature() if not self.signature:
self.define_signature()
if signature==True: if signature==True:
signature_list = [self.signature] signature_list = [self.signature]
elif not signature: elif not signature:
@@ -1622,7 +1627,7 @@ class Auth(object):
userfield = 'email' userfield = 'email'
passfield = self.settings.password_field passfield = self.settings.password_field
user = self.db(table_user[userfield] == username).select().first() 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] password = table_user[passfield].validate(password)[0]
if not user.registration_key and password == user[passfield]: if not user.registration_key and password == user[passfield]:
user = Storage(table_user._filter_fields(user, id=True)) user = Storage(table_user._filter_fields(user, id=True))
@@ -3392,6 +3397,8 @@ class Crud(object):
query = table.id > 0 query = table.id > 0
if not fields: if not fields:
fields = [field for field in table if field.readable] 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, rows = self.db(query).select(*fields,**dict(orderby=orderby,
limitby=limitby)) limitby=limitby))
return rows return rows
+21 -1
View File
@@ -16,6 +16,7 @@ import random
import time import time
import os import os
import logging import logging
import socket
from contrib.pbkdf2 import pbkdf2_hex from contrib.pbkdf2 import pbkdf2_hex
logger = logging.getLogger("web2py") logger = logging.getLogger("web2py")
@@ -69,7 +70,7 @@ def get_digest(value):
elif value == "sha512": elif value == "sha512":
return hashlib.sha512 return hashlib.sha512
else: else:
raise ValueError("Invalid digest algorithm: %s" % value) raise ValueError("Invalid digest algorithm: %s" % value)
DIGEST_ALG_BY_SIZE = { DIGEST_ALG_BY_SIZE = {
128/4: 'md5', 128/4: 'md5',
@@ -146,6 +147,25 @@ def web2py_uuid():
bytes = ''.join(chr(c ^ ctokens[i]) for i,c in enumerate(bytes)) bytes = ''.join(chr(c ^ ctokens[i]) for i,c in enumerate(bytes))
return str(uuid.UUID(bytes=bytes, version=4)) 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, justify=Tkinter.LEFT).grid(row=0,
column=0, column=0,
sticky=sticky) sticky=sticky)
self.ip = Tkinter.Entry(self.root) self.ips = {}
self.ip.insert(Tkinter.END, self.options.ip) self.selected_ip = Tkinter.StringVar()
self.ip.grid(row=0, column=1, sticky=sticky) 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 # Port
Tkinter.Label(self.root, Tkinter.Label(self.root,
text='Server Port:', text='Server Port:',
justify=Tkinter.LEFT).grid(row=1, justify=Tkinter.LEFT).grid(row=shift,
column=0, column=0,
sticky=sticky) sticky=sticky)
self.port_number = Tkinter.Entry(self.root) self.port_number = Tkinter.Entry(self.root)
self.port_number.insert(Tkinter.END, self.options.port) 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 # Password
Tkinter.Label(self.root, Tkinter.Label(self.root,
text='Choose Password:', text='Choose Password:',
justify=Tkinter.LEFT).grid(row=2, justify=Tkinter.LEFT).grid(row=shift+1,
column=0, column=0,
sticky=sticky) sticky=sticky)
self.password = Tkinter.Entry(self.root, show='*') self.password = Tkinter.Entry(self.root, show='*')
self.password.bind('<Return>', lambda e: self.start()) self.password.bind('<Return>', lambda e: self.start())
self.password.focus_force() 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 # Prepare the canvas
self.canvas = Tkinter.Canvas(self.root, self.canvas = Tkinter.Canvas(self.root,
width=300, width=300,
height=100, height=100,
bg='black') 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) self.canvas.after(1000, self.update_canvas)
# Prepare the frame # Prepare the frame
frame = Tkinter.Frame(self.root) frame = Tkinter.Frame(self.root)
frame.grid(row=4, column=0, columnspan=2) frame.grid(row=shift+3, column=0, columnspan=2)
# Start button # Start button
self.button_start = Tkinter.Button(frame, self.button_start = Tkinter.Button(frame,
@@ -359,7 +369,7 @@ class web2pyDialog(object):
if not password: if not password:
self.error('no password, no web admin interface') 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}' regexp = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
if ip and not re.compile(regexp).match(ip): 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)) thread.start_new_thread(start_browser, (proto, ip, port))
self.password.configure(state='readonly') 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') self.port_number.configure(state='readonly')
if self.tb: if self.tb:
@@ -435,7 +445,7 @@ class web2pyDialog(object):
self.button_start.configure(state='normal') self.button_start.configure(state='normal')
self.button_stop.configure(state='disabled') self.button_stop.configure(state='disabled')
self.password.configure(state='normal') 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.port_number.configure(state='normal')
self.server.stop() self.server.stop()
@@ -790,6 +800,13 @@ def console():
global_settings.cmd_options = options global_settings.cmd_options = options
global_settings.cmd_args = args 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: if options.run_system_tests:
run_system_tests() run_system_tests()
+9 -4
View File
@@ -90,8 +90,12 @@ class Web2pyService(Service):
cls = _winreg.QueryValue(h, 'PythonClass') cls = _winreg.QueryValue(h, 'PythonClass')
finally: finally:
_winreg.CloseKey(h) _winreg.CloseKey(h)
dir = os.path.dirname(cls) dir = os.path.dirname(cls)
os.chdir(dir) 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 return True
except: except:
self.log("Can't change to web2py working path; server is stopped") 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'): def web2py_windows_service_handler(argv=None, opt_file='options'):
path = os.path.dirname(__file__) path = os.path.dirname(__file__)
classstring = os.path.normpath(os.path.join(up(path), web2py_path = up(path)
'gluon.winservice.Web2pyService')) os.chdir(web2py_path)
classstring = os.path.normpath(
os.path.join(web2py_path,'gluon.winservice.Web2pyService'))
if opt_file: if opt_file:
Web2pyService._exe_args_ = opt_file Web2pyService._exe_args_ = opt_file
win32serviceutil.HandleCommandLine(Web2pyService, win32serviceutil.HandleCommandLine(Web2pyService,
@@ -158,7 +164,6 @@ def web2py_windows_service_handler(argv=None, opt_file='options'):
win32serviceutil.HandleCommandLine(Web2pyService, win32serviceutil.HandleCommandLine(Web2pyService,
serviceClassString=classstring, argv=argv) serviceClassString=classstring, argv=argv)
if __name__ == '__main__': if __name__ == '__main__':
web2py_windows_service_handler() web2py_windows_service_handler()