Compare commits

..
27 Commits
Author SHA1 Message Date
Massimo b9d80fcdc7 R-2.4.5 2013-03-18 17:37:49 -05:00
Massimo 3f7d085f73 fixed timezone and textarea default width 2013-03-18 17:34:06 -05:00
Massimo d57dd72780 upgraded fpdf, better tests, passes travis.ci tests, thanks Niphlod 2013-03-18 16:59:57 -05:00
Massimo a8d1d5cfcf upgraded fpdf, better tests, passes travis.ci tests, thanks Niphlod 2013-03-18 16:52:47 -05:00
mdipierro 8857e3d521 IS_DATE/DATETIME/etc. (timezone=user_timezone in hrs) 2013-03-17 18:53:19 -05:00
mdipierro 3b1a5be1be fixed (possibly) truncate for GAE 2013-03-17 15:27:47 -05:00
mdipierro c9a63a8524 fixed issue 1393, cast_keys in dict for python 2.5 support, thanks Alan and Niphlod 2013-03-17 12:11:10 -05:00
mdipierro cd967d2551 new travis.ci 2013-03-16 20:53:24 -05:00
mdipierro 92b5247f9f typo Fir(e)bird, thanks Jonathan 2013-03-15 21:27:52 -05:00
mdipierro 45a5b436c8 patched fpdf for 2.5, thanks Niphlod 2013-03-15 15:28:50 -05:00
mdipierro bfd385f969 travis badge in README, thanks niphlod 2013-03-15 15:12:01 -05:00
mdipierro f693fe6b2a added .travis.yml 2013-03-15 14:55:36 -05:00
mdipierro b040159a9b fixed issue 1385, thanks Alan 2013-03-15 14:14:40 -05:00
mdipierro f3af2a1999 fixed issue 1382, sanitizer accepts mailto, thanks lightdot 2013-03-15 10:17:33 -05:00
mdipierro f613a4cc99 wiki.settings, thanks Alan 2013-03-15 10:14:33 -05:00
mdipierro e4a96125a6 fixed issue 1390, dateTtime separator in MSSQL, thanks score2000 2013-03-15 10:05:52 -05:00
mdipierro e48074ff54 fixed issue 1391, sub for decimal 2013-03-15 10:00:45 -05:00
mdipierro 2ed122a534 fixed typo, thank you Philipp Storz 2013-03-15 09:49:46 -05:00
mdipierro 65c0d9b18b italian patch 2013-03-15 09:46:55 -05:00
mdipierro 6702694590 fixed slidetoggle boxes in new admin, thanks Annet and Niphlod 2013-03-15 09:44:22 -05:00
mdipierro 6f0d4d039e fixed upload default values, thanks Marin Pranjic 2013-03-15 09:38:47 -05:00
mdipierro 1325b0e48f Merge pull request #65 from michele-comitini/master
HTTP exceptions in jsonrpc2
2013-03-15 07:25:55 -07:00
Michele Comitini 7421eb8068 avoid treating HTTP redirections or status codes generated in jsonrpc2 functions as error exceptions 2013-03-14 21:54:38 +01:00
Michele Comitini 6c7a9a4030 avoid treating HTTP redirections or status codes generated in jsonrpc2 functions as error exceptions 2013-03-14 21:49:05 +01:00
Massimo ba0a143717 scheduler patch allows termination of tasks, thanks niphlod 2013-03-12 10:40:57 -05:00
mdipierro 28bcb5ed6c added sessions in cookie for examples 2013-03-11 18:12:01 -05:00
mdipierro 44fd637a1f R-2.4.4 2013-03-11 15:26:36 -05:00
24 changed files with 1081 additions and 839 deletions
+25
View File
@@ -0,0 +1,25 @@
language: python
python:
- '2.5'
- '2.6'
- '2.7'
env:
- DB=sqlite:memory
- DB=mysql://root:@localhost/test_w2p
- DB=postgres://postgres:@localhost/test_w2p
before_script:
- pip install unittest2
- if [[ $DB == postgres* ]]; then pip install --use-mirrors psycopg2; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install --use-mirrors pysqlite; fi
- if [[ $DB == mysql* ]]; then mysql -e 'create database test_w2p;'; fi
- if [[ $DB == postgres* ]]; then psql -c 'create database test_w2p;' -U postgres; fi
#Temporal solution to travis issue #155
- sudo chmod 777 /dev/shm
- sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm
script: PYTHONPATH=. unit2 -v gluon.tests
notifications:
email: false
irc:
channels: "irc.freenode.org#web2py"
+5
View File
@@ -1,3 +1,8 @@
## 2.4.5
- travis.ci integration (thanks Marc Abramowitz and Niphlod). Passes all tests (thanks Niplod).
- IS_DATE and IS_DATETIME can specify timezone
## 2.4.1- 2.4.3 ## 2.4.1- 2.4.3
- 2D GEO API: geoPoint, getLine, geoPolygon - 2D GEO API: geoPoint, getLine, geoPolygon
+1 -1
View File
@@ -30,7 +30,7 @@ update:
echo "remember that pymysql was tweaked" echo "remember that pymysql was tweaked"
src: src:
### Use semantic versioning ### Use semantic versioning
echo 'Version 2.4.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION echo 'Version 2.4.5-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files ### rm -f all junk files
make clean make clean
### clean up baisc apps ### clean up baisc apps
+5
View File
@@ -6,6 +6,11 @@ It is written and programmable in Python. LGPLv3 License
Learn more at http://web2py.com Learn more at http://web2py.com
## Tests
[![Build Status](https://travis-ci.org/web2py/web2py.png)](https://travis-ci.org/web2py/web2py)
## Installation Instructions ## Installation Instructions
To start web2py there is NO NEED to install it. Just unzip and do: To start web2py there is NO NEED to install it. Just unzip and do:
+1 -1
View File
@@ -1 +1 @@
Version 2.4.3-stable+timestamp.2013.03.11.14.40.17 Version 2.4.5-stable+timestamp.2013.03.18.17.37.19
@@ -102,6 +102,7 @@ td.w2p_fl,td.w2p_fc {padding:0;}
==============================================================*/ ==============================================================*/
/* because web2py handles this via js */ /* because web2py handles this via js */
textarea { width:90%}
.hidden{visibility:visible;} .hidden{visibility:visible;}
/* right folder for bootstrap black images/icons */ /* right folder for bootstrap black images/icons */
[class^="icon-"],[class*=" icon-"]{ [class^="icon-"],[class*=" icon-"]{
+6 -6
View File
@@ -368,8 +368,8 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
</ul> </ul>
{{pass}} {{pass}}
<div class="controls formfield"> <div class="controls formfield">
<button onclick="jQuery('#form5').slideToggle()" class="btn btn-mini">Create/Upload</button> <button onclick="jQuery('#form6').slideToggle()" class="btn btn-mini">Create/Upload</button>
<div id="form5" class="row-fluid" style="display:none"> <div id="form6" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/modules/' % app, 'modules')}}<em>{{=T('or alternatively')}}</em></div> <div class="span3">{{=file_create_form('%s/modules/' % app, 'modules')}}<em>{{=T('or alternatively')}}</em></div>
<div class="span3">{{=file_upload_form('%s/modules/' % app, 'modules')}}</div> <div class="span3">{{=file_upload_form('%s/modules/' % app, 'modules')}}</div>
</div> </div>
@@ -425,8 +425,8 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
{{pass}} {{pass}}
</ul> </ul>
<div class="controls formfield"> <div class="controls formfield">
<button onclick="jQuery('#form6').slideToggle()" class="btn btn-mini">Create/Upload</button> <button onclick="jQuery('#form7').slideToggle()" class="btn btn-mini">Create/Upload</button>
<div id="form6" class="row-fluid" style="display:none"> <div id="form7" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/private/' % app, 'private')}}<em>{{=T('or alternatively')}}</em></div> <div class="span3">{{=file_create_form('%s/private/' % app, 'private')}}<em>{{=T('or alternatively')}}</em></div>
<div class="span3">{{=file_upload_form('%s/private/' % app, 'private')}}</div> <div class="span3">{{=file_upload_form('%s/private/' % app, 'private')}}</div>
</div> </div>
@@ -458,8 +458,8 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
<p><strong>{{=T('There are no plugins')}}</strong></p> <p><strong>{{=T('There are no plugins')}}</strong></p>
{{pass}} {{pass}}
<div class="controls formfield"> <div class="controls formfield">
<button onclick="jQuery('#form7').slideToggle()" class="btn btn-mini">Upload</button> <button onclick="jQuery('#form8').slideToggle()" class="btn btn-mini">Upload</button>
<div id="form7" class="row-fluid" style="display:none"> <div id="form8" class="row-fluid" style="display:none">
<div class="row-fluid"> <div class="row-fluid">
<div class="span3">{{=upload_plugin_form(app, 'plugins')}}</div> <div class="span3">{{=upload_plugin_form(app, 'plugins')}}</div>
</div> </div>
@@ -36,7 +36,7 @@ Tweet attributes
<td><img src="{{=t['profile_image_url_https']}}"/></td> <td><img src="{{=t['profile_image_url_https']}}"/></td>
<td> <td>
<a href="http://twitter.com/{{=t['from_user']}}">{{=t["from_user_name"]}}</a>: <a href="http://twitter.com/{{=t['from_user']}}">{{=t["from_user_name"]}}</a>:
{{=XML(hashtag.sub('<a href="http://twitter.com/\\g<2>">\\g<1>\\g<2></a>',link.sub('<a href="\\g<0>">\\g<0></a>',t["text"])),sanitize=True)}} {{=XML(hashtag.sub('<a href="http://twitter.com/\\g<2>">\\g<1>\\g<2></a>',link.sub('<a href="\\g<0>">\\g<0></a>',t["text"].encode('utf8'))),sanitize=True)}}
</td> </td>
</tr> </tr>
{{ pass }} {{ pass }}
+1
View File
@@ -0,0 +1 @@
session.connect(request,response,cookie_key='yoursecret')
@@ -102,6 +102,7 @@ td.w2p_fl,td.w2p_fc {padding:0;}
==============================================================*/ ==============================================================*/
/* because web2py handles this via js */ /* because web2py handles this via js */
textarea { width:90%}
.hidden{visibility:visible;} .hidden{visibility:visible;}
/* right folder for bootstrap black images/icons */ /* right folder for bootstrap black images/icons */
[class^="icon-"],[class*=" icon-"]{ [class^="icon-"],[class*=" icon-"]{
@@ -43,7 +43,7 @@ random.shuffle(quotes)
<a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23"><img src="{{=URL('static','images/infoworld2012.jpeg')}}" width="200px"/></a><br/> <a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23"><img src="{{=URL('static','images/infoworld2012.jpeg')}}" width="200px"/></a><br/>
<a class="btn btn-danger" href="{{=URL('download')}}" style="margin-top:10px; width:180px; color:white">Download Now</a><br/> <a class="btn btn-danger" href="{{=URL('download')}}" style="margin-top:10px; width:180px; color:white">Download Now</a><br/>
<a class="btn btn-danger" href="http://web2py.com/demo_admin" style="margin-top:10px; width:180px; color:white">Online Demo</a><br/> <a class="btn btn-danger" href="http://web2py.com/demo_admin" style="margin-top:10px; width:180px; color:white">Online Demo</a><br/>
<a class="btn btn-danger" href="http://web2py.com/poweredby" style="margin-top:10px; width:180px; color:white">Sites Powereb by web2py</a> <a class="btn btn-danger" href="http://web2py.com/poweredby" style="margin-top:10px; width:180px; color:white">Sites Powered by web2py</a>
</div> </div>
</div> </div>
</div> </div>
+72 -66
View File
@@ -4,8 +4,8 @@
'!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 ',
'%(nrows)s records found': '%(nrows)s records found', '%(nrows)s records found': '%(nrows)s record trovati',
'%d seconds ago': '%d seconds ago', '%d seconds ago': '%d secondi fa',
'%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',
@@ -16,40 +16,41 @@
'=': '=', '=': '=',
'>': '>', '>': '>',
'>=': '>=', '>=': '>=',
'@markmin\x01Number of entries: **%s**': 'Number of entries: **%s**', '@markmin\x01Number of entries: **%s**': 'Numero di entità: **%s**',
'About': 'About', 'About': 'About',
'Access Control': 'Access Control', 'Access Control': 'Controllo Accessi',
'Add': 'Add', 'Add': 'Aggiungi',
'Administrative Interface': 'Administrative Interface', 'Administrative Interface': 'Interfaccia Amministrativa',
'Administrative interface': 'Interfaccia amministrativa', 'Administrative interface': 'Interfaccia amministrativa',
'Ajax Recipes': 'Ajax Recipes', 'Ajax Recipes': 'Ajax Recipes',
'And': 'And', 'An error occured, please %s the page': "E' stato rilevato un errore, prego %s la pagina",
'And': 'E',
'appadmin is disabled because insecure channel': 'Amministrazione (appadmin) disabilitata: comunicazione non sicura', 'appadmin is disabled because insecure channel': 'Amministrazione (appadmin) disabilitata: comunicazione non sicura',
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?', 'Are you sure you want to delete this object?': 'Sicuro di voler cancellare questo oggetto ?',
'Available Databases and Tables': 'Database e tabelle disponibili', 'Available Databases and Tables': 'Database e tabelle disponibili',
'Back': 'Back', 'Back': 'Indietro',
'Buy this book': 'Buy this book', 'Buy this book': 'Compra questo libro',
'cache': 'cache', 'cache': 'cache',
'Cache': 'Cache', 'Cache': 'Cache',
'Cache Keys': 'Cache Keys', 'Cache Keys': 'Cache Keys',
'Cannot be empty': 'Non può essere vuoto', 'Cannot be empty': 'Non può essere vuoto',
'Change password': 'Change password', 'Change password': 'Cambia Password',
'change password': 'Cambia password', 'change password': 'Cambia password',
'Check to delete': 'Seleziona per cancellare', 'Check to delete': 'Seleziona per cancellare',
'Clear': 'Clear', 'Clear': 'Resetta',
'Clear CACHE?': 'Clear CACHE?', 'Clear CACHE?': 'Resetta CACHE?',
'Clear DISK': 'Clear DISK', 'Clear DISK': 'Resetta DISK',
'Clear RAM': 'Clear RAM', 'Clear RAM': 'Resetta RAM',
'Client IP': 'Client IP', 'Client IP': 'Client IP',
'Close': 'Close', 'Close': 'Chiudi',
'Cognome': 'Cognome', 'Cognome': 'Cognome',
'Community': 'Community', 'Community': 'Community',
'Components and Plugins': 'Components and Plugins', 'Components and Plugins': 'Componenti and Plugin',
'contains': 'contains', 'contains': 'contiene',
'Controller': 'Controller', 'Controller': 'Controller',
'Copyright': 'Copyright', 'Copyright': 'Copyright',
'Created By': 'Created By', 'Created By': 'Creato Da',
'Created On': 'Created On', 'Created On': 'Creato Il',
'CSV': 'CSV', 'CSV': 'CSV',
'CSV (hidden cols)': 'CSV (hidden cols)', 'CSV (hidden cols)': 'CSV (hidden cols)',
'Current request': 'Richiesta (request) corrente', 'Current request': 'Richiesta (request) corrente',
@@ -61,7 +62,7 @@
'Database %s select': 'Database %s select', 'Database %s select': 'Database %s select',
'db': 'db', 'db': 'db',
'DB Model': 'Modello di DB', 'DB Model': 'Modello di DB',
'Delete': 'Delete', 'Delete': 'Cancella',
'Delete:': 'Cancella:', 'Delete:': 'Cancella:',
'Demo': 'Demo', 'Demo': 'Demo',
'Deployment Recipes': 'Deployment Recipes', 'Deployment Recipes': 'Deployment Recipes',
@@ -71,7 +72,7 @@
'Disk Cache Keys': 'Disk Cache Keys', 'Disk Cache Keys': 'Disk Cache Keys',
'Disk Cleared': 'Disk Cleared', 'Disk Cleared': 'Disk Cleared',
'Documentation': 'Documentazione', 'Documentation': 'Documentazione',
"Don't know what to do?": "Don't know what to do?", "Don't know what to do?": 'Non sai cosa fare?',
'done!': 'fatto!', 'done!': 'fatto!',
'Download': 'Download', 'Download': 'Download',
'E-mail': 'E-mail', 'E-mail': 'E-mail',
@@ -79,18 +80,19 @@
'Edit current record': 'Modifica record corrente', 'Edit current record': 'Modifica record corrente',
'edit profile': 'modifica profilo', 'edit profile': 'modifica profilo',
'Edit This App': 'Modifica questa applicazione', 'Edit This App': 'Modifica questa applicazione',
'Email and SMS': 'Email and SMS', 'Email and SMS': 'Email e SMS',
'Email non valida': 'Email non valida', 'Email non valida': 'Email non valida',
'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g', 'enter an integer between %(min)g and %(max)g': 'inserisci un intero tra %(min)g e %(max)g',
'Errors': 'Errors', 'Errors': 'Errori',
'Errors in form, please check it out.': 'Errors in form, please check it out.', 'Errors in form, please check it out.': 'Errori nel form, ricontrollalo',
'export as csv file': 'esporta come file CSV', 'export as csv file': 'esporta come file CSV',
'Export:': 'Export:', 'Export:': 'Esporta:',
'FAQ': 'FAQ', 'FAQ': 'FAQ',
'First name': 'Nome', 'First name': 'Nome',
'Forgot username?': 'Forgot username?', 'Forgot username?': 'Dimenticato lo username?',
'Forms and Validators': 'Forms and Validators', 'Forms and Validators': 'Forms and Validators',
'Free Applications': 'Free Applications', 'Free Applications': 'Free Applications',
'Graph Model': 'Graph Model',
'Group %(group_id)s created': 'Group %(group_id)s created', 'Group %(group_id)s created': 'Group %(group_id)s created',
'Group ID': 'ID Gruppo', 'Group ID': 'ID Gruppo',
'Group uniquely assigned to user %(id)s': 'Group uniquely assigned to user %(id)s', 'Group uniquely assigned to user %(id)s': 'Group uniquely assigned to user %(id)s',
@@ -100,69 +102,70 @@
'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!',
'Home': 'Home', 'Home': 'Home',
'How did you get here?': 'How did you get here?', 'How did you get here?': 'Come sei arrivato qui?',
'HTML': 'HTML', 'HTML': 'HTML',
'import': 'import', 'import': 'importa',
'Import/Export': 'Importa/Esporta', 'Import/Export': 'Importa/Esporta',
'Index': 'Indice', 'Index': 'Indice',
'insert new': 'inserisci nuovo', 'insert new': 'inserisci nuovo',
'insert new %s': 'inserisci nuovo %s', 'insert new %s': 'inserisci nuovo %s',
'Internal State': 'Stato interno', 'Internal State': 'Stato interno',
'Introduction': 'Introduction', 'Introduction': 'Introduzione',
'Invalid email': 'Email non valida', 'Invalid email': 'Email non valida',
'Invalid login': 'Invalid login', 'Invalid login': 'Login non valido',
'Invalid Query': 'Richiesta (query) non valida', 'Invalid Query': 'Richiesta (query) non valida',
'invalid request': 'richiesta non valida', 'invalid request': 'richiesta non valida',
'Is Active': 'Is Active', 'Is Active': "E' attivo",
'Key': 'Key', 'Key': 'Chiave',
'Last name': 'Cognome', 'Last name': 'Cognome',
'Layout': 'Layout', 'Layout': 'Layout',
'Layout Plugins': 'Layout Plugins', 'Layout Plugins': 'Layout Plugins',
'Layouts': 'Layouts', 'Layouts': 'Layouts',
'Live Chat': 'Live Chat', 'Live Chat': 'Live Chat',
'Logged in': 'Logged in', 'Logged in': 'Loggato',
'Logged out': 'Logged out', 'Logged out': 'Disconnesso',
'login': 'accesso', 'login': 'accesso',
'Login': 'Login', 'Login': 'Login',
'logout': 'uscita', 'logout': 'uscita',
'Logout': 'Logout', 'Logout': 'Logout',
'Lost Password': 'Lost Password', 'Lost Password': 'Password Smarrita',
'Lost password?': 'Lost password?', 'Lost password?': 'Password smarrita?',
'lost password?': 'dimenticato la password?', 'lost password?': 'dimenticato la password?',
'Main Menu': 'Menu principale', 'Main Menu': 'Menu principale',
'Manage Cache': 'Manage Cache', 'Manage Cache': 'Manage Cache',
'Menu Model': 'Menu Modelli', 'Menu Model': 'Menu Modelli',
'Modified By': 'Modified By', 'Modified By': 'Modificato da',
'Modified On': 'Modified On', 'Modified On': 'Modificato il',
'My Sites': 'My Sites', 'My Sites': 'My Sites',
'Name': 'Nome', 'Name': 'Nome',
'New': 'New', 'New': 'Nuovo',
'New password': 'New password', 'New password': 'Nuova password',
'New Record': 'Nuovo elemento (record)', 'New Record': 'Nuovo elemento (record)',
'new record inserted': 'nuovo record inserito', 'new record inserted': 'nuovo record inserito',
'next 100 rows': 'prossime 100 righe', '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',
'No records found': 'No records found', 'No records found': 'Nessun record trovato',
'Nome': 'Nome', 'Nome': 'Nome',
'Non può essere vuoto': 'Non può essere vuoto', 'Non può essere vuoto': 'Non può essere vuoto',
'not authorized': 'non autorizzato', 'not authorized': 'non autorizzato',
'Object or table name': 'Object or table name', 'Object or table name': 'Oggeto o nome tabella',
'Old password': 'Old password', 'Old password': 'Vecchia password',
'Online examples': 'Vedere gli esempi', 'Online examples': 'Vedere gli esempi',
'Or': 'Or', 'Or': 'O',
'or import from csv file': 'oppure importa da file CSV', 'or import from csv file': 'oppure importa da file CSV',
'Origin': 'Origine', 'Origin': 'Origine',
'Other Plugins': 'Other Plugins', 'Other Plugins': 'Other Plugins',
'Other Recipes': 'Other Recipes', 'Other Recipes': 'Other Recipes',
'Overview': 'Overview', 'Overview': 'Overview',
'Password': 'Password', 'Password': 'Password',
"Password fields don't match": "Password fields don't match", "Password fields don't match": 'I campi password non sono uguali',
'please input your password again': 'please input your password again', 'please input your password again': 'perfavore reimmeti la tua password',
'Plugins': 'Plugins', 'Plugins': 'Plugins',
'Powered by': 'Powered by', 'Powered by': 'Powered by',
'Preface': 'Preface', 'Preface': 'Preface',
'previous 100 rows': '100 righe precedenti', 'previous 100 rows': '100 righe precedenti',
'Profile': 'Profile', 'Profile': 'Profilo',
'pygraphviz library not found': 'pygraphviz library not found',
'Python': 'Python', 'Python': 'Python',
'Query:': 'Richiesta (query):', 'Query:': 'Richiesta (query):',
'Quick Examples': 'Quick Examples', 'Quick Examples': 'Quick Examples',
@@ -174,28 +177,30 @@
'record does not exist': 'il record non esiste', 'record does not exist': 'il record non esiste',
'Record ID': 'Record ID', 'Record ID': 'Record ID',
'Record id': 'Record id', 'Record id': 'Record id',
'Register': 'Register', 'Register': 'Registrati',
'register': 'registrazione', 'register': 'registrazione',
'Registration identifier': 'Registration identifier', 'Registration identifier': 'Registration identifier',
'Registration key': 'Chiave di Registazione', 'Registration key': 'Chiave di Registazione',
'Registration successful': 'Registration successful', 'Registration successful': 'Registrazione avvenuta',
'Remember me (for 30 days)': 'Remember me (for 30 days)', 'reload': 'reload',
'Request reset password': 'Request reset password', 'Remember me (for 30 days)': 'Ricordami (per 30 giorni)',
'Request reset password': 'Richiedi il reset della password',
'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',
'Save profile': 'Save profile', 'Save model as...': 'Salva modello come...',
'Search': 'Search', 'Save profile': 'Salva profilo',
'Search': 'Ricerca',
'Semantic': 'Semantic', 'Semantic': 'Semantic',
'Services': 'Services', 'Services': 'Servizi',
'Size of cache:': 'Size of cache:', 'Size of cache:': 'Size of cache:',
'starts with': 'starts with', 'starts with': 'comincia con',
'state': 'stato', 'state': 'stato',
'Statistics': 'Statistics', 'Statistics': 'Statistics',
'Stylesheet': 'Foglio di stile (stylesheet)', 'Stylesheet': 'Foglio di stile (stylesheet)',
'submit': 'submit', 'submit': 'Inviai',
'Submit': 'Submit', 'Submit': 'Invia',
'Support': 'Support', 'Support': 'Support',
'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': 'tabella',
@@ -208,12 +213,13 @@
'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)",
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)', 'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
'Timestamp': 'Ora (timestamp)', 'Timestamp': 'Ora (timestamp)',
'too short': 'too short', 'too short': 'troppo corto',
'TSV (Excel compatible)': 'TSV (Excel compatible)', 'Traceback': 'Traceback',
'TSV (Excel compatible, hidden cols)': 'TSV (Excel compatible, hidden cols)', 'TSV (Excel compatible)': 'TSV (Excel compatibile)',
'TSV (Excel compatible, hidden cols)': 'TSV (Excel compatibile, hidden cols)',
'Twitter': 'Twitter', 'Twitter': 'Twitter',
'unable to parse csv file': 'non riesco a decodificare questo file CSV', 'unable to parse csv file': 'non riesco a decodificare questo file CSV',
'Update': 'Update', 'Update': 'Aggiorna',
'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).',
'User %(id)s Logged-in': 'User %(id)s Logged-in', 'User %(id)s Logged-in': 'User %(id)s Logged-in',
@@ -223,14 +229,14 @@
'User %(id)s Profile updated': 'User %(id)s Profile updated', 'User %(id)s Profile updated': 'User %(id)s Profile updated',
'User %(id)s Registered': 'User %(id)s Registered', 'User %(id)s Registered': 'User %(id)s Registered',
'User ID': 'ID Utente', 'User ID': 'ID Utente',
'value already in database or empty': 'value already in database or empty', 'value already in database or empty': 'valore già presente nel database o vuoto',
'Verify Password': 'Verify Password', 'Verify Password': 'Verifica Password',
'Videos': 'Videos', 'Videos': 'Videos',
'View': 'Vista', 'View': 'Vista',
'Welcome': 'Welcome', 'Welcome': 'Welcome',
'Welcome %s': 'Benvenuto %s', 'Welcome %s': 'Benvenuto %s',
'Welcome to web2py': 'Benvenuto su web2py', 'Welcome to web2py': 'Benvenuto su web2py',
'Welcome to web2py!': 'Welcome to web2py!', 'Welcome to web2py!': 'Benvenuto in web2py!',
'Which called the function %s located in the file %s': 'che ha chiamato la funzione %s presente nel file %s', 'Which called the function %s located in the file %s': 'che ha chiamato la funzione %s presente nel file %s',
'XML': 'XML', 'XML': 'XML',
'You are successfully running web2py': 'Stai eseguendo web2py con successo', 'You are successfully running web2py': 'Stai eseguendo web2py con successo',
@@ -102,6 +102,7 @@ td.w2p_fl,td.w2p_fc {padding:0;}
==============================================================*/ ==============================================================*/
/* because web2py handles this via js */ /* because web2py handles this via js */
textarea { width:90%}
.hidden{visibility:visible;} .hidden{visibility:visible;}
/* right folder for bootstrap black images/icons */ /* right folder for bootstrap black images/icons */
[class^="icon-"],[class*=" icon-"]{ [class^="icon-"],[class*=" icon-"]{
+35 -2
View File
@@ -373,6 +373,21 @@ class FPDF(object):
"Draw a line" "Draw a line"
self._out(sprintf('%.2f %.2f m %.2f %.2f l S',x1*self.k,(self.h-y1)*self.k,x2*self.k,(self.h-y2)*self.k)) self._out(sprintf('%.2f %.2f m %.2f %.2f l S',x1*self.k,(self.h-y1)*self.k,x2*self.k,(self.h-y2)*self.k))
def _set_dash(self, dash_length=False, space_length=False):
if(dash_length and space_length):
s = sprintf('[%.3f %.3f] 0 d', dash_length*self.k, space_length*self.k)
else:
s = '[] 0 d'
self._out(s)
def dashed_line(self, x1,y1,x2,y2, dash_length=1, space_length=1):
"""Draw a dashed line. Same interface as line() except:
- dash_length: Length of the dash
- space_length: Length of the space between dashes"""
self._set_dash(dash_length, space_length)
self.line(x1, y1, x2, y2)
self._set_dash()
def rect(self, x,y,w,h,style=''): def rect(self, x,y,w,h,style=''):
"Draw a rectangle" "Draw a rectangle"
if(style=='F'): if(style=='F'):
@@ -450,7 +465,7 @@ class FPDF(object):
fh = open(unifilename, "w") fh = open(unifilename, "w")
pickle.dump(font_dict, fh) pickle.dump(font_dict, fh)
fh.close() fh.close()
except IOError as e: except IOError, e:
if not e.errno == errno.EACCES: if not e.errno == errno.EACCES:
raise # Not a permission error. raise # Not a permission error.
del ttf del ttf
@@ -900,6 +915,24 @@ class FPDF(object):
info=self._parsepng(name) info=self._parsepng(name)
else: else:
#Allow for additional formats #Allow for additional formats
#maybe the image is not showing the correct extension,
#but the header is OK,
succeed_parsing = False
#try all the parsing functions
parsing_functions = [self._parsejpg,self._parsepng,self._parsegif]
for pf in parsing_functions:
try:
info = pf(name)
succeed_parsing = True
break;
except:
pass
#last resource
if not succeed_parsing:
mtd='_parse'+type
if not hasattr(self,mtd):
self.error('Unsupported image type: '+type)
info=getattr(self, mtd)(name)
mtd='_parse'+type mtd='_parse'+type
if not hasattr(self,mtd): if not hasattr(self,mtd):
self.error('Unsupported image type: '+type) self.error('Unsupported image type: '+type)
@@ -1341,7 +1374,7 @@ class FPDF(object):
font_dict['range'] = range_ font_dict['range'] = range_
pickle.dump(font_dict, fh) pickle.dump(font_dict, fh)
fh.close() fh.close()
except IOError as e: except IOError, e:
if not e.errno == errno.EACCES: if not e.errno == errno.EACCES:
raise # Not a permission error. raise # Not a permission error.
if (font['cw'][cid] == 0): if (font['cw'][cid] == 0):
+37 -10
View File
@@ -370,7 +370,7 @@ if not 'google' in DRIVERS:
try: try:
import fdb import fdb
DRIVERS.append('Firbird(fdb)') DRIVERS.append('Firebird(fdb)')
except ImportError: except ImportError:
LOGGER.debug('no Firebird driver fdb') LOGGER.debug('no Firebird driver fdb')
##### #####
@@ -633,6 +633,7 @@ class BaseAdapter(ConnectionPool):
TRUE = 'T' TRUE = 'T'
FALSE = 'F' FALSE = 'F'
T_SEP = ' '
types = { types = {
'boolean': 'CHAR(1)', 'boolean': 'CHAR(1)',
'string': 'CHAR(%(length)s)', 'string': 'CHAR(%(length)s)',
@@ -1810,7 +1811,7 @@ class BaseAdapter(ConnectionPool):
obj = str(obj) obj = str(obj)
elif fieldtype == 'datetime': elif fieldtype == 'datetime':
if isinstance(obj, datetime.datetime): if isinstance(obj, datetime.datetime):
obj = obj.isoformat()[:19].replace('T',' ') obj = obj.isoformat(self.T_SEP)[:19]
elif isinstance(obj, datetime.date): elif isinstance(obj, datetime.date):
obj = obj.isoformat()[:10]+' 00:00:00' obj = obj.isoformat()[:10]+' 00:00:00'
else: else:
@@ -2418,6 +2419,9 @@ class MySQLAdapter(BaseAdapter):
return ['SET FOREIGN_KEY_CHECKS=0;','DROP TABLE %s;' % table, return ['SET FOREIGN_KEY_CHECKS=0;','DROP TABLE %s;' % table,
'SET FOREIGN_KEY_CHECKS=1;'] 'SET FOREIGN_KEY_CHECKS=1;']
def _insert_empty(self, table):
return 'INSERT INTO %s VALUES (DEFAULT);' % table
def distributed_transaction_begin(self,key): def distributed_transaction_begin(self,key):
self.execute('XA START;') self.execute('XA START;')
@@ -3033,6 +3037,7 @@ class OracleAdapter(BaseAdapter):
class MSSQLAdapter(BaseAdapter): class MSSQLAdapter(BaseAdapter):
drivers = ('pyodbc',) drivers = ('pyodbc',)
T_SEP = 'T'
types = { types = {
'boolean': 'BIT', 'boolean': 'BIT',
@@ -4654,7 +4659,7 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
return self.expand(first) return self.expand(first)
def truncate(self,table,mode): def truncate(self,table,mode):
self.db(table._id).delete() self.db(self.db._adapter.id_query(table)).delete()
def select_raw(self,query,fields=None,attributes=None): def select_raw(self,query,fields=None,attributes=None):
db = self.db db = self.db
@@ -7052,15 +7057,13 @@ class DAL(object):
Example:: Example::
db = DAL('sqlite://test.db') db = DAL('sqlite://test.db')
or
db = DAL({"uri": ..., "items": ...}) # experimental
db.define_table('tablename', Field('fieldname1'), db.define_table('tablename', Field('fieldname1'),
Field('fieldname2')) Field('fieldname2'))
(experimental)
you can pass a dict object as uri with the uri string
and table/field definitions. For an example of valid data check
the output of:
>>> db.as_dict(flat=True, sanitize=False)
""" """
def __new__(cls, uri='sqlite://dummy.db', *args, **kwargs): def __new__(cls, uri='sqlite://dummy.db', *args, **kwargs):
@@ -7181,6 +7184,28 @@ class DAL(object):
:uri: string that contains information for connecting to a database. :uri: string that contains information for connecting to a database.
(default: 'sqlite://dummy.db') (default: 'sqlite://dummy.db')
experimental: you can specify a dictionary as uri
parameter i.e. with
db = DAL({"uri": "sqlite://storage.sqlite",
"items": {...}, ...})
for an example of dict input you can check the output
of the scaffolding db model with
db.as_dict()
Note that for compatibility with Python older than
version 2.6.5 you should cast your dict input keys
to str due to a syntax limitation on kwarg names.
for proper DAL dictionary input you can use one of:
obj = serializers.cast_keys(dict, [encoding="utf-8"])
or else (for parsing json input)
obj = serializers.loads_json(data, unicode_keys=False)
:pool_size: How many open connections to make to the database object. :pool_size: How many open connections to make to the database object.
:folder: where .table files will be created. :folder: where .table files will be created.
automatically set within web2py automatically set within web2py
@@ -8817,6 +8842,8 @@ class Expression(object):
result_type = 'integer' result_type = 'integer'
elif self.type in ['date','time','datetime','double','float']: elif self.type in ['date','time','datetime','double','float']:
result_type = 'double' result_type = 'double'
elif self.type.startswith('decimal('):
result_type = self.type
else: else:
raise SyntaxError("subtraction operation not supported for type") raise SyntaxError("subtraction operation not supported for type")
return Expression(db,db._adapter.SUB,self,other,result_type) return Expression(db,db._adapter.SUB,self,other,result_type)
+1 -1
View File
@@ -131,7 +131,7 @@ def get_client(env):
""" """
g = regex_client.search(env.get('http_x_forwarded_for', '')) g = regex_client.search(env.get('http_x_forwarded_for', ''))
client = (g.group() or '').split(',')[0] if g else None client = (g.group() or '').split(',')[0] if g else None
if client in (None, '', 'unkown'): if client in (None, '', 'unknown'):
g = regex_client.search(env.get('remote_addr', '')) g = regex_client.search(env.get('remote_addr', ''))
if g: if g:
client = g.group() client = g.group()
+3 -2
View File
@@ -66,7 +66,7 @@ class XssCleaner(HTMLParser):
# The only schemes allowed in URLs (for href and src attributes). # The only schemes allowed in URLs (for href and src attributes).
# Adding "javascript" or "vbscript" to this list would not be smart. # Adding "javascript" or "vbscript" to this list would not be smart.
self.allowed_schemes = ['http', 'https', 'ftp'] self.allowed_schemes = ['http', 'https', 'ftp', 'mailto']
#to strip or escape disallowed tags? #to strip or escape disallowed tags?
self.strip_disallowed = strip_disallowed self.strip_disallowed = strip_disallowed
@@ -151,11 +151,12 @@ class XssCleaner(HTMLParser):
def url_is_acceptable(self, url): def url_is_acceptable(self, url):
""" """
Accepts relative and absolute urls Accepts relative, absolute, and mailto urls
""" """
parsed = urlparse(url) parsed = urlparse(url)
return (parsed[0] in self.allowed_schemes and '.' in parsed[1]) \ return (parsed[0] in self.allowed_schemes and '.' in parsed[1]) \
or (parsed[0] in self.allowed_schemes and '@' in parsed[2]) \
or (parsed[0] == '' and parsed[2].startswith('/')) or (parsed[0] == '' and parsed[2].startswith('/'))
def strip(self, rawstring, escape=True): def strip(self, rawstring, escape=True):
+40 -2
View File
@@ -107,6 +107,7 @@ TERMINATE = 'TERMINATE'
DISABLED = 'DISABLED' DISABLED = 'DISABLED'
KILL = 'KILL' KILL = 'KILL'
PICK = 'PICK' PICK = 'PICK'
STOP_TASK = 'STOP_TASK'
EXPIRED = 'EXPIRED' EXPIRED = 'EXPIRED'
SECONDS = 1 SECONDS = 1
HEARTBEAT = 3 * SECONDS HEARTBEAT = 3 * SECONDS
@@ -398,7 +399,7 @@ class MetaScheduler(threading.Thread):
TASK_STATUS = (QUEUED, RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED, EXPIRED) TASK_STATUS = (QUEUED, RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED, EXPIRED)
RUN_STATUS = (RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED) RUN_STATUS = (RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED)
WORKER_STATUS = (ACTIVE, PICK, DISABLED, TERMINATE, KILL) WORKER_STATUS = (ACTIVE, PICK, DISABLED, TERMINATE, KILL, STOP_TASK)
class TYPE(object): class TYPE(object):
@@ -746,7 +747,7 @@ class Scheduler(MetaScheduler):
# keep sleeping # keep sleeping
self.worker_status[0] = DISABLED self.worker_status[0] = DISABLED
if self.worker_status[1] == MAXHIBERNATION: if self.worker_status[1] == MAXHIBERNATION:
logger.debug('........recording heartbeat') logger.debug('........recording heartbeat (%s)', self.worker_status[0])
db(sw.worker_name == self.worker_name).update( db(sw.worker_name == self.worker_name).update(
last_heartbeat=now) last_heartbeat=now)
elif mybackedstatus == TERMINATE: elif mybackedstatus == TERMINATE:
@@ -758,6 +759,9 @@ class Scheduler(MetaScheduler):
self.worker_status[0] = KILL self.worker_status[0] = KILL
self.die() self.die()
else: else:
if mybackedstatus == STOP_TASK:
logger.info('Asked to kill the current task')
self.terminate_process()
logger.debug('........recording heartbeat (%s)', self.worker_status[0]) logger.debug('........recording heartbeat (%s)', self.worker_status[0])
db(sw.worker_name == self.worker_name).update( db(sw.worker_name == self.worker_name).update(
last_heartbeat=now, status=ACTIVE) last_heartbeat=now, status=ACTIVE)
@@ -1007,6 +1011,40 @@ class Scheduler(MetaScheduler):
object_hook=_decode_dict) or None object_hook=_decode_dict) or None
return row return row
def stop_task(self, ref):
"""
Experimental!!!
Shortcut for task termination.
If the task is RUNNING it will terminate it --> execution will be set as FAILED
If the task is QUEUED, its stop_time will be set as to "now",
the enabled flag will be set to False, status to STOPPED
:param ref: can be
- integer --> lookup will be done by scheduler_task.id
- string --> lookup will be done by scheduler_task.uuid
Returns:
- 1 if task was stopped (meaning an update has been done)
- None if task was not found, or if task was not RUNNING or QUEUED
"""
from gluon.dal import Query
st, sw = self.db.scheduler_task, self.db.scheduler_worker
if isinstance(ref, int):
q = st.id == ref
elif isinstance(ref, str):
q = st.uuid == ref
else:
raise SyntaxError(
"You can retrieve results only by id or uuid")
task = self.db(q).select(st.id, st.status, st.assigned_worker_name).first()
rtn = None
if not task:
return rtn
if task.status == 'RUNNING':
rtn = self.db(sw.worker_name == task.assigned_worker_name).update(status=STOP_TASK)
elif task.status == 'QUEUED':
rtn = self.db(q).update(stop_time=self.now(), enabled=False, status=STOPPED)
return rtn
def main(): def main():
""" """
+45 -2
View File
@@ -25,9 +25,52 @@ try:
except ImportError: except ImportError:
have_yaml = False have_yaml = False
def loads_json(o): def cast_keys(o, cast=str, encoding="utf-8"):
""" Builds a new object with <cast> type keys
Arguments:
o is the object input
cast (defaults to str) is an object type or function
which supports conversion such as:
>>> converted = cast(o)
encoding (defaults to utf-8) is the encoding for unicode
keys. This is not used for custom cast functions
Use this funcion if you are in Python < 2.6.5
This avoids syntax errors when unpacking dictionary arguments.
"""
if isinstance(o, (dict, Storage)):
if isinstance(o, dict):
newobj = dict()
else:
newobj = Storage()
for k, v in o.items():
if (cast == str) and isinstance(k, unicode):
key = k.encode(encoding)
else:
key = cast(k)
if isinstance(v, (dict, Storage)):
value = cast_keys(v, cast=cast, encoding=encoding)
else:
value = v
newobj[key] = value
else:
raise TypeError("Cannot cast keys: %s is not supported" % \
type(o))
return newobj
def loads_json(o, unicode_keys=True, **kwargs):
# deserialize a json string # deserialize a json string
return json_parser.loads(o) result = json_parser.loads(o, **kwargs)
if not unicode_keys:
# filter non-str keys in dictionary objects
result = cast_keys(result,
encoding=kwargs.get("encoding", "utf-8"))
return result
def custom_json(o): def custom_json(o):
if hasattr(o, 'custom_json') and callable(o.custom_json): if hasattr(o, 'custom_json') and callable(o.custom_json):
+2 -1
View File
@@ -1440,7 +1440,8 @@ class SQLFORM(FORM):
f = self.table[fieldname].default or '' f = self.table[fieldname].default or ''
fields[fieldname] = f fields[fieldname] = f
else: else:
fields[fieldname] = '' f = self.table[fieldname].default or ''
fields[fieldname] = f
self.vars[fieldname] = fields[fieldname] self.vars[fieldname] = fields[fieldname]
if not f: if not f:
continue continue
+304 -282
View File
@@ -6,6 +6,8 @@
import sys import sys
import os import os
import glob
if os.path.isdir('gluon'): if os.path.isdir('gluon'):
sys.path.append(os.path.realpath('gluon')) sys.path.append(os.path.realpath('gluon'))
else: else:
@@ -19,6 +21,11 @@ except:
from io import StringIO from io import StringIO
from dal import DAL, Field, Table, SQLALL from dal import DAL, Field, Table, SQLALL
#for travis-ci
DEFAULT_URI = os.environ.get('DB', 'sqlite:memory')
print 'Testing against %s engine (%s)' % (DEFAULT_URI.partition(':')[0], DEFAULT_URI)
ALLOWED_DATATYPES = [ ALLOWED_DATATYPES = [
'string', 'string',
'text', 'text',
@@ -41,6 +48,8 @@ def setUpModule():
def tearDownModule(): def tearDownModule():
if os.path.isfile('sql.log'): if os.path.isfile('sql.log'):
os.unlink('sql.log') os.unlink('sql.log')
for a in glob.glob('*.table'):
os.unlink(a)
class TestFields(unittest.TestCase): class TestFields(unittest.TestCase):
@@ -96,35 +105,35 @@ class TestFields(unittest.TestCase):
isinstance(f.formatter(datetime.datetime.now()), str) isinstance(f.formatter(datetime.datetime.now()), str)
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
for ft in ['string', 'text', 'password', 'upload', 'blob']: for ft in ['string', 'text', 'password', 'upload', 'blob']:
db.define_table('t', Field('a', ft, default='')) db.define_table('tt', Field('aa', ft, default=''))
self.assertEqual(db.t.insert(a='x'), 1) self.assertEqual(db.tt.insert(aa='x'), 1)
self.assertEqual(db().select(db.t.a)[0].a, 'x') self.assertEqual(db().select(db.tt.aa)[0].aa, 'x')
db.t.drop() db.tt.drop()
db.define_table('t', Field('a', 'integer', default=1)) db.define_table('tt', Field('aa', 'integer', default=1))
self.assertEqual(db.t.insert(a=3), 1) self.assertEqual(db.tt.insert(aa=3), 1)
self.assertEqual(db().select(db.t.a)[0].a, 3) self.assertEqual(db().select(db.tt.aa)[0].aa, 3)
db.t.drop() db.tt.drop()
db.define_table('t', Field('a', 'double', default=1)) db.define_table('tt', Field('aa', 'double', default=1))
self.assertEqual(db.t.insert(a=3.1), 1) self.assertEqual(db.tt.insert(aa=3.1), 1)
self.assertEqual(db().select(db.t.a)[0].a, 3.1) self.assertEqual(db().select(db.tt.aa)[0].aa, 3.1)
db.t.drop() db.tt.drop()
db.define_table('t', Field('a', 'boolean', default=True)) db.define_table('tt', Field('aa', 'boolean', default=True))
self.assertEqual(db.t.insert(a=True), 1) self.assertEqual(db.tt.insert(aa=True), 1)
self.assertEqual(db().select(db.t.a)[0].a, True) self.assertEqual(db().select(db.tt.aa)[0].aa, True)
db.t.drop() db.tt.drop()
db.define_table('t', Field('a', 'json', default={})) db.define_table('tt', Field('aa', 'json', default={}))
self.assertEqual(db.t.insert(a={}), 1) self.assertEqual(db.tt.insert(aa={}), 1)
self.assertEqual(db().select(db.t.a)[0].a, {}) self.assertEqual(db().select(db.tt.aa)[0].aa, {})
db.t.drop() db.tt.drop()
db.define_table('t', Field('a', 'date', db.define_table('tt', Field('aa', 'date',
default=datetime.date.today())) default=datetime.date.today()))
t0 = datetime.date.today() t0 = datetime.date.today()
self.assertEqual(db.t.insert(a=t0), 1) self.assertEqual(db.tt.insert(aa=t0), 1)
self.assertEqual(db().select(db.t.a)[0].a, t0) self.assertEqual(db().select(db.tt.aa)[0].aa, t0)
db.t.drop() db.tt.drop()
db.define_table('t', Field('a', 'datetime', db.define_table('tt', Field('aa', 'datetime',
default=datetime.datetime.today())) default=datetime.datetime.today()))
t0 = datetime.datetime( t0 = datetime.datetime(
1971, 1971,
@@ -135,34 +144,34 @@ class TestFields(unittest.TestCase):
55, 55,
0, 0,
) )
self.assertEqual(db.t.insert(a=t0), 1) self.assertEqual(db.tt.insert(aa=t0), 1)
self.assertEqual(db().select(db.t.a)[0].a, t0) self.assertEqual(db().select(db.tt.aa)[0].aa, t0)
## Row APIs ## Row APIs
row = db().select(db.t.a)[0] row = db().select(db.tt.aa)[0]
self.assertEqual(db.t[1].a,t0) self.assertEqual(db.tt[1].aa,t0)
self.assertEqual(db.t['a'],db.t.a) self.assertEqual(db.tt['aa'],db.tt.aa)
self.assertEqual(db.t(1).a,t0) self.assertEqual(db.tt(1).aa,t0)
self.assertTrue(db.t(1,a=None)==None) self.assertTrue(db.tt(1,aa=None)==None)
self.assertFalse(db.t(1,a=t0)==None) self.assertFalse(db.tt(1,aa=t0)==None)
self.assertEqual(row.a,t0) self.assertEqual(row.aa,t0)
self.assertEqual(row['a'],t0) self.assertEqual(row['aa'],t0)
self.assertEqual(row['t.a'],t0) self.assertEqual(row['tt.aa'],t0)
self.assertEqual(row('t.a'),t0) self.assertEqual(row('tt.aa'),t0)
## Lazy and Virtual fields ## Lazy and Virtual fields
db.t.b = Field.Virtual(lambda row: row.t.a) db.tt.b = Field.Virtual(lambda row: row.tt.aa)
db.t.c = Field.Lazy(lambda row: row.t.a) db.tt.c = Field.Lazy(lambda row: row.tt.aa)
row = db().select(db.t.a)[0] row = db().select(db.tt.aa)[0]
self.assertEqual(row.b,t0) self.assertEqual(row.b,t0)
self.assertEqual(row.c(),t0) self.assertEqual(row.c(),t0)
db.t.drop() db.tt.drop()
db.define_table('t', Field('a', 'time', default='11:30')) db.define_table('tt', Field('aa', 'time', default='11:30'))
t0 = datetime.time(10, 30, 55) t0 = datetime.time(10, 30, 55)
self.assertEqual(db.t.insert(a=t0), 1) self.assertEqual(db.tt.insert(aa=t0), 1)
self.assertEqual(db().select(db.t.a)[0].a, t0) self.assertEqual(db().select(db.tt.aa)[0].aa, t0)
db.t.drop() db.tt.drop()
class TestAll(unittest.TestCase): class TestAll(unittest.TestCase):
@@ -198,7 +207,7 @@ class TestTable(unittest.TestCase):
in str(persons.ALL)) in str(persons.ALL))
def testTableAlias(self): def testTableAlias(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
persons = Table(db, 'persons', Field('firstname', persons = Table(db, 'persons', Field('firstname',
'string'), Field('lastname', 'string')) 'string'), Field('lastname', 'string'))
aliens = persons.with_alias('aliens') aliens = persons.with_alias('aliens')
@@ -221,276 +230,278 @@ class TestTable(unittest.TestCase):
class TestInsert(unittest.TestCase): class TestInsert(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a')) db.define_table('tt', Field('aa'))
self.assertEqual(db.t.insert(a='1'), 1) self.assertEqual(db.tt.insert(aa='1'), 1)
self.assertEqual(db.t.insert(a='1'), 2) self.assertEqual(db.tt.insert(aa='1'), 2)
self.assertEqual(db.t.insert(a='1'), 3) self.assertEqual(db.tt.insert(aa='1'), 3)
self.assertEqual(db(db.t.a == '1').count(), 3) self.assertEqual(db(db.tt.aa == '1').count(), 3)
self.assertEqual(db(db.t.a == '1').update(a='2'), 3) self.assertEqual(db(db.tt.aa == '1').update(aa='2'), 3)
self.assertEqual(db(db.t.a == '2').count(), 3) self.assertEqual(db(db.tt.aa == '2').count(), 3)
self.assertEqual(db(db.t.a == '2').delete(), 3) self.assertEqual(db(db.tt.aa == '2').delete(), 3)
db.t.drop() db.tt.drop()
class TestSelect(unittest.TestCase): class TestSelect(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a')) db.define_table('tt', Field('aa'))
self.assertEqual(db.t.insert(a='1'), 1) self.assertEqual(db.tt.insert(aa='1'), 1)
self.assertEqual(db.t.insert(a='2'), 2) self.assertEqual(db.tt.insert(aa='2'), 2)
self.assertEqual(db.t.insert(a='3'), 3) self.assertEqual(db.tt.insert(aa='3'), 3)
self.assertEqual(len(db(db.t.id > 0).select()), 3) self.assertEqual(len(db(db.tt.id > 0).select()), 3)
self.assertEqual(db(db.t.id > 0).select(orderby=~db.t.a self.assertEqual(db(db.tt.id > 0).select(orderby=~db.tt.aa
| db.t.id)[0].a, '3') | db.tt.id)[0].aa, '3')
self.assertEqual(len(db(db.t.id > 0).select(limitby=(1, 2))), 1) self.assertEqual(len(db(db.tt.id > 0).select(limitby=(1, 2))), 1)
self.assertEqual(db(db.t.id > 0).select(limitby=(1, 2))[0].a, self.assertEqual(db(db.tt.id > 0).select(limitby=(1, 2))[0].aa,
'2') '2')
self.assertEqual(len(db().select(db.t.ALL)), 3) self.assertEqual(len(db().select(db.tt.ALL)), 3)
self.assertEqual(len(db(db.t.a == None).select()), 0) self.assertEqual(len(db(db.tt.aa == None).select()), 0)
self.assertEqual(len(db(db.t.a != None).select()), 3) self.assertEqual(len(db(db.tt.aa != None).select()), 3)
self.assertEqual(len(db(db.t.a > '1').select()), 2) self.assertEqual(len(db(db.tt.aa > '1').select()), 2)
self.assertEqual(len(db(db.t.a >= '1').select()), 3) self.assertEqual(len(db(db.tt.aa >= '1').select()), 3)
self.assertEqual(len(db(db.t.a == '1').select()), 1) self.assertEqual(len(db(db.tt.aa == '1').select()), 1)
self.assertEqual(len(db(db.t.a != '1').select()), 2) self.assertEqual(len(db(db.tt.aa != '1').select()), 2)
self.assertEqual(len(db(db.t.a < '3').select()), 2) self.assertEqual(len(db(db.tt.aa < '3').select()), 2)
self.assertEqual(len(db(db.t.a <= '3').select()), 3) self.assertEqual(len(db(db.tt.aa <= '3').select()), 3)
self.assertEqual(len(db(db.t.a > '1')(db.t.a < '3').select()), 1) self.assertEqual(len(db(db.tt.aa > '1')(db.tt.aa < '3').select()), 1)
self.assertEqual(len(db((db.t.a > '1') & (db.t.a < '3')).select()), 1) self.assertEqual(len(db((db.tt.aa > '1') & (db.tt.aa < '3')).select()), 1)
self.assertEqual(len(db((db.t.a > '1') | (db.t.a < '3')).select()), 3) self.assertEqual(len(db((db.tt.aa > '1') | (db.tt.aa < '3')).select()), 3)
self.assertEqual(len(db((db.t.a > '1') & ~(db.t.a > '2')).select()), 1) self.assertEqual(len(db((db.tt.aa > '1') & ~(db.tt.aa > '2')).select()), 1)
self.assertEqual(len(db(~(db.t.a > '1') & (db.t.a > '2')).select()), 0) self.assertEqual(len(db(~(db.tt.aa > '1') & (db.tt.aa > '2')).select()), 0)
db.t.drop() db.tt.drop()
class TestBelongs(unittest.TestCase): class TestBelongs(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a')) db.define_table('tt', Field('aa'))
self.assertEqual(db.t.insert(a='1'), 1) self.assertEqual(db.tt.insert(aa='1'), 1)
self.assertEqual(db.t.insert(a='2'), 2) self.assertEqual(db.tt.insert(aa='2'), 2)
self.assertEqual(db.t.insert(a='3'), 3) self.assertEqual(db.tt.insert(aa='3'), 3)
self.assertEqual(len(db(db.t.a.belongs(('1', '3'))).select()), self.assertEqual(len(db(db.tt.aa.belongs(('1', '3'))).select()),
2) 2)
self.assertEqual(len(db(db.t.a.belongs(db(db.t.id self.assertEqual(len(db(db.tt.aa.belongs(db(db.tt.id
> 2)._select(db.t.a))).select()), 1) > 2)._select(db.tt.aa))).select()), 1)
self.assertEqual(len(db(db.t.a.belongs(db(db.t.a.belongs(('1', self.assertEqual(len(db(db.tt.aa.belongs(db(db.tt.aa.belongs(('1',
'3')))._select(db.t.a))).select()), 2) '3')))._select(db.tt.aa))).select()), 2)
self.assertEqual(len(db(db.t.a.belongs(db(db.t.a.belongs(db self.assertEqual(len(db(db.tt.aa.belongs(db(db.tt.aa.belongs(db
(db.t.a.belongs(('1', '3')))._select(db.t.a)))._select( (db.tt.aa.belongs(('1', '3')))._select(db.tt.aa)))._select(
db.t.a))).select()), db.tt.aa))).select()),
2) 2)
db.t.drop() db.tt.drop()
class TestContains(unittest.TestCase): class TestContains(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a', 'list:string')) db.define_table('tt', Field('aa', 'list:string'))
self.assertEqual(db.t.insert(a=['aaa','bbb']), 1) self.assertEqual(db.tt.insert(aa=['aaa','bbb']), 1)
self.assertEqual(db.t.insert(a=['bbb','ddd']), 2) self.assertEqual(db.tt.insert(aa=['bbb','ddd']), 2)
self.assertEqual(db.t.insert(a=['eee','aaa']), 3) self.assertEqual(db.tt.insert(aa=['eee','aaa']), 3)
self.assertEqual(len(db(db.t.a.contains('aaa')).select()), self.assertEqual(len(db(db.tt.aa.contains('aaa')).select()),
2) 2)
self.assertEqual(len(db(db.t.a.contains('bbb')).select()), self.assertEqual(len(db(db.tt.aa.contains('bbb')).select()),
2) 2)
self.assertEqual(len(db(db.t.a.contains('aa')).select()), self.assertEqual(len(db(db.tt.aa.contains('aa')).select()),
0) 0)
db.t.drop() db.tt.drop()
class TestLike(unittest.TestCase): class TestLike(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a')) db.define_table('tt', Field('aa'))
self.assertEqual(db.t.insert(a='abc'), 1) self.assertEqual(db.tt.insert(aa='abc'), 1)
self.assertEqual(len(db(db.t.a.like('a%')).select()), 1) self.assertEqual(len(db(db.tt.aa.like('a%')).select()), 1)
self.assertEqual(len(db(db.t.a.like('%b%')).select()), 1) self.assertEqual(len(db(db.tt.aa.like('%b%')).select()), 1)
self.assertEqual(len(db(db.t.a.like('%c')).select()), 1) self.assertEqual(len(db(db.tt.aa.like('%c')).select()), 1)
self.assertEqual(len(db(db.t.a.like('%d%')).select()), 0) self.assertEqual(len(db(db.tt.aa.like('%d%')).select()), 0)
self.assertEqual(len(db(db.t.a.lower().like('A%')).select()), 1) self.assertEqual(len(db(db.tt.aa.lower().like('A%')).select()), 1)
self.assertEqual(len(db(db.t.a.lower().like('%B%')).select()), self.assertEqual(len(db(db.tt.aa.lower().like('%B%')).select()),
1) 1)
self.assertEqual(len(db(db.t.a.lower().like('%C')).select()), 1) self.assertEqual(len(db(db.tt.aa.lower().like('%C')).select()), 1)
self.assertEqual(len(db(db.t.a.upper().like('A%')).select()), 1) self.assertEqual(len(db(db.tt.aa.upper().like('A%')).select()), 1)
self.assertEqual(len(db(db.t.a.upper().like('%B%')).select()), self.assertEqual(len(db(db.tt.aa.upper().like('%B%')).select()),
1) 1)
self.assertEqual(len(db(db.t.a.upper().like('%C')).select()), 1) self.assertEqual(len(db(db.tt.aa.upper().like('%C')).select()), 1)
db.t.drop() db.tt.drop()
db.define_table('t', Field('a', 'integer')) db.define_table('tt', Field('aa', 'integer'))
self.assertEqual(db.t.insert(a=1111111111), 1) self.assertEqual(db.tt.insert(aa=1111111111), 1)
self.assertEqual(len(db(db.t.a.like('1%')).select()), 1) self.assertEqual(len(db(db.tt.aa.like('1%')).select()), 1)
self.assertEqual(len(db(db.t.a.like('2%')).select()), 0) self.assertEqual(len(db(db.tt.aa.like('2%')).select()), 0)
db.t.drop() db.tt.drop()
class TestDatetime(unittest.TestCase): class TestDatetime(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a', 'datetime')) db.define_table('tt', Field('aa', 'datetime'))
self.assertEqual(db.t.insert(a=datetime.datetime(1971, 12, 21, self.assertEqual(db.tt.insert(aa=datetime.datetime(1971, 12, 21,
11, 30)), 1) 11, 30)), 1)
self.assertEqual(db.t.insert(a=datetime.datetime(1971, 11, 21, self.assertEqual(db.tt.insert(aa=datetime.datetime(1971, 11, 21,
10, 30)), 2) 10, 30)), 2)
self.assertEqual(db.t.insert(a=datetime.datetime(1970, 12, 21, self.assertEqual(db.tt.insert(aa=datetime.datetime(1970, 12, 21,
9, 30)), 3) 9, 30)), 3)
self.assertEqual(len(db(db.t.a == datetime.datetime(1971, 12, self.assertEqual(len(db(db.tt.aa == datetime.datetime(1971, 12,
21, 11, 30)).select()), 1) 21, 11, 30)).select()), 1)
self.assertEqual(db(db.t.a.year() == 1971).count(), 2) self.assertEqual(db(db.tt.aa.year() == 1971).count(), 2)
self.assertEqual(db(db.t.a.month() == 12).count(), 2) self.assertEqual(db(db.tt.aa.month() == 12).count(), 2)
self.assertEqual(db(db.t.a.day() == 21).count(), 3) self.assertEqual(db(db.tt.aa.day() == 21).count(), 3)
self.assertEqual(db(db.t.a.hour() == 11).count(), 1) self.assertEqual(db(db.tt.aa.hour() == 11).count(), 1)
self.assertEqual(db(db.t.a.minutes() == 30).count(), 3) self.assertEqual(db(db.tt.aa.minutes() == 30).count(), 3)
self.assertEqual(db(db.t.a.seconds() == 0).count(), 3) self.assertEqual(db(db.tt.aa.seconds() == 0).count(), 3)
self.assertEqual(db(db.t.a.epoch()<365*24*3600).count(),1) self.assertEqual(db(db.tt.aa.epoch()<365*24*3600).count(),1)
db.t.drop() db.tt.drop()
class TestExpressions(unittest.TestCase): class TestExpressions(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a', 'integer')) db.define_table('tt', Field('aa', 'integer'))
self.assertEqual(db.t.insert(a=1), 1) self.assertEqual(db.tt.insert(aa=1), 1)
self.assertEqual(db.t.insert(a=2), 2) self.assertEqual(db.tt.insert(aa=2), 2)
self.assertEqual(db.t.insert(a=3), 3) self.assertEqual(db.tt.insert(aa=3), 3)
self.assertEqual(db(db.t.a == 3).update(a=db.t.a + 1), 1) self.assertEqual(db(db.tt.aa == 3).update(aa=db.tt.aa + 1), 1)
self.assertEqual(db(db.t.a == 4).count(), 1) self.assertEqual(db(db.tt.aa == 4).count(), 1)
db.t.drop() db.tt.drop()
class TestJoin(unittest.TestCase): class TestJoin(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t1', Field('a')) db.define_table('t1', Field('aa'))
db.define_table('t2', Field('a'), Field('b', db.t1)) db.define_table('t2', Field('aa'), Field('b', db.t1))
i1 = db.t1.insert(a='1') i1 = db.t1.insert(aa='1')
i2 = db.t1.insert(a='2') i2 = db.t1.insert(aa='2')
i3 = db.t1.insert(a='3') i3 = db.t1.insert(aa='3')
db.t2.insert(a='4', b=i1) db.t2.insert(aa='4', b=i1)
db.t2.insert(a='5', b=i2) db.t2.insert(aa='5', b=i2)
db.t2.insert(a='6', b=i2) db.t2.insert(aa='6', b=i2)
self.assertEqual(len(db(db.t1.id self.assertEqual(len(db(db.t1.id
== db.t2.b).select(orderby=db.t1.a == db.t2.b).select(orderby=db.t1.aa
| db.t2.a)), 3) | db.t2.aa)), 3)
self.assertEqual(db(db.t1.id == db.t2.b).select(orderby=db.t1.a self.assertEqual(db(db.t1.id == db.t2.b).select(orderby=db.t1.aa
| db.t2.a)[2].t1.a, '2') | db.t2.aa)[2].t1.aa, '2')
self.assertEqual(db(db.t1.id == db.t2.b).select(orderby=db.t1.a self.assertEqual(db(db.t1.id == db.t2.b).select(orderby=db.t1.aa
| db.t2.a)[2].t2.a, '6') | db.t2.aa)[2].t2.aa, '6')
self.assertEqual(len(db().select(db.t1.ALL, db.t2.ALL, self.assertEqual(len(db().select(db.t1.ALL, db.t2.ALL,
left=db.t2.on(db.t1.id == db.t2.b), left=db.t2.on(db.t1.id == db.t2.b),
orderby=db.t1.a | db.t2.a)), 4) orderby=db.t1.aa | db.t2.aa)), 4)
self.assertEqual(db().select(db.t1.ALL, db.t2.ALL, self.assertEqual(db().select(db.t1.ALL, db.t2.ALL,
left=db.t2.on(db.t1.id == db.t2.b), left=db.t2.on(db.t1.id == db.t2.b),
orderby=db.t1.a | db.t2.a)[2].t1.a, '2') orderby=db.t1.aa | db.t2.aa)[2].t1.aa, '2')
self.assertEqual(db().select(db.t1.ALL, db.t2.ALL, self.assertEqual(db().select(db.t1.ALL, db.t2.ALL,
left=db.t2.on(db.t1.id == db.t2.b), left=db.t2.on(db.t1.id == db.t2.b),
orderby=db.t1.a | db.t2.a)[2].t2.a, '6') orderby=db.t1.aa | db.t2.aa)[2].t2.aa, '6')
self.assertEqual(db().select(db.t1.ALL, db.t2.ALL, self.assertEqual(db().select(db.t1.ALL, db.t2.ALL,
left=db.t2.on(db.t1.id == db.t2.b), left=db.t2.on(db.t1.id == db.t2.b),
orderby=db.t1.a | db.t2.a)[3].t1.a, '3') orderby=db.t1.aa | db.t2.aa)[3].t1.aa, '3')
self.assertEqual(db().select(db.t1.ALL, db.t2.ALL, self.assertEqual(db().select(db.t1.ALL, db.t2.ALL,
left=db.t2.on(db.t1.id == db.t2.b), left=db.t2.on(db.t1.id == db.t2.b),
orderby=db.t1.a | db.t2.a)[3].t2.a, None) orderby=db.t1.aa | db.t2.aa)[3].t2.aa, None)
self.assertEqual(len(db().select(db.t1.ALL, db.t2.id.count(), self.assertEqual(len(db().select(db.t1.aa, db.t2.id.count(),
left=db.t2.on(db.t1.id == db.t2.b), left=db.t2.on(db.t1.id == db.t2.b),
orderby=db.t1.a | db.t2.a, groupby=db.t1.a)), orderby=db.t1.aa, groupby=db.t1.aa)),
3) 3)
self.assertEqual(db().select(db.t1.ALL, db.t2.id.count(), self.assertEqual(db().select(db.t1.aa, db.t2.id.count(),
left=db.t2.on(db.t1.id == db.t2.b), left=db.t2.on(db.t1.id == db.t2.b),
orderby=db.t1.a | db.t2.a, orderby=db.t1.aa,
groupby=db.t1.a)[0]._extra[db.t2.id.count()], groupby=db.t1.aa)[0]._extra[db.t2.id.count()],
1) 1)
self.assertEqual(db().select(db.t1.ALL, db.t2.id.count(), self.assertEqual(db().select(db.t1.aa, db.t2.id.count(),
left=db.t2.on(db.t1.id == db.t2.b), left=db.t2.on(db.t1.id == db.t2.b),
orderby=db.t1.a | db.t2.a, orderby=db.t1.aa,
groupby=db.t1.a)[1]._extra[db.t2.id.count()], groupby=db.t1.aa)[1]._extra[db.t2.id.count()],
2) 2)
self.assertEqual(db().select(db.t1.ALL, db.t2.id.count(), self.assertEqual(db().select(db.t1.aa, db.t2.id.count(),
left=db.t2.on(db.t1.id == db.t2.b), left=db.t2.on(db.t1.id == db.t2.b),
orderby=db.t1.a | db.t2.a, orderby=db.t1.aa,
groupby=db.t1.a)[2]._extra[db.t2.id.count()], groupby=db.t1.aa)[2]._extra[db.t2.id.count()],
0) 0)
db.t1.drop()
db.t2.drop() db.t2.drop()
db.t1.drop()
db.define_table('person',Field('name')) db.define_table('person',Field('name'))
id = db.person.insert(name="max") id = db.person.insert(name="max")
self.assertEqual(id.name,'max') self.assertEqual(id.name,'max')
db.define_table('dog',Field('name'),Field('owner','reference person')) db.define_table('dog',Field('name'),Field('ownerperson','reference person'))
db.dog.insert(name='skipper',owner=1) db.dog.insert(name='skipper',ownerperson=1)
row = db(db.person.id==db.dog.owner).select().first() row = db(db.person.id==db.dog.ownerperson).select().first()
self.assertEqual(row[db.person.name],'max') self.assertEqual(row[db.person.name],'max')
self.assertEqual(row['person.name'],'max') self.assertEqual(row['person.name'],'max')
db.dog.drop() db.dog.drop()
self.assertEqual(len(db.person._referenced_by),0) self.assertEqual(len(db.person._referenced_by),0)
db.person.drop() db.person.drop()
class TestMinMaxSum(unittest.TestCase): class TestMinMaxSumAvg(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a', 'integer')) db.define_table('tt', Field('aa', 'integer'))
self.assertEqual(db.t.insert(a=1), 1) self.assertEqual(db.tt.insert(aa=1), 1)
self.assertEqual(db.t.insert(a=2), 2) self.assertEqual(db.tt.insert(aa=2), 2)
self.assertEqual(db.t.insert(a=3), 3) self.assertEqual(db.tt.insert(aa=3), 3)
s = db.t.a.min() s = db.tt.aa.min()
self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 1) self.assertEqual(db(db.tt.id > 0).select(s)[0]._extra[s], 1)
self.assertEqual(db(db.t.id > 0).select(s).first()[s], 1) self.assertEqual(db(db.tt.id > 0).select(s).first()[s], 1)
self.assertEqual(db().select(s).first()[s], 1) self.assertEqual(db().select(s).first()[s], 1)
s = db.t.a.max() s = db.tt.aa.max()
self.assertEqual(db().select(s).first()[s], 3) self.assertEqual(db().select(s).first()[s], 3)
s = db.t.a.sum() s = db.tt.aa.sum()
self.assertEqual(db().select(s).first()[s], 6) self.assertEqual(db().select(s).first()[s], 6)
s = db.t.a.count() s = db.tt.aa.count()
self.assertEqual(db().select(s).first()[s], 3) self.assertEqual(db().select(s).first()[s], 3)
db.t.drop() s = db.tt.aa.avg()
self.assertEqual(db().select(s).first()[s], 2)
db.tt.drop()
class TestCache(unittest.TestCase): class TestCache(unittest.TestCase):
def testRun(self): def testRun(self):
from cache import CacheInRam from cache import CacheInRam
cache = CacheInRam() cache = CacheInRam()
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a')) db.define_table('tt', Field('aa'))
db.t.insert(a='1') db.tt.insert(aa='1')
r0 = db().select(db.t.ALL) r0 = db().select(db.tt.ALL)
r1 = db().select(db.t.ALL, cache=(cache, 1000)) r1 = db().select(db.tt.ALL, cache=(cache, 1000))
self.assertEqual(len(r0),len(r1)) self.assertEqual(len(r0),len(r1))
r2 = db().select(db.t.ALL, cache=(cache, 1000)) r2 = db().select(db.tt.ALL, cache=(cache, 1000))
self.assertEqual(len(r0),len(r2)) self.assertEqual(len(r0),len(r2))
r3 = db().select(db.t.ALL, cache=(cache, 1000), cacheable=True) r3 = db().select(db.tt.ALL, cache=(cache, 1000), cacheable=True)
self.assertEqual(len(r0),len(r3)) self.assertEqual(len(r0),len(r3))
r4 = db().select(db.t.ALL, cache=(cache, 1000), cacheable=True) r4 = db().select(db.tt.ALL, cache=(cache, 1000), cacheable=True)
self.assertEqual(len(r0),len(r4)) self.assertEqual(len(r0),len(r4))
db.t.drop() db.tt.drop()
class TestMigrations(unittest.TestCase): class TestMigrations(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite://.storage.db') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a'), migrate='.storage.table') db.define_table('tt', Field('aa'), migrate='.storage.table')
db.commit() db.commit()
db.close() db.close()
db = DAL('sqlite://.storage.db') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a'), Field('b'), db.define_table('tt', Field('aa'), Field('b'),
migrate='.storage.table') migrate='.storage.table')
db.commit() db.commit()
db.close() db.close()
db = DAL('sqlite://.storage.db') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a'), Field('b', 'text'), db.define_table('tt', Field('aa'), Field('b', 'text'),
migrate='.storage.table') migrate='.storage.table')
db.commit() db.commit()
db.close() db.close()
db = DAL('sqlite://.storage.db') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a'), migrate='.storage.table') db.define_table('tt', Field('aa'), migrate='.storage.table')
db.t.drop() db.tt.drop()
db.commit() db.commit()
db.close() db.close()
@@ -500,81 +511,81 @@ class TestMigrations(unittest.TestCase):
if os.path.exists('.storage.table'): if os.path.exists('.storage.table'):
os.unlink('.storage.table') os.unlink('.storage.table')
class TestReferece(unittest.TestCase): class TestReference(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('name'), Field('a','reference t')) db.define_table('tt', Field('name'), Field('aa','reference tt'))
db.commit() db.commit()
x = db.t.insert(name='max') x = db.tt.insert(name='max')
assert x.id == 1 assert x.id == 1
assert x['id'] == 1 assert x['id'] == 1
x.a = x x.aa = x
assert x.a == 1 assert x.aa == 1
x.update_record() x.update_record()
y = db.t[1] y = db.tt[1]
assert y.a == 1 assert y.aa == 1
assert y.a.a.a.a.a.a.name == 'max' assert y.aa.aa.aa.aa.aa.aa.name == 'max'
z=db.t.insert(name='xxx', a = y) z=db.tt.insert(name='xxx', aa = y)
assert z.a == y.id assert z.aa == y.id
db.t.drop() db.tt.drop()
db.commit() db.commit()
class TestClientLevelOps(unittest.TestCase): class TestClientLevelOps(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a')) db.define_table('tt', Field('aa'))
db.commit() db.commit()
db.t.insert(a="test") db.tt.insert(aa="test")
rows1 = db(db.t.id>0).select() rows1 = db(db.tt.id>0).select()
rows2 = db(db.t.id>0).select() rows2 = db(db.tt.id>0).select()
rows3 = rows1 & rows2 rows3 = rows1 & rows2
assert len(rows3) == 2 assert len(rows3) == 2
rows4 = rows1 | rows2 rows4 = rows1 | rows2
assert len(rows4) == 1 assert len(rows4) == 1
rows5 = rows1.find(lambda row: row.a=="test") rows5 = rows1.find(lambda row: row.aa=="test")
assert len(rows5) == 1 assert len(rows5) == 1
rows6 = rows2.exclude(lambda row: row.a=="test") rows6 = rows2.exclude(lambda row: row.aa=="test")
assert len(rows6) == 1 assert len(rows6) == 1
rows7 = rows5.sort(lambda row: row.a) rows7 = rows5.sort(lambda row: row.aa)
assert len(rows7) == 1 assert len(rows7) == 1
db.t.drop() db.tt.drop()
db.commit() db.commit()
class TestVirtualFields(unittest.TestCase): class TestVirtualFields(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', Field('a')) db.define_table('tt', Field('aa'))
db.commit() db.commit()
db.t.insert(a="test") db.tt.insert(aa="test")
class Compute: class Compute:
def a_upper(row): return row.t.a.upper() def a_upper(row): return row.tt.aa.upper()
db.t.virtualfields.append(Compute()) db.tt.virtualfields.append(Compute())
assert db(db.t.id>0).select().first().a_upper == 'TEST' assert db(db.tt.id>0).select().first().a_upper == 'TEST'
db.t.drop() db.tt.drop()
db.commit() db.commit()
class TestComputedFields(unittest.TestCase): class TestComputedFields(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('t', db.define_table('tt',
Field('a'), Field('aa'),
Field('b',default='x'), Field('bb',default='x'),
Field('c',compute=lambda r: r.a+r.b)) Field('cc',compute=lambda r: r.aa+r.bb))
db.commit() db.commit()
id = db.t.insert(a="z") id = db.tt.insert(aa="z")
self.assertEqual(db.t[id].c,'zx') self.assertEqual(db.tt[id].cc,'zx')
db.t.drop() db.tt.drop()
db.commit() db.commit()
class TestImportExportFields(unittest.TestCase): class TestImportExportFields(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('person', Field('name')) db.define_table('person', Field('name'))
db.define_table('pet',Field('friend',db.person),Field('name')) db.define_table('pet',Field('friend',db.person),Field('name'))
for n in range(2): for n in range(2):
@@ -598,7 +609,7 @@ class TestImportExportFields(unittest.TestCase):
class TestImportExportUuidFields(unittest.TestCase): class TestImportExportUuidFields(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('person', Field('name'),Field('uuid')) db.define_table('person', Field('name'),Field('uuid'))
db.define_table('pet',Field('friend',db.person),Field('name')) db.define_table('pet',Field('friend',db.person),Field('name'))
for n in range(2): for n in range(2):
@@ -622,7 +633,7 @@ class TestImportExportUuidFields(unittest.TestCase):
class TestDALDictImportExport(unittest.TestCase): class TestDALDictImportExport(unittest.TestCase):
def testRun(self): def testRun(self):
db = DAL('sqlite:memory:') db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('person', Field('name', default="Michael"),Field('uuid')) db.define_table('person', Field('name', default="Michael"),Field('uuid'))
db.define_table('pet',Field('friend',db.person),Field('name')) db.define_table('pet',Field('friend',db.person),Field('name'))
dbdict = db.as_dict(flat=True, sanitize=False) dbdict = db.as_dict(flat=True, sanitize=False)
@@ -635,77 +646,88 @@ class TestDALDictImportExport(unittest.TestCase):
assert dbdict["items"]["person"]["items"]["name"]["default"] == db.person.name.default assert dbdict["items"]["person"]["items"]["name"]["default"] == db.person.name.default
assert dbdict assert dbdict
db2 = DAL(dbdict) db2 = DAL(dbdict, check_reserved=['all'])
assert len(db.tables) == len(db2.tables) assert len(db.tables) == len(db2.tables)
assert hasattr(db2, "pet") and isinstance(db2.pet, Table) assert hasattr(db2, "pet") and isinstance(db2.pet, Table)
assert hasattr(db2.pet, "friend") and isinstance(db2.pet.friend, Field) assert hasattr(db2.pet, "friend") and isinstance(db2.pet.friend, Field)
db.pet.drop()
db.commit()
db2.commit()
have_serializers = True have_serializers = True
try: try:
import serializers import serializers
dbjson = db.as_json(sanitize=False) dbjson = db.as_json(sanitize=False)
assert isinstance(dbjson, basestring) and len(dbjson) > 0 assert isinstance(dbjson, basestring) and len(dbjson) > 0
db3 = DAL(serializers.loads_json(dbjson))
unicode_keys = True
if sys.version < "2.6.5":
unicode_keys = False
db3 = DAL(serializers.loads_json(dbjson,
unicode_keys=unicode_keys))
assert hasattr(db3, "person") and hasattr(db3.person, "uuid") and\ assert hasattr(db3, "person") and hasattr(db3.person, "uuid") and\
db3.person.uuid.type == db.person.uuid.type db3.person.uuid.type == db.person.uuid.type
db3.pet.drop()
db3.person.drop() db3.person.drop()
db3.commit() db3.commit()
except ImportError: except ImportError:
pass pass
mpfc = "Monty Python's Flying Circus" mpfc = "Monty Python's Flying Circus"
dbdict4 = {"uri": 'sqlite:memory:', dbdict4 = {"uri": DEFAULT_URI,
"items":{"staff":{"items": {"name": "items":{"staff":{"items": {"name":
{"default":"Michael"}, {"default":"Michael"},
"food": "food":
{"default":"Spam"}, {"default":"Spam"},
"show": "tvshow":
{"type": "reference show"} {"type": "reference tvshow"}
}}, }},
"show":{"items": {"name": "tvshow":{"items": {"name":
{"default":mpfc}, {"default":mpfc},
"rating": "rating":
{"type":"double"}}}}} {"type":"double"}}}}}
db4 = DAL(dbdict4) db4 = DAL(dbdict4, check_reserved=['all'])
assert "staff" in db4.tables assert "staff" in db4.tables
assert "name" in db4.staff assert "name" in db4.staff
assert db4.show.rating.type == "double" assert db4.tvshow.rating.type == "double"
assert (db4.show.insert(), db4.show.insert(name="Loriot"), assert (db4.tvshow.insert(), db4.tvshow.insert(name="Loriot"),
db4.show.insert(name="Il Mattatore")) == (1, 2, 3) db4.tvshow.insert(name="Il Mattatore")) == (1, 2, 3)
assert db4(db4.show).select().first().id == 1 assert db4(db4.tvshow).select().first().id == 1
assert db4(db4.show).select().first().name == mpfc assert db4(db4.tvshow).select().first().name == mpfc
dbdict5 = {"uri": 'sqlite:memory:'} db4.staff.drop()
db5 = DAL(dbdict5) db4.tvshow.drop()
db4.commit()
dbdict5 = {"uri": DEFAULT_URI}
db5 = DAL(dbdict5, check_reserved=['all'])
assert db5.tables in ([], None) assert db5.tables in ([], None)
assert not (str(db5) in ("", None)) assert not (str(db5) in ("", None))
dbdict6 = {"uri": 'sqlite:memory:', dbdict6 = {"uri": DEFAULT_URI,
"items":{"staff":{}, "items":{"staff":{},
"show":{"items": {"name": {}, "tvshow":{"items": {"name": {},
"rating": "rating":
{"type":"double"}}}}} {"type":"double"}
db6 = DAL(dbdict6) }
}
}
}
db6 = DAL(dbdict6, check_reserved=['all'])
assert len(db6["staff"].fields) == 1 assert len(db6["staff"].fields) == 1
assert "name" in db6["show"].fields assert "name" in db6["tvshow"].fields
assert db6.staff.insert() is not None assert db6.staff.insert() is not None
assert db6(db6.staff).select().first().id == 1 assert db6(db6.staff).select().first().id == 1
db6.staff.drop() db6.staff.drop()
db6.show.drop() db6.tvshow.drop()
db6.commit() db6.commit()
db4.staff.drop()
db4.show.drop()
db4.commit()
db2.pet.drop()
db2.person.drop()
db2.commit()
db.pet.drop()
db.person.drop()
db.commit()
if __name__ == '__main__': if __name__ == '__main__':
+55 -28
View File
@@ -4475,6 +4475,8 @@ class Service(object):
return return_response(id, s) return return_response(id, s)
else: else:
return '' return ''
except HTTP, e:
raise e
except Service.JsonRpcException, e: except Service.JsonRpcException, e:
return return_error(id, e.code, e.info) return return_error(id, e.code, e.info)
except BaseException: except BaseException:
@@ -4897,7 +4899,7 @@ class Wiki(object):
everybody = 'everybody' everybody = 'everybody'
rows_page = 25 rows_page = 25
def markmin_base(self,body): def markmin_base(self,body):
return MARKMIN(body, extra=self.extra, return MARKMIN(body, extra=self.settings.extra,
url=True, environment=self.env, url=True, environment=self.env,
autolinks=lambda link: expand_one(link, {})).xml() autolinks=lambda link: expand_one(link, {})).xml()
@@ -4931,29 +4933,43 @@ class Wiki(object):
controller, function, args = items[0], items[1], items[2:] controller, function, args = items[0], items[1], items[2:]
return LOAD(controller, function, args=args, ajax=True).xml() return LOAD(controller, function, args=args, ajax=True).xml()
def get_render(self):
if isinstance(self.settings.render, basestring):
r = getattr(self, "%s_render" % self.settings.render)
elif callable(self.settings.render):
r = self.settings.render
else:
raise ValueError("Invalid render type %s" % type(render))
return r
def __init__(self, auth, env=None, render='markmin', def __init__(self, auth, env=None, render='markmin',
manage_permissions=False, force_prefix='', manage_permissions=False, force_prefix='',
restrict_search=False, extra=None, restrict_search=False, extra=None,
menu_groups=None, templates=None): menu_groups=None, templates=None):
settings = self.settings = Settings()
# render: "markmin", "html", ..., <function>
settings.render = render
perms = settings.manage_permissions = manage_permissions
settings.force_prefix = force_prefix
settings.restrict_search = restrict_search
settings.extra = extra or {}
settings.menu_groups = menu_groups
settings.templates = templates
db = auth.db db = auth.db
self.env = env or {} self.env = env or {}
self.env['component'] = Wiki.component self.env['component'] = Wiki.component
if render == 'markmin':
render = self.markmin_render
elif render == 'html':
render = self.html_render
self.render = render
self.auth = auth self.auth = auth
self.menu_groups = menu_groups
if self.auth.user: if self.auth.user:
self.force_prefix = force_prefix % self.auth.user self.settings.force_prefix = force_prefix % self.auth.user
else: else:
self.force_prefix = force_prefix self.settings.force_prefix = force_prefix
self.host = current.request.env.http_host self.host = current.request.env.http_host
perms = self.manage_permissions = manage_permissions
self.restrict_search = restrict_search
self.extra = extra or {}
self.templates = templates
table_definitions = [ table_definitions = [
('wiki_page', { ('wiki_page', {
@@ -4973,7 +4989,8 @@ class Wiki(object):
writable=perms, readable=perms, writable=perms, readable=perms,
default=[Wiki.everybody]), default=[Wiki.everybody]),
Field('changelog'), Field('changelog'),
Field('html', 'text', compute=render, Field('html', 'text',
compute=self.get_render(),
readable=False, writable=False), readable=False, writable=False),
auth.signature], auth.signature],
'vars':{'format':'%(title)s'}}), 'vars':{'format':'%(title)s'}}),
@@ -5006,8 +5023,9 @@ class Wiki(object):
args += value['args'] args += value['args']
db.define_table(key, *args, **value['vars']) db.define_table(key, *args, **value['vars'])
if self.templates is None and not self.manage_permissions: if self.settings.templates is None and not \
self.templates = db.wiki_page.tags.contains('template')&\ self.settings.manage_permissions:
self.settings.templates = db.wiki_page.tags.contains('template')&\
db.wiki_page.can_read.contains('everybody') db.wiki_page.can_read.contains('everybody')
def update_tags_insert(page, id, db=db): def update_tags_insert(page, id, db=db):
@@ -5031,13 +5049,17 @@ class Wiki(object):
gid = group.id if group else db.auth_group.insert( gid = group.id if group else db.auth_group.insert(
role='wiki_editor') role='wiki_editor')
auth.add_membership(gid) auth.add_membership(gid)
settings.lock_keys = True
# WIKI ACCESS POLICY # WIKI ACCESS POLICY
def not_authorized(self, page=None): def not_authorized(self, page=None):
raise HTTP(401) raise HTTP(401)
def can_read(self, page): def can_read(self, page):
if 'everybody' in page.can_read or not self.manage_permissions: if 'everybody' in page.can_read or not \
self.settings.manage_permissions:
return True return True
elif self.auth.user: elif self.auth.user:
groups = self.auth.user_groups.values() groups = self.auth.user_groups.values()
@@ -5067,11 +5089,11 @@ class Wiki(object):
return True return True
def can_see_menu(self): def can_see_menu(self):
if self.menu_groups is None: if self.settings.menu_groups is None:
return True return True
if self.auth.user: if self.auth.user:
groups = self.auth.user_groups.values() groups = self.auth.user_groups.values()
if any(t in self.menu_groups for t in groups): if any(t in self.settings.menu_groups for t in groups):
return True return True
return False return False
@@ -5108,7 +5130,7 @@ class Wiki(object):
elif zero == '_cloud': elif zero == '_cloud':
return self.cloud() return self.cloud()
elif zero == '_preview': elif zero == '_preview':
return self.preview(self.render) return self.preview(self.get_render())
def first_paragraph(self, page): def first_paragraph(self, page):
if not self.can_read(page): if not self.can_read(page):
@@ -5176,9 +5198,9 @@ class Wiki(object):
title_guess = ' '.join(c.capitalize() for c in slug.split('-')) title_guess = ' '.join(c.capitalize() for c in slug.split('-'))
if not page: if not page:
if not (self.can_manage() or if not (self.can_manage() or
slug.startswith(self.force_prefix)): slug.startswith(self.settings.force_prefix)):
current.session.flash = 'slug must have "%s" prefix' \ current.session.flash = 'slug must have "%s" prefix' \
% self.force_prefix % self.settings.force_prefix
redirect(URL(args=('_create'))) redirect(URL(args=('_create')))
db.wiki_page.can_read.default = [Wiki.everybody] db.wiki_page.can_read.default = [Wiki.everybody]
db.wiki_page.can_edit.default = [auth.user_group_role()] db.wiki_page.can_edit.default = [auth.user_group_role()]
@@ -5234,7 +5256,10 @@ class Wiki(object):
} }
}) })
}) })
""" % dict(url=URL(args=('_preview')), urlmedia=URL(extension='load',args=('_editmedia'),vars=dict(embedded=1))) """ % dict(url=URL(args=('_preview', slug)),
urlmedia=URL(extension='load',
args=('_editmedia',slug),
vars=dict(embedded=1)))
return dict(content=TAG[''](form, SCRIPT(script))) return dict(content=TAG[''](form, SCRIPT(script)))
def editmedia(self, slug): def editmedia(self, slug):
@@ -5280,12 +5305,13 @@ class Wiki(object):
options=[OPTION(row.slug,_value=row.id) for row in slugs] options=[OPTION(row.slug,_value=row.id) for row in slugs]
options.insert(0, OPTION('',_value='')) options.insert(0, OPTION('',_value=''))
fields = [Field("slug", default=current.request.args(1) or fields = [Field("slug", default=current.request.args(1) or
self.force_prefix, self.settings.force_prefix,
requires=(IS_SLUG(), IS_NOT_IN_DB(db,db.wiki_page.slug))),] requires=(IS_SLUG(), IS_NOT_IN_DB(db,db.wiki_page.slug))),]
if self.templates: if self.settings.templates:
fields.append( fields.append(
Field("from_template", "reference wiki_page", Field("from_template", "reference wiki_page",
requires=IS_EMPTY_OR(IS_IN_DB(db(self.templates), requires=IS_EMPTY_OR(
IS_IN_DB(db(self.settings.templates),
db.wiki_page._id, db.wiki_page._id,
'%(slug)s')), '%(slug)s')),
comment=current.T( comment=current.T(
@@ -5329,7 +5355,7 @@ class Wiki(object):
request, db = current.request, self.auth.db request, db = current.request, self.auth.db
media = db.wiki_media(id) media = db.wiki_media(id)
if media: if media:
if self.manage_permissions: if self.settings.manage_permissions:
page = db.wiki_page(media.wiki_page) page = db.wiki_page(media.wiki_page)
if not self.can_read(page): if not self.can_read(page):
return self.not_authorized(page) return self.not_authorized(page)
@@ -5428,7 +5454,7 @@ class Wiki(object):
query = (db.wiki_page.id == db.wiki_tag.wiki_page) &\ query = (db.wiki_page.id == db.wiki_tag.wiki_page) &\
(db.wiki_tag.name.belongs(tags)) (db.wiki_tag.name.belongs(tags))
query = query | db.wiki_page.title.contains(request.vars.q) query = query | db.wiki_page.title.contains(request.vars.q)
if self.restrict_search and not self.manage(): if self.settings.restrict_search and not self.manage():
query = query & (db.wiki_page.created_by == self.auth.user_id) query = query & (db.wiki_page.created_by == self.auth.user_id)
pages = db(query).select(count, pages = db(query).select(count,
*fields, **dict(orderby=orderby or ~count, *fields, **dict(orderby=orderby or ~count,
@@ -5489,6 +5515,7 @@ class Wiki(object):
request = current.request request = current.request
return render(request.post_vars) return render(request.post_vars)
if __name__ == '__main__': if __name__ == '__main__':
import doctest import doctest
doctest.testmod() doctest.testmod()
+34 -12
View File
@@ -2219,22 +2219,29 @@ class IS_DATE(Validator):
""" """
def __init__(self, format='%Y-%m-%d', def __init__(self, format='%Y-%m-%d',
error_message='enter date as %(format)s'): error_message='enter date as %(format)s',
timezone = None):
self.format = translate(format) self.format = translate(format)
self.error_message = str(error_message) self.error_message = str(error_message)
self.timezone = timezone
self.extremes = {} self.extremes = {}
def __call__(self, value): def __call__(self, value):
ovalue = value
if isinstance(value, datetime.date): if isinstance(value, datetime.date):
if self.timezone is not None:
value = value - datetime.timedelta(seconds=self.timezone*3600)
return (value, None) return (value, None)
try: try:
(y, m, d, hh, mm, ss, t0, t1, t2) = \ (y, m, d, hh, mm, ss, t0, t1, t2) = \
time.strptime(value, str(self.format)) time.strptime(value, str(self.format))
value = datetime.date(y, m, d) value = datetime.date(y, m, d)
if self.timezone is not None:
value = value - datetime.timedelta(seconds=self.timezone*3600)
return (value, None) return (value, None)
except: except:
self.extremes.update(IS_DATETIME.nice(self.format)) self.extremes.update(IS_DATETIME.nice(self.format))
return (value, translate(self.error_message) % self.extremes) return (ovalue, translate(self.error_message) % self.extremes)
def formatter(self, value): def formatter(self, value):
if value is None: if value is None:
@@ -2247,6 +2254,8 @@ class IS_DATE(Validator):
if year < 1900: if year < 1900:
year = 2000 year = 2000
d = datetime.date(year, value.month, value.day) d = datetime.date(year, value.month, value.day)
if self.timezone is not None:
d = d + datetime.timedelta(seconds=self.timezone*3600)
return d.strftime(format) return d.strftime(format)
@@ -2279,22 +2288,27 @@ class IS_DATETIME(Validator):
return dict(format=format) return dict(format=format)
def __init__(self, format='%Y-%m-%d %H:%M:%S', def __init__(self, format='%Y-%m-%d %H:%M:%S',
error_message='enter date and time as %(format)s'): error_message='enter date and time as %(format)s',
timezone=None):
self.format = translate(format) self.format = translate(format)
self.error_message = str(error_message) self.error_message = str(error_message)
self.extremes = {} self.extremes = {}
self.timezone = timezone
def __call__(self, value): def __call__(self, value):
ovalue = value
if isinstance(value, datetime.datetime): if isinstance(value, datetime.datetime):
return (value, None) return (value, None)
try: try:
(y, m, d, hh, mm, ss, t0, t1, t2) = \ (y, m, d, hh, mm, ss, t0, t1, t2) = \
time.strptime(value, str(self.format)) time.strptime(value, str(self.format))
value = datetime.datetime(y, m, d, hh, mm, ss) value = datetime.datetime(y, m, d, hh, mm, ss)
if self.timezone is not None:
value = value - datetime.timedelta(seconds=self.timezone*3600)
return (value, None) return (value, None)
except: except:
self.extremes.update(IS_DATETIME.nice(self.format)) self.extremes.update(IS_DATETIME.nice(self.format))
return (value, translate(self.error_message) % self.extremes) return (ovalue, translate(self.error_message) % self.extremes)
def formatter(self, value): def formatter(self, value):
if value is None: if value is None:
@@ -2308,6 +2322,8 @@ class IS_DATETIME(Validator):
year = 2000 year = 2000
d = datetime.datetime(year, value.month, value.day, d = datetime.datetime(year, value.month, value.day,
value.hour, value.minute, value.second) value.hour, value.minute, value.second)
if self.timezone is not None:
d = d + datetime.timedelta(seconds=self.timezone*3600)
return d.strftime(format) return d.strftime(format)
@@ -2336,7 +2352,8 @@ class IS_DATE_IN_RANGE(IS_DATE):
minimum=None, minimum=None,
maximum=None, maximum=None,
format='%Y-%m-%d', format='%Y-%m-%d',
error_message=None): error_message=None,
timezone=None):
self.minimum = minimum self.minimum = minimum
self.maximum = maximum self.maximum = maximum
if error_message is None: if error_message is None:
@@ -2348,17 +2365,19 @@ class IS_DATE_IN_RANGE(IS_DATE):
error_message = "enter date in range %(min)s %(max)s" error_message = "enter date in range %(min)s %(max)s"
IS_DATE.__init__(self, IS_DATE.__init__(self,
format=format, format=format,
error_message=error_message) error_message=error_message,
timezone=timezone)
self.extremes = dict(min=minimum, max=maximum) self.extremes = dict(min=minimum, max=maximum)
def __call__(self, value): def __call__(self, value):
ovalue = value
(value, msg) = IS_DATE.__call__(self, value) (value, msg) = IS_DATE.__call__(self, value)
if msg is not None: if msg is not None:
return (value, msg) return (value, msg)
if self.minimum and self.minimum > value: if self.minimum and self.minimum > value:
return (value, translate(self.error_message) % self.extremes) return (ovalue, translate(self.error_message) % self.extremes)
if self.maximum and value > self.maximum: if self.maximum and value > self.maximum:
return (value, translate(self.error_message) % self.extremes) return (ovalue, translate(self.error_message) % self.extremes)
return (value, None) return (value, None)
@@ -2386,7 +2405,8 @@ class IS_DATETIME_IN_RANGE(IS_DATETIME):
minimum=None, minimum=None,
maximum=None, maximum=None,
format='%Y-%m-%d %H:%M:%S', format='%Y-%m-%d %H:%M:%S',
error_message=None): error_message=None,
timezone=None):
self.minimum = minimum self.minimum = minimum
self.maximum = maximum self.maximum = maximum
if error_message is None: if error_message is None:
@@ -2398,17 +2418,19 @@ class IS_DATETIME_IN_RANGE(IS_DATETIME):
error_message = "enter date and time in range %(min)s %(max)s" error_message = "enter date and time in range %(min)s %(max)s"
IS_DATETIME.__init__(self, IS_DATETIME.__init__(self,
format=format, format=format,
error_message=error_message) error_message=error_message,
timezone=timezone)
self.extremes = dict(min=minimum, max=maximum) self.extremes = dict(min=minimum, max=maximum)
def __call__(self, value): def __call__(self, value):
ovalue = value
(value, msg) = IS_DATETIME.__call__(self, value) (value, msg) = IS_DATETIME.__call__(self, value)
if msg is not None: if msg is not None:
return (value, msg) return (value, msg)
if self.minimum and self.minimum > value: if self.minimum and self.minimum > value:
return (value, translate(self.error_message) % self.extremes) return (ovalue, translate(self.error_message) % self.extremes)
if self.maximum and value > self.maximum: if self.maximum and value > self.maximum:
return (value, translate(self.error_message) % self.extremes) return (ovalue, translate(self.error_message) % self.extremes)
return (value, None) return (value, None)
-17
View File
@@ -1,17 +0,0 @@
language: python
python:
- '2.5'
- '2.6'
- '2.7'
before_script:
- pip install unittest2
- mysql -e 'create database test_pymysql;'
- mysql -e 'create database test_pymysql2;'
script: PYTHONPATH=. unit2 -v gluon.tests
notifications:
irc:
channels: "irc.freenode.org#web2py"