diff --git a/applications/admin/views/default/design.html b/applications/admin/views/default/design.html
index 3664edbf..e5b92ab8 100644
--- a/applications/admin/views/default/design.html
+++ b/applications/admin/views/default/design.html
@@ -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"))}}
@@ -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')}}
+
+
+
+ {{=T("Private files")}}
+ {{=helpicon()}} {{=T("These files are not served, they are only available from within your app")}}
+
+
+
+
+ {{if not privates:}}
{{=T("There are no private files")}}
{{pass}}
+
+ {{
+ 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)
+ }}
+
+ {{=button(URL('gae','deploy'), T('Deploy on Google App Engine'))}}
+ {{=button(URL('openshift','deploy'),T('Deploy to OpenShift'))}}
+
{{if TWITTER_HASH:}}
diff --git a/applications/welcome/languages/it.py b/applications/welcome/languages/it.py
index 8039c9db..afa3109a 100644
--- a/applications/welcome/languages/it.py
+++ b/applications/welcome/languages/it.py
@@ -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',
}
diff --git a/applications/welcome/languages/ro.py b/applications/welcome/languages/ro.py
index b48c1a5c..e2f0a31a 100644
--- a/applications/welcome/languages/ro.py
+++ b/applications/welcome/languages/ro.py
@@ -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',
}
diff --git a/gluon/cache.py b/gluon/cache.py
index 14006087..437e140e 100644
--- a/gluon/cache.py
+++ b/gluon/cache.py
@@ -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):
diff --git a/gluon/cfs.py b/gluon/cfs.py
index 8dd69cc2..a99f9c06 100644
--- a/gluon/cfs.py
+++ b/gluon/cfs.py
@@ -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()
diff --git a/gluon/contrib/spreadsheet.py b/gluon/contrib/spreadsheet.py
index 09a00e34..e91dc733 100644
--- a/gluon/contrib/spreadsheet.py
+++ b/gluon/contrib/spreadsheet.py
@@ -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.""")
diff --git a/gluon/dal.py b/gluon/dal.py
index 42d357e1..50d52717 100644
--- a/gluon/dal.py
+++ b/gluon/dal.py
@@ -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)
diff --git a/gluon/html.py b/gluon/html.py
index aed30ae2..3776f67b 100644
--- a/gluon/html.py
+++ b/gluon/html.py
@@ -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):
diff --git a/gluon/main.py b/gluon/main.py
index cff04b3c..a3779359 100644
--- a/gluon/main.py
+++ b/gluon/main.py
@@ -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()
diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py
index 0c7cae23..f77e35e5 100644
--- a/gluon/sqlhtml.py
+++ b/gluon/sqlhtml.py
@@ -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
diff --git a/gluon/tools.py b/gluon/tools.py
index 24467443..28e7129b 100644
--- a/gluon/tools.py
+++ b/gluon/tools.py
@@ -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
diff --git a/gluon/utils.py b/gluon/utils.py
index d429b100..911182fb 100644
--- a/gluon/utils.py
+++ b/gluon/utils.py
@@ -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
diff --git a/gluon/widget.py b/gluon/widget.py
index 96ef1d6e..916f3495 100644
--- a/gluon/widget.py
+++ b/gluon/widget.py
@@ -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('', 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()
diff --git a/gluon/winservice.py b/gluon/winservice.py
index bbda61ca..78c28d1a 100644
--- a/gluon/winservice.py
+++ b/gluon/winservice.py
@@ -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()