Compare commits
159 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b1b9e73c5 | |||
| 0fc60eedfb | |||
| 9e19833421 | |||
| 31318340e2 | |||
| 6cef27e69c | |||
| 4f6ab86491 | |||
| 4147867098 | |||
| a0dead1586 | |||
| 0afc18fd24 | |||
| 9f695c783e | |||
| 15263ea1d6 | |||
| 693c5cd5e4 | |||
| cc3f045efa | |||
| 43806a26e7 | |||
| cb4c4b3eb5 | |||
| 39558267b9 | |||
| 59a3c5a6e6 | |||
| 2c9eeccc0e | |||
| 7576d85dba | |||
| 579f0d752f | |||
| 65e9cc2127 | |||
| dc2fd49ecb | |||
| a380ae69d5 | |||
| 0b84911d38 | |||
| e64c556f84 | |||
| 18644e027e | |||
| 65642c029c | |||
| 11540a6132 | |||
| a6e728316b | |||
| 4cb5e3c674 | |||
| d918a6b2e2 | |||
| d721451d15 | |||
| c5aaebd4a8 | |||
| 4c1dbf4e40 | |||
| c20c055788 | |||
| 44c13f9fa3 | |||
| ae44a9e0ac | |||
| 97ba47f339 | |||
| 8671b897d0 | |||
| 1a3361a1bb | |||
| 9ac438d05a | |||
| 18e45ab994 | |||
| f00090846e | |||
| 6590de030c | |||
| 714fcc5202 | |||
| becc947556 | |||
| d547a955ac | |||
| 97e0736d8e | |||
| 20db6daa65 | |||
| 3aefb5f6de | |||
| 30818cdf5e | |||
| 4089cf2145 | |||
| d7893c5292 | |||
| cdd3f6ed00 | |||
| e44254346c | |||
| adf82c1267 | |||
| 4d98b72702 | |||
| f02240acd3 | |||
| cabef57851 | |||
| 98ad12a06c | |||
| d454eb2fe7 | |||
| 0facbe04ef | |||
| 8f9f4aae32 | |||
| d5bfaf83ee | |||
| 9e46c3cbc4 | |||
| d431206e30 | |||
| 6adfb8b944 | |||
| 0c8ccaac9a | |||
| 4f0ee4543b | |||
| 1383d13334 | |||
| 1b260c4b84 | |||
| 0ff586a1ca | |||
| 95adb233f7 | |||
| e7f2e494af | |||
| ded9e5886d | |||
| d2757294af | |||
| 39da6a68fe | |||
| 8e4bc72fca | |||
| bc481e954c | |||
| e4ba924dc1 | |||
| 193510b57d | |||
| 88ad882d83 | |||
| a90c4f7953 | |||
| 0c2d97b9db | |||
| 1c77fdd9e1 | |||
| 681bc9755e | |||
| 31e992696c | |||
| 813411a408 | |||
| 1702d1ac1f | |||
| 022a2af8b1 | |||
| d46d16bc2b | |||
| ae3ff2313d | |||
| 669586f15e | |||
| 46fbcc8d22 | |||
| 178f94af8c | |||
| 2f6b3a0bda | |||
| 97b70eff9f | |||
| 3efa77b57a | |||
| 48977ba04c | |||
| 94e3271481 | |||
| 04f6e7ee38 | |||
| 15c9e685c8 | |||
| b00463bdf4 | |||
| c17e642f1f | |||
| a2e118ec50 | |||
| 45bda63ed3 | |||
| c909af4a86 | |||
| b5a7c7e30f | |||
| 6594e7a607 | |||
| 4d8dedeb27 | |||
| b0e3e386e7 | |||
| ad4870e338 | |||
| c1f0bc3a6d | |||
| 887d4cc136 | |||
| bbe787b7b4 | |||
| 7341988b01 | |||
| 44eb880108 | |||
| c01f860009 | |||
| f9315a8cc7 | |||
| 40918f44fd | |||
| 9fdb586d03 | |||
| 448c398341 | |||
| 618c322603 | |||
| b5c9ed633b | |||
| 80342a22f5 | |||
| c999b16a27 | |||
| 9bda793d46 | |||
| 5e9fbdd24f | |||
| 446e641e64 | |||
| 7d53ec6d71 | |||
| 7b0cf5bc12 | |||
| a82034d516 | |||
| 4973361136 | |||
| 3b69d4bf7b | |||
| fd5e14ea0d | |||
| 9fc3b2bc26 | |||
| 19cc977917 | |||
| ce481d9002 | |||
| 2421a31b25 | |||
| 94c79820b9 | |||
| 80b38a743f | |||
| af01b45852 | |||
| a2de6f12d1 | |||
| 2af81d2e7a | |||
| 0f691f2757 | |||
| be021db3d3 | |||
| acd0ebd09d | |||
| 3d5e594070 | |||
| 58247e3d6b | |||
| 9f35635233 | |||
| 2119bacd22 | |||
| d55fab540c | |||
| 1344c50f85 | |||
| e8d97f5706 | |||
| 35704f3527 | |||
| 96fb5b0755 | |||
| 81f017a7c0 | |||
| b863ff048e | |||
| 79a4693f34 |
+19
-6
@@ -4,22 +4,35 @@ python:
|
||||
- '2.5'
|
||||
- '2.6'
|
||||
- '2.7'
|
||||
- 'pypy'
|
||||
install:
|
||||
- pip install -e . --use-mirrors
|
||||
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.7' ]]; then pip install --use-mirrors unittest2; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors coverage; fi;
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors python-coveralls; fi
|
||||
- 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
|
||||
matrix:
|
||||
exclude:
|
||||
- python: 'pypy'
|
||||
env: DB=postgres://postgres:@localhost/test_w2p
|
||||
- python: 'pypy'
|
||||
env: DB=mysql://root:@localhost/test_w2p
|
||||
|
||||
script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage
|
||||
after_success:
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coverage combine; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --config_file=gluon/tests/coverage.ini; fi
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
channels: "irc.freenode.org#web2py"
|
||||
email: true
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
## 2.4.7
|
||||
|
||||
- pypy support, thanks Niphlod
|
||||
- more bug fixes
|
||||
- ...
|
||||
|
||||
## 2.4.6
|
||||
|
||||
- better tests
|
||||
|
||||
@@ -30,7 +30,7 @@ update:
|
||||
echo "remember that pymysql was tweaked"
|
||||
src:
|
||||
### Use semantic versioning
|
||||
echo 'Version 2.4.6-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
echo 'Version 2.4.7-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
### rm -f all junk files
|
||||
make clean
|
||||
### clean up baisc apps
|
||||
@@ -118,6 +118,7 @@ commit:
|
||||
push:
|
||||
hg push
|
||||
git push
|
||||
git push --tags
|
||||
tag:
|
||||
git tag -l '$(S)'
|
||||
hg tag -l '$(S)'
|
||||
|
||||
@@ -11,6 +11,8 @@ Learn more at http://web2py.com
|
||||
|
||||
[](https://travis-ci.org/web2py/web2py)
|
||||
|
||||
[](https://coveralls.io/r/web2py/web2py)
|
||||
|
||||
## Installation Instructions
|
||||
|
||||
To start web2py there is NO NEED to install it. Just unzip and do:
|
||||
|
||||
@@ -1 +1 @@
|
||||
Version 2.4.6-stable+timestamp.2013.04.06.12.11.51
|
||||
Version 2.4.7-stable+timestamp.2013.05.25.10.38.02
|
||||
|
||||
@@ -279,14 +279,15 @@ def update():
|
||||
(db, table) = get_table(request)
|
||||
keyed = hasattr(db[table], '_primarykey')
|
||||
record = None
|
||||
db[table]._common_filter = None
|
||||
if keyed:
|
||||
key = [f for f in request.vars if f in db[table]._primarykey]
|
||||
if key:
|
||||
record = db(db[table][key[0]] == request.vars[key[
|
||||
0]], ignore_common_filters=True).select().first()
|
||||
0]]).select().first()
|
||||
else:
|
||||
record = db(db[table].id == request.args(
|
||||
2), ignore_common_filters=True).select().first()
|
||||
2)).select().first()
|
||||
|
||||
if not record:
|
||||
qry = query_by_table_type(table, db)
|
||||
|
||||
@@ -138,17 +138,17 @@ def check_version():
|
||||
session.forget()
|
||||
session._unlock(response)
|
||||
|
||||
new_version, version_number = check_new_version(request.env.web2py_version,
|
||||
WEB2PY_VERSION_URL)
|
||||
new_version, version = check_new_version(request.env.web2py_version,
|
||||
WEB2PY_VERSION_URL)
|
||||
|
||||
if new_version == -1:
|
||||
return A(T('Unable to check for upgrades'), _href=WEB2PY_URL)
|
||||
elif new_version != True:
|
||||
return A(T('web2py is up to date'), _href=WEB2PY_URL)
|
||||
elif platform.system().lower() in ('windows', 'win32', 'win64') and os.path.exists("web2py.exe"):
|
||||
return SPAN('You should upgrade to version %s' % version_number)
|
||||
return SPAN('You should upgrade to %s' % version.split('(')[0])
|
||||
else:
|
||||
return sp_button(URL('upgrade_web2py'), T('upgrade now to %s') % version_number.split('-')[0])
|
||||
return sp_button(URL('upgrade_web2py'), T('upgrade now to %s') % version.split('(')[0])
|
||||
|
||||
|
||||
def logout():
|
||||
|
||||
+160
-129
@@ -1,119 +1,126 @@
|
||||
# coding: utf8
|
||||
# Translation by: Klaus Kappel <kkappel@yahoo.de>
|
||||
{
|
||||
'!langcode!': 'de',
|
||||
'!langname!': 'Deutsch',
|
||||
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"Update" ist ein optionaler Ausdruck wie "Feld1 = \'newvalue". JOIN Ergebnisse können nicht aktualisiert oder gelöscht werden',
|
||||
'%s %%{row} deleted': '%s Zeilen gelöscht',
|
||||
'%s %%{row} updated': '%s Zeilen aktualisiert',
|
||||
'%Y-%m-%d': '%Y-%m-%d',
|
||||
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
|
||||
'(requires internet access)': '(requires internet access)',
|
||||
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"Update" ist ein optionaler Ausdruck wie "Feld1 = \'newvalue". JOIN Ergebnisse k?nnen nicht aktualisiert oder gel?scht werden',
|
||||
'%s %%{row} deleted': '%s %%{row} Zeilen gel?scht',
|
||||
'%s %%{row} updated': '%s %%{row} Zeilen aktualisiert',
|
||||
'%Y-%m-%d': '%d.%m.%Y',
|
||||
'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S',
|
||||
'(requires internet access)': '(Internet Zugang wir ben?tigt)',
|
||||
'(requires internet access, experimental)': '(ben?tigt Internet Zugang)',
|
||||
'(something like "it-it")': '(so etwas wie "it-it")',
|
||||
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
|
||||
'A new version of web2py is available': 'Eine neue Version von web2py ist verfügbar',
|
||||
'A new version of web2py is available: %s': 'Eine neue Version von web2py ist verfügbar: %s',
|
||||
'@markmin\x01Searching: **%s** %%{file}': '@markmin\x01Suche: **%s** Dateien',
|
||||
'A new version of web2py is available': 'Eine neue Version von web2py ist verf?gbar',
|
||||
'A new version of web2py is available: %s': 'Eine neue Version von web2py ist verf?gbar: %s',
|
||||
'Abort': 'Abbrechen',
|
||||
'About': 'Über',
|
||||
'About application': 'Über die Anwendung',
|
||||
'Additional code for your application': 'Additional code for your application',
|
||||
'additional code for your application': 'zusätzlicher Code für Ihre Anwendung',
|
||||
'admin disabled because no admin password': ' admin ist deaktiviert, weil kein Admin-Passwort gesetzt ist',
|
||||
'admin disabled because not supported on google apps engine': 'admin ist deaktiviert, es existiert dafür keine Unterstützung auf der google apps engine',
|
||||
'About': '?ber',
|
||||
'About application': '?ber die Anwendung',
|
||||
'Additional code for your application': 'zus?tzlicher Code f?r Ihre Anwendung',
|
||||
'admin disabled because no admin password': 'admin ist deaktiviert, weil kein Admin-Passwort gesetzt ist',
|
||||
'admin disabled because not supported on google apps engine': 'admin ist deaktiviert, es existiert daf?r keine Unterst?tzung auf der google apps engine',
|
||||
'admin disabled because unable to access password file': 'admin ist deaktiviert, weil kein Zugriff auf die Passwortdatei besteht',
|
||||
'Admin is disabled because insecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals',
|
||||
'Admin is disabled because unsecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals',
|
||||
'Admin language': 'Admin language',
|
||||
'administrative interface': 'administrative interface',
|
||||
'Admin language': 'Admin-Sprache',
|
||||
'administrative interface': 'Administrative Schnittstelle',
|
||||
'Administrator Password:': 'Administrator Passwort:',
|
||||
'An error occured, please %s the page': 'Ein Fehler ist aufgetereten, bitte %s die Seite',
|
||||
'and rename it (required):': 'und benenne sie um (erforderlich):',
|
||||
'and rename it:': ' und benenne sie um:',
|
||||
'appadmin': 'appadmin',
|
||||
'appadmin is disabled because insecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals',
|
||||
'Application': 'Anwendung',
|
||||
'application "%s" uninstalled': 'Anwendung "%s" deinstalliert',
|
||||
'application compiled': 'Anwendung kompiliert',
|
||||
'application is compiled and cannot be designed': 'Die Anwendung ist kompiliert kann deswegen nicht mehr geändert werden',
|
||||
'Application name:': 'Application name:',
|
||||
'Are you sure you want to delete file "%s"?': 'Sind Sie sich sicher, dass Sie diese Datei löschen wollen "%s"?',
|
||||
'application is compiled and cannot be designed': 'Die Anwendung ist kompiliert kann deswegen nicht mehr ge?ndert werden',
|
||||
'Application name:': 'Name der Applikation:',
|
||||
'Are you sure you want to delete file "%s"?': 'Sind Sie sich sicher, dass Sie diese Datei l?schen wollen "%s"?',
|
||||
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
|
||||
'Are you sure you want to uninstall application "%s"': 'Sind Sie sich sicher, dass Sie diese Anwendung deinstallieren wollen "%s"',
|
||||
'Are you sure you want to uninstall application "%s"?': 'Sind Sie sich sicher, dass Sie diese Anwendung deinstallieren wollen "%s"?',
|
||||
'Are you sure you want to upgrade web2py now?': 'Sind Sie sich sicher, dass Sie web2py jetzt upgraden möchten?',
|
||||
'Are you sure you want to upgrade web2py now?': 'Sind Sie sich sicher, dass Sie web2py jetzt upgraden m?chten?',
|
||||
'arguments': 'arguments',
|
||||
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ACHTUNG: Die Einwahl benötigt eine sichere (HTTPS) Verbindung. Es sei denn sie läuft Lokal(localhost).',
|
||||
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ACHTUNG: Testen ist nicht threadsicher. Führen sie also nicht mehrere Tests gleichzeitig aus.',
|
||||
'ATTENTION: This is an experimental feature and it needs more testing.': 'ACHTUNG: Dies ist eine experimentelle Funktion und benötigt noch weitere Tests.',
|
||||
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ACHTUNG: Die Einwahl ben?tigt eine sichere (HTTPS) Verbindung. Es sei denn sie l?uft Lokal(localhost).',
|
||||
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ACHTUNG: Testen ist nicht threadsicher. F?hren sie also nicht mehrere Tests gleichzeitig aus.',
|
||||
'ATTENTION: This is an experimental feature and it needs more testing.': 'ACHTUNG: Dies ist eine experimentelle Funktion und ben?tigt noch weitere Tests.',
|
||||
'ATTENTION: you cannot edit the running application!': 'ACHTUNG: Eine laufende Anwendung kann nicht editiert werden!',
|
||||
'Authentication': 'Authentifizierung',
|
||||
'Available databases and tables': 'Verfügbare Datenbanken und Tabellen',
|
||||
'back': 'zurück',
|
||||
'beautify': 'beautify',
|
||||
'cache': 'Cache',
|
||||
'cache, errors and sessions cleaned': 'Zwischenspeicher (cache), Fehler und Sitzungen (sessions) gelöscht',
|
||||
'call': 'call',
|
||||
'can be a git repo': 'can be a git repo',
|
||||
'Available databases and tables': 'Verf?gbare Datenbanken und Tabellen',
|
||||
'back': 'zur?ck',
|
||||
'beautify': 'versch?nern',
|
||||
'cache': 'Pufferspeicher',
|
||||
'cache, errors and sessions cleaned': 'Zwischenspeicher (cache), Fehler und Sitzungen (sessions) gel?scht',
|
||||
'call': 'Aufruf',
|
||||
'can be a git repo': 'kann ein git Repository sein',
|
||||
'Cannot be empty': 'Darf nicht leer sein',
|
||||
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Nicht Kompilierbar:Es sind Fehler in der Anwendung. Beseitigen Sie die Fehler und versuchen Sie es erneut.',
|
||||
'cannot create file': 'Kann Datei nicht erstellen',
|
||||
'cannot upload file "%(filename)s"': 'Kann Datei nicht Hochladen "%(filename)s"',
|
||||
'Change admin password': 'Administrator-Passwort ändern',
|
||||
'Change Password': 'Passwort ändern',
|
||||
'change password': 'Passwort ändern',
|
||||
'check all': 'alles auswählen',
|
||||
'Change admin password': 'Administrator-Passwort ?ndern',
|
||||
'Change Password': 'Passwort ?ndern',
|
||||
'change password': 'Passwort ?ndern',
|
||||
'check all': 'alles ausw?hlen',
|
||||
'Check for upgrades': 'check for upgrades',
|
||||
'Check to delete': 'Markiere zum löschen',
|
||||
'Checking for upgrades...': 'Auf Updates überprüfen...',
|
||||
'Clean': 'löschen',
|
||||
'click here for online examples': 'hier klicken für online Beispiele',
|
||||
'click here for the administrative interface': 'hier klicken für die Administrationsoberfläche ',
|
||||
'Check to delete': 'Markiere zum l?schen',
|
||||
'Checking for upgrades...': 'Auf Updates ?berpr?fen...',
|
||||
'Clean': 'leeren',
|
||||
'click here for online examples': 'hier klicken f?r online Beispiele',
|
||||
'click here for the administrative interface': 'hier klicken f?r die Administrationsoberfl?che ',
|
||||
'Click row to expand traceback': 'Klicke auf die Zeile f?r Fehlerverfolgung',
|
||||
'click to check for upgrades': 'hier klicken um nach Upgrades zu suchen',
|
||||
'Client IP': 'Client IP',
|
||||
'code': 'code',
|
||||
'collapse/expand all': 'collapse/expand all',
|
||||
'collapse/expand all': 'alles zu- bzw. aufklappen',
|
||||
'Compile': 'kompilieren',
|
||||
'compiled application removed': 'kompilierte Anwendung gelöscht',
|
||||
'compiled application removed': 'kompilierte Anwendung gel?scht',
|
||||
'Controller': 'Controller',
|
||||
'Controllers': 'Controller',
|
||||
'controllers': 'Controllers',
|
||||
'Copyright': 'Urheberrecht',
|
||||
'Count': 'Anzahl',
|
||||
'Create': 'erstellen',
|
||||
'create file with filename:': 'erzeuge Datei mit Dateinamen:',
|
||||
'create new application:': 'erzeuge neue Anwendung:',
|
||||
'Create new simple application': 'Erzeuge neue Anwendung',
|
||||
'created by': 'created by',
|
||||
'created by': 'erstellt von',
|
||||
'crontab': 'crontab',
|
||||
'Current request': 'Aktuelle Anfrage (request)',
|
||||
'Current response': 'Aktuelle Antwort (response)',
|
||||
'Current session': 'Aktuelle Sitzung (session)',
|
||||
'currently running': 'currently running',
|
||||
'currently running': 'aktuell in Betrieb',
|
||||
'currently saved or': 'des derzeit gespeicherten oder',
|
||||
'customize me!': 'pass mich an!',
|
||||
'data uploaded': 'Daten hochgeladen',
|
||||
'Database': 'Datenbank',
|
||||
'database': 'Datenbank',
|
||||
'database %s select': 'Datenbank %s ausgewählt',
|
||||
'database %s select': 'Datenbank %s ausgew?hlt',
|
||||
'database administration': 'Datenbankadministration',
|
||||
'Date and Time': 'Datum und Uhrzeit',
|
||||
'db': 'db',
|
||||
'DB Model': 'DB Modell',
|
||||
'Debug': 'Debug',
|
||||
'defines tables': 'definiere Tabellen',
|
||||
'Delete': 'Löschen',
|
||||
'delete': 'löschen',
|
||||
'delete all checked': 'lösche alle markierten',
|
||||
'delete plugin': 'Plugin löschen',
|
||||
'Delete:': 'Löschen:',
|
||||
'Deploy': 'deploy',
|
||||
'Delete': 'L?schen',
|
||||
'delete': 'l?schen',
|
||||
'delete all checked': 'l?sche alle markierten',
|
||||
'delete plugin': 'Plugin l?schen',
|
||||
'Delete:': 'L?schen:',
|
||||
'Deploy': 'Installieren',
|
||||
'Deploy on Google App Engine': 'Auf Google App Engine installieren',
|
||||
'Deploy to OpenShift': 'Deploy to OpenShift',
|
||||
'Deploy to OpenShift': 'Auf OpenShift installieren',
|
||||
'Description': 'Beschreibung',
|
||||
'design': 'design',
|
||||
'DESIGN': 'design',
|
||||
'Design for': 'Design für',
|
||||
'Design for': 'Design f?r',
|
||||
'Detailed traceback description': 'Detailed traceback description',
|
||||
'direction: ltr': 'direction: ltr',
|
||||
'Disable': 'Disable',
|
||||
'Disable': 'Deaktivieren',
|
||||
'documentation': 'Dokumentation',
|
||||
'done!': 'fertig!',
|
||||
'download layouts': 'download layouts',
|
||||
'Download .w2p': 'Download .w2p',
|
||||
'download layouts': 'Layouts herunterladen',
|
||||
'download plugins': 'download plugins',
|
||||
'E-mail': 'E-mail',
|
||||
'EDIT': 'BEARBEITEN',
|
||||
@@ -129,56 +136,64 @@
|
||||
'Editing file "%s"': 'Bearbeite Datei "%s"',
|
||||
'Editing Language file': 'Sprachdatei bearbeiten',
|
||||
'Enterprise Web Framework': 'Enterprise Web Framework',
|
||||
'Error logs for "%(app)s"': 'Fehlerprotokoll für "%(app)s"',
|
||||
'Error': 'Fehler',
|
||||
'Error logs for "%(app)s"': 'Fehlerprotokoll f?r "%(app)s"',
|
||||
'Error snapshot': 'Error snapshot',
|
||||
'Error ticket': 'Error ticket',
|
||||
'Errors': 'Fehler',
|
||||
'escape': 'escape',
|
||||
'Exception instance attributes': 'Atribute der Ausnahmeinstanz',
|
||||
'Expand Abbreviation': 'Kürzel erweitern',
|
||||
'Expand Abbreviation': 'K?rzel erweitern',
|
||||
'export as csv file': 'Exportieren als CSV-Datei',
|
||||
'exposes': 'stellt zur Verfügung',
|
||||
'exposes': 'stellt zur Verf?gung',
|
||||
'extends': 'erweitert',
|
||||
'failed to reload module': 'neu laden des Moduls fehlgeschlagen',
|
||||
'File': 'Datei',
|
||||
'file "%(filename)s" created': 'Datei "%(filename)s" erstellt',
|
||||
'file "%(filename)s" deleted': 'Datei "%(filename)s" gelöscht',
|
||||
'file "%(filename)s" deleted': 'Datei "%(filename)s" gel?scht',
|
||||
'file "%(filename)s" uploaded': 'Datei "%(filename)s" hochgeladen',
|
||||
'file "%(filename)s" was not deleted': 'Datei "%(filename)s" wurde nicht gelöscht',
|
||||
'file "%(filename)s" was not deleted': 'Datei "%(filename)s" wurde nicht gel?scht',
|
||||
'file "%s" of %s restored': 'Datei "%s" von %s wiederhergestellt',
|
||||
'file changed on disk': 'Datei auf Festplatte geändert',
|
||||
'file changed on disk': 'Datei auf Festplatte ge?ndert',
|
||||
'file does not exist': 'Datei existiert nicht',
|
||||
'file saved on %(time)s': 'Datei gespeichert am %(time)s',
|
||||
'file saved on %s': 'Datei gespeichert auf %s',
|
||||
'filter': 'filter',
|
||||
'First name': 'Vorname',
|
||||
'Frames': 'Frames',
|
||||
'Functions with no doctests will result in [passed] tests.': 'Funktionen ohne doctests erzeugen [passed] in Tests',
|
||||
'Get from URL:': 'Get from URL:',
|
||||
'Git Pull': 'Git Pull',
|
||||
'Git Push': 'Git Push',
|
||||
'Go to Matching Pair': 'gehe zum übereinstimmenden Paar',
|
||||
'Go to Matching Pair': 'gehe zum ?bereinstimmenden Paar',
|
||||
'Goto': 'Goto',
|
||||
'Group ID': 'Gruppen ID',
|
||||
'Hello World': 'Hallo Welt',
|
||||
'Help': 'Hilfe',
|
||||
'Home': 'Home',
|
||||
'htmledit': 'htmledit',
|
||||
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Falls der obere Test eine Fehler-Ticketnummer enthält deutet das auf einen Fehler in der Ausführung des Controllers hin, noch bevor der Doctest ausgeführt werden konnte. Gewöhnlich führen fehlerhafte Einrückungen oder fehlerhafter Code ausserhalb der Funktion zu solchen Fehlern. Ein grüner Titel deutet darauf hin, dass alle Test(wenn sie vorhanden sind) erfolgreich durchlaufen wurden. In diesem Fall werden die Testresultate nicht angezeigt.',
|
||||
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Falls der obere Test eine Fehler-Ticketnummer enth?lt deutet das auf einen Fehler in der Ausf?hrung des Controllers hin, noch bevor der Doctest ausgef?hrt werden konnte. Gew?hnlich f?hren fehlerhafte Einr?ckungen oder fehlerhafter Code ausserhalb der Funktion zu solchen Fehlern. Ein gr?ner Titel deutet darauf hin, dass alle Test(wenn sie vorhanden sind) erfolgreich durchlaufen wurden. In diesem Fall werden die Testresultate nicht angezeigt.',
|
||||
'If you answer "yes", be patient, it may take a while to download': '',
|
||||
'If you answer yes, be patient, it may take a while to download': 'If you answer yes, be patient, it may take a while to download',
|
||||
'Import/Export': 'Importieren/Exportieren',
|
||||
'includes': 'Einfügen',
|
||||
'includes': 'Einf?gen',
|
||||
'Index': 'Index',
|
||||
'index': 'index',
|
||||
'insert new': 'neu einfügen',
|
||||
'insert new %s': 'neu einfügen %s',
|
||||
'insert new': 'neu einf?gen',
|
||||
'insert new %s': 'neu einf?gen %s',
|
||||
'inspect attributes': 'inspect attributes',
|
||||
'Install': 'installieren',
|
||||
'Installed applications': 'Installierte Anwendungen',
|
||||
'internal error': 'interner Fehler',
|
||||
'Internal State': 'interner Status',
|
||||
'Invalid action': 'Ungültige Aktion',
|
||||
'Invalid email': 'Ungültige Email',
|
||||
'invalid password': 'Ungültiges Passwort',
|
||||
'Invalid Query': 'Ungültige Abfrage',
|
||||
'invalid request': 'ungültige Anfrage',
|
||||
'invalid ticket': 'ungültiges Ticket',
|
||||
'Invalid action': 'Ung?ltige Aktion',
|
||||
'Invalid email': 'Ung?ltige Email',
|
||||
'invalid password': 'Ung?ltiges Passwort',
|
||||
'Invalid Query': 'Ung?ltige Abfrage',
|
||||
'invalid request': 'ung?ltige Anfrage',
|
||||
'invalid ticket': 'ung?ltiges Ticket',
|
||||
'Key bindings': 'Tastenbelegungen',
|
||||
'Key bindings for ZenConding Plugin': 'Tastenbelegungen für das ZenConding Plugin',
|
||||
'Key bindings for ZenConding Plugin': 'Tastenbelegungen f?r das ZenConding Plugin',
|
||||
'language file "%(filename)s" created/updated': 'Sprachdatei "%(filename)s" erstellt/aktualisiert',
|
||||
'Language files (static strings) updated': 'Sprachdatei (statisch Strings) aktualisiert',
|
||||
'languages': 'Sprachen',
|
||||
@@ -187,8 +202,9 @@
|
||||
'Last name': 'Nachname',
|
||||
'Last saved on:': 'Zuletzt gespeichert am:',
|
||||
'Layout': 'Layout',
|
||||
'License for': 'Lizenz für',
|
||||
'License for': 'Lizenz f?r',
|
||||
'loading...': 'lade...',
|
||||
'locals': 'locals',
|
||||
'located in the file': 'located in Datei',
|
||||
'Login': 'Anmelden',
|
||||
'login': 'anmelden',
|
||||
@@ -196,33 +212,37 @@
|
||||
'Logout': 'abmelden',
|
||||
'Lost Password': 'Passwort vergessen',
|
||||
'lost password?': 'Passwort vergessen?',
|
||||
'Main Menu': 'Menú principal',
|
||||
'Main Menu': 'Men? principal',
|
||||
'Manage': 'Verwalten',
|
||||
'Match Pair': 'Paare finden',
|
||||
'Menu Model': 'Menü Modell',
|
||||
'Menu Model': 'Men? Modell',
|
||||
'merge': 'verbinden',
|
||||
'Merge Lines': 'Zeilen zusammenfügen',
|
||||
'Merge Lines': 'Zeilen zusammenf?gen',
|
||||
'Models': 'Modelle',
|
||||
'models': 'Modelle',
|
||||
'Modules': 'Module',
|
||||
'modules': 'Module',
|
||||
'Name': 'Name',
|
||||
'new application "%s" created': 'neue Anwendung "%s" erzeugt',
|
||||
'New application wizard': 'New application wizard',
|
||||
'New application wizard': 'Neue Anwendung per Assistent',
|
||||
'New Record': 'Neuer Datensatz',
|
||||
'new record inserted': 'neuer Datensatz eingefügt',
|
||||
'New simple application': 'New simple application',
|
||||
'next 100 rows': 'nächsten 100 Zeilen',
|
||||
'Next Edit Point': 'nächster Bearbeitungsschritt',
|
||||
'new record inserted': 'neuer Datensatz eingef?gt',
|
||||
'New simple application': 'Neue einfache Anwendung',
|
||||
'next 100 rows': 'n?chsten 100 Zeilen',
|
||||
'Next Edit Point': 'n?chster Bearbeitungsschritt',
|
||||
'NO': 'NEIN',
|
||||
'No databases in this application': 'Keine Datenbank in dieser Anwendung',
|
||||
'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder',
|
||||
'Or Get from URL:': 'oder hole es von folgender URL:',
|
||||
'or import from csv file': 'oder importieren von cvs Datei',
|
||||
'or provide app url:': 'oder geben Sie eine Anwendungs-URL an:',
|
||||
'or provide application url:': 'oder geben Sie eine Anwendungs-URL an:',
|
||||
'Origin': 'Herkunft',
|
||||
'Original/Translation': 'Original/Übersetzung',
|
||||
'Overwrite installed app': 'installierte Anwendungen überschreiben',
|
||||
'Original/Translation': 'Original/?bersetzung',
|
||||
'Overwrite installed app': 'installierte Anwendungen ?berschreiben',
|
||||
'Pack all': 'verpacke alles',
|
||||
'Pack compiled': 'Verpacke kompiliert',
|
||||
'Pack custom': 'Verpacke individuell',
|
||||
'pack plugin': 'Plugin verpacken',
|
||||
'Password': 'Passwort',
|
||||
'Peeking at file': 'Dateiansicht',
|
||||
@@ -230,9 +250,10 @@
|
||||
'Plugin "%s" in application': 'Plugin "%s" in Anwendung',
|
||||
'plugins': 'plugins',
|
||||
'Plugins': 'Plugins',
|
||||
'Powered by': 'Unterstützt von',
|
||||
'Powered by': 'Unterst?tzt von',
|
||||
'previous 100 rows': 'vorherige 100 zeilen',
|
||||
'Previous Edit Point': 'vorheriger Bearbeitungsschritt',
|
||||
'Project Progress': 'Projekt Fortschritt',
|
||||
'Query:': 'Abfrage:',
|
||||
'record': 'Datensatz',
|
||||
'record does not exist': 'Datensatz existiert nicht',
|
||||
@@ -240,33 +261,38 @@
|
||||
'Record ID': 'Datensatz ID',
|
||||
'register': 'Registrierung',
|
||||
'Register': 'registrieren',
|
||||
'Registration key': 'Registrierungsschlüssel',
|
||||
'Reload routes': 'Reload routes',
|
||||
'Remove compiled': 'kompilat gelöscht',
|
||||
'Reset Password key': 'Passwortschlüssel zurücksetzen',
|
||||
'Registration key': 'Registrierungsschl?ssel',
|
||||
'reload': 'Neu laden',
|
||||
'Reload routes': 'Routen neu laden',
|
||||
'Remove compiled': 'Bytecode l?schen',
|
||||
'request': 'request',
|
||||
'Reset Password key': 'Passwortschl?ssel zur?cksetzen',
|
||||
'Resolve Conflict file': 'bereinige Konflikt-Datei',
|
||||
'response': 'Antwort',
|
||||
'restore': 'wiederherstellen',
|
||||
'revert': 'zurückkehren',
|
||||
'revert': 'zur?ckkehren',
|
||||
'Role': 'Rolle',
|
||||
'Rows in table': 'Zeilen in Tabelle',
|
||||
'Rows selected': 'Zeilen ausgewählt',
|
||||
'Running on %s': 'Running on %s',
|
||||
'Rows selected': 'Zeilen ausgew?hlt',
|
||||
'Running on %s': 'L?uft auf %s',
|
||||
'save': 'sichern',
|
||||
'Save via Ajax': 'via Ajax sichern',
|
||||
'Saved file hash:': 'Gespeicherter Datei-Hash:',
|
||||
'selected': 'ausgewählt(e)',
|
||||
'session expired': 'Sitzung Abgelaufen',
|
||||
'Select Files to Package': 'Dateien zum Paketieren w?hlen',
|
||||
'selected': 'ausgew?hlt(e)',
|
||||
'session': 'Sitzung',
|
||||
'session expired': 'Sitzung abgelaufen',
|
||||
'shell': 'shell',
|
||||
'Site': 'Seite',
|
||||
'some files could not be removed': 'einige Dateien konnten nicht gelöscht werden',
|
||||
'Start wizard': 'start wizard',
|
||||
'some files could not be removed': 'einige Dateien konnten nicht gel?scht werden',
|
||||
'Start wizard': 'Assistent starten',
|
||||
'state': 'Status',
|
||||
'static': 'statische Dateien',
|
||||
'Static files': 'statische Dateien',
|
||||
'Stylesheet': 'Stylesheet',
|
||||
'Submit': 'Submit',
|
||||
'submit': 'Absenden',
|
||||
'Sure you want to delete this object?': 'Wollen Sie das Objekt wirklich löschen?',
|
||||
'Sure you want to delete this object?': 'Wollen Sie das Objekt wirklich l?schen?',
|
||||
'table': 'Tabelle',
|
||||
'Table name': 'Tabellen Name',
|
||||
'test': 'Test',
|
||||
@@ -276,42 +302,45 @@
|
||||
'test_try': 'test_try',
|
||||
'Testing application': 'Teste die Anwendung',
|
||||
'Testing controller': 'teste Controller',
|
||||
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'Die "query" ist eine Bedingung wie "db.table1.field1 == \'Wert\'". Etwas wie "db.table1.field1 db.table2.field2 ==" führt zu einem SQL JOIN.',
|
||||
'the application logic, each URL path is mapped in one exposed function in the controller': 'Die Logik der Anwendung, jeder URL-Pfad wird auf eine Funktion abgebildet die der Controller zur Verfügung stellt',
|
||||
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'Die "query" ist eine Bedingung wie "db.table1.field1 == \'Wert\'". Etwas wie "db.table1.field1 db.table2.field2 ==" f?hrt zu einem SQL JOIN.',
|
||||
'the application logic, each URL path is mapped in one exposed function in the controller': 'Die Logik der Anwendung, jeder URL-Pfad wird auf eine Funktion abgebildet die der Controller zur Verf?gung stellt',
|
||||
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
|
||||
'the data representation, define database tables and sets': 'Die Datenrepräsentation definiert Mengen von Tabellen und Datenbanken ',
|
||||
'the data representation, define database tables and sets': 'Die Datenrepr?sentation definiert Mengen von Tabellen und Datenbanken ',
|
||||
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
|
||||
'The output of the file is a dictionary that was rendered by the view': 'The output of the file is a dictionary that was rendered by the view',
|
||||
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
|
||||
'the presentations layer, views are also known as templates': 'Die Präsentationsschicht, Views sind auch bekannt als Vorlagen/Templates',
|
||||
'the presentations layer, views are also known as templates': 'Die Pr?sentationsschicht, Views sind auch bekannt als Vorlagen/Templates',
|
||||
'There are no controllers': 'Keine Controller vorhanden',
|
||||
'There are no models': 'Keine Modelle vorhanden',
|
||||
'There are no modules': 'Keine Module vorhanden',
|
||||
'There are no plugins': 'There are no plugins',
|
||||
'There are no static files': 'Keine statischen Dateien vorhanden',
|
||||
'There are no translators, only default language is supported': 'Keine Übersetzungen vorhanden, nur die voreingestellte Sprache wird unterstützt',
|
||||
'There are no translators, only default language is supported': 'Keine ?bersetzungen vorhanden, nur die voreingestellte Sprache wird unterst?tzt',
|
||||
'There are no views': 'Keine Views vorhanden',
|
||||
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
|
||||
'these files are served without processing, your images go here': 'Diese Dateien werden ohne Verarbeitung ausgeliefert. Beispielsweise Bilder kommen hier hin.',
|
||||
'This is a copy of the scaffolding application': 'Dies ist eine Kopie einer Grundgerüst-Anwendung',
|
||||
'This is a copy of the scaffolding application': 'Dies ist eine Kopie einer Grundger?st-Anwendung',
|
||||
'This is the %(filename)s template': 'Dies ist das Template %(filename)s',
|
||||
'Ticket': 'Ticket',
|
||||
'Timestamp': 'Timestamp',
|
||||
'Ticket ID': 'Ticket ID',
|
||||
'Timestamp': 'Zeitstempel',
|
||||
'TM': 'TM',
|
||||
'to previous version.': 'zu einer früheren Version.',
|
||||
'to previous version.': 'zu einer fr?heren Version.',
|
||||
'To create a plugin, name a file/folder plugin_[name]': 'Um ein Plugin zu erstellen benennen Sie eine(n) Datei/Ordner plugin_[Name]',
|
||||
'translation strings for the application': 'Übersetzungs-Strings für die Anwendung',
|
||||
'Translation strings for the application': 'Translation strings for the application',
|
||||
'Traceback': 'Traceback',
|
||||
'translation strings for the application': '?bersetzungs-Strings f?r die Anwendung',
|
||||
'Translation strings for the application': '?bersetzungs-Strings f?r die Anwendung',
|
||||
'try': 'versuche',
|
||||
'try something like': 'versuche so etwas wie',
|
||||
'Unable to check for upgrades': 'überprüfen von Upgrades nicht möglich',
|
||||
'unable to create application "%s"': 'erzeugen von Anwendung "%s" nicht möglich',
|
||||
'unable to delete file "%(filename)s"': 'löschen von Datein "%(filename)s" nicht möglich',
|
||||
'Unable to download': 'herunterladen nicht möglich',
|
||||
'Unable to download app': 'herunterladen der Anwendung nicht möglich',
|
||||
'unable to parse csv file': 'analysieren der cvs Datei nicht möglich',
|
||||
'unable to uninstall "%s"': 'deinstallieren von "%s" nicht möglich',
|
||||
'uncheck all': 'alles demarkieren',
|
||||
'Try the mobile interface': 'Try the mobile interface',
|
||||
'Unable to check for upgrades': '?berpr?fen von Upgrades nicht m?glich',
|
||||
'unable to create application "%s"': 'erzeugen von Anwendung "%s" nicht m?glich',
|
||||
'unable to delete file "%(filename)s"': 'l?schen von Datein "%(filename)s" nicht m?glich',
|
||||
'Unable to download': 'herunterladen nicht m?glich',
|
||||
'Unable to download app': 'herunterladen der Anwendung nicht m?glich',
|
||||
'unable to parse csv file': 'analysieren der cvs Datei nicht m?glich',
|
||||
'unable to uninstall "%s"': 'deinstallieren von "%s" nicht m?glich',
|
||||
'uncheck all': 'Selektionen entfernen',
|
||||
'Uninstall': 'deinstallieren',
|
||||
'update': 'aktualisieren',
|
||||
'update all languages': 'aktualisiere alle Sprachen',
|
||||
@@ -319,33 +348,35 @@
|
||||
'upgrade web2py now': 'jetzt web2py upgraden',
|
||||
'upload': 'upload',
|
||||
'Upload & install packed application': 'Verpackte Anwendung hochladen und installieren',
|
||||
'Upload a package:': 'Upload a package:',
|
||||
'Upload and install packed application': 'Upload and install packed application',
|
||||
'Upload a package:': 'Ein Packet hochladen:',
|
||||
'Upload and install packed application': 'Verpackte Anwendung hochladen und installieren',
|
||||
'upload application:': 'lade Anwendung hoch:',
|
||||
'Upload existing application': 'lade existierende Anwendung hoch',
|
||||
'upload file:': 'lade Datei hoch:',
|
||||
'upload plugin file:': 'Plugin-Datei hochladen:',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Benutze (...)&(...) für AND, (...)|(...) für OR, und ~(...) für NOT, um komplexe Abfragen zu erstellen.',
|
||||
'Use an url:': 'Use an url:',
|
||||
'user': 'user',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Benutze (...)&(...) f?r AND, (...)|(...) f?r OR, und ~(...) f?r NOT, um komplexe Abfragen zu erstellen.',
|
||||
'Use an url:': 'Verwende URL:',
|
||||
'user': 'Nutzer',
|
||||
'User ID': 'Benutzer ID',
|
||||
'variables': 'variables',
|
||||
'variables': 'Variablen',
|
||||
'Version': 'Version',
|
||||
'Version %s.%s.%s (%s) %s': 'Version %s.%s.%s (%s) %s',
|
||||
'versioning': 'Versionierung',
|
||||
'View': 'View',
|
||||
'view': 'View',
|
||||
'Views': 'Views',
|
||||
'views': 'Views',
|
||||
'Versioning': 'Versionierung',
|
||||
'View': 'Ansicht',
|
||||
'view': 'Ansicht',
|
||||
'Views': 'Ansichten',
|
||||
'views': 'Ansichten',
|
||||
'Web Framework': 'Web Framework',
|
||||
'web2py is up to date': 'web2py ist auf dem neuesten Stand',
|
||||
'web2py Recent Tweets': 'neuste Tweets von web2py',
|
||||
'Welcome %s': 'Willkommen %s',
|
||||
'Welcome to web2py': 'Willkommen zu web2py',
|
||||
'Which called the function': 'Which called the function',
|
||||
'Wrap with Abbreviation': 'mit Kürzel einhüllen',
|
||||
'Which called the function': 'welche die Funktion aufrief',
|
||||
'Wrap with Abbreviation': 'mit K?rzel einh?llen',
|
||||
'xml': 'xml',
|
||||
'YES': 'JA',
|
||||
'You are successfully running web2py': 'web2by wird erfolgreich ausgeführt',
|
||||
'You can modify this application and adapt it to your needs': 'Sie können diese Anwendung verändern und Ihren Bedürfnissen anpassen',
|
||||
'You are successfully running web2py': 'web2by wird erfolgreich ausgef?hrt',
|
||||
'You can modify this application and adapt it to your needs': 'Sie k?nnen diese Anwendung ver?ndern und Ihren Bed?rfnissen anpassen',
|
||||
'You visited the url': 'Sie besuchten die URL',
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" est une expression en option tels que "field1 = \'newvalue\'". Vous ne pouvez pas mettre à jour ou supprimer les résultats d\'une jointure "a JOIN"',
|
||||
'%Y-%m-%d': '%d-%m-%Y',
|
||||
'%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S',
|
||||
'%s %%{row} deleted': 'lignes %s supprimé',
|
||||
'%s %%{row} updated': 'lignes %s mis à jour',
|
||||
'(requires internet access)': '(requires internet access)',
|
||||
'%s %%{row} deleted': 'lignes %s supprimées',
|
||||
'%s %%{row} updated': 'lignes %s mises à jour',
|
||||
'(requires internet access)': '(nécessite un accès Internet)',
|
||||
'(something like "it-it")': '(quelque chose comme "it-it") ',
|
||||
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
|
||||
'@markmin\x01Searching: **%s** %%{file}': 'Cherche: **%s** fichiers',
|
||||
'A new version of web2py is available: %s': 'Une nouvelle version de web2py est disponible: %s ',
|
||||
'A new version of web2py is available: Version 1.68.2 (2009-10-21 09:59:29)\n': 'Une nouvelle version de web2py est disponible: Version 1.68.2 (2009-10-21 09:59:29)\r\n',
|
||||
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATTENTION: nécessite une connexion sécurisée (HTTPS) ou être en localhost. ',
|
||||
@@ -17,59 +17,59 @@
|
||||
'ATTENTION: you cannot edit the running application!': "ATTENTION: vous ne pouvez pas modifier l'application qui tourne!",
|
||||
'About': 'à propos',
|
||||
'About application': "A propos de l'application",
|
||||
'Additional code for your application': 'Additional code for your application',
|
||||
'Additional code for your application': 'Code additionnel pour votre application',
|
||||
'Admin is disabled because insecure channel': 'Admin est désactivé parce que canal non sécurisé',
|
||||
'Admin language': 'Admin language',
|
||||
'Admin language': "Language de l'admin",
|
||||
'Administrator Password:': 'Mot de passe Administrateur:',
|
||||
'Application name:': 'Application name:',
|
||||
'Are you sure you want to delete file "%s"?': 'Etes-vous sûr de vouloir supprimer le fichier «%s»?',
|
||||
'Are you sure you want to delete plugin "%s"?': 'Etes-vous sûr de vouloir effacer le plugin "%s"?',
|
||||
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
|
||||
'Application name:': "Nom de l'application:",
|
||||
'Are you sure you want to delete file "%s"?': 'Êtes-vous sûr de vouloir supprimer le fichier «%s»?',
|
||||
'Are you sure you want to delete plugin "%s"?': 'Êtes-vous sûr de vouloir supprimer le plugin "%s"?',
|
||||
'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
|
||||
'Are you sure you want to uninstall application "%s"?': "Êtes-vous sûr de vouloir désinstaller l'application «%s»?",
|
||||
'Are you sure you want to upgrade web2py now?': 'Are you sure you want to upgrade web2py now?',
|
||||
'Are you sure you want to upgrade web2py now?': 'Êtes-vous sûr de vouloir mettre à jour web2py maintenant?',
|
||||
'Available databases and tables': 'Bases de données et tables disponible',
|
||||
'Cannot be empty': 'Ne peut pas être vide',
|
||||
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Ne peut pas compiler: il y a des erreurs dans votre application. corriger les erreurs et essayez à nouveau.',
|
||||
'Cannot compile: there are errors in your app:': 'Cannot compile: there are errors in your app:',
|
||||
'Change admin password': 'change admin password',
|
||||
'Check for upgrades': 'check for upgrades',
|
||||
'Cannot compile: there are errors in your app:': 'Ne peut pas compiler: il y a des erreurs dans votre application:',
|
||||
'Change admin password': 'Changer le mot de passe admin',
|
||||
'Check for upgrades': 'Vérifier les mises à jour',
|
||||
'Check to delete': 'Cocher pour supprimer',
|
||||
'Checking for upgrades...': 'Vérification des mises à jour ... ',
|
||||
'Clean': 'nettoyer',
|
||||
'Compile': 'compiler',
|
||||
'Controllers': 'Contrôleurs',
|
||||
'Create': 'create',
|
||||
'Create': 'Créer',
|
||||
'Create new simple application': 'Créer une nouvelle application',
|
||||
'Current request': 'Requête actuel',
|
||||
'Current request': 'Requête actuelle',
|
||||
'Current response': 'Réponse actuelle',
|
||||
'Current session': 'Session en cours',
|
||||
'Date and Time': 'Date et heure',
|
||||
'Delete': 'Supprimer',
|
||||
'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)',
|
||||
'Delete this file (you will be asked to confirm deletion)': 'Supprimer ce fichier (on vous demandera de confirmer la suppression)',
|
||||
'Delete:': 'Supprimer:',
|
||||
'Deploy': 'deploy',
|
||||
'Deploy': 'Déployer',
|
||||
'Deploy on Google App Engine': 'Déployer sur Google App Engine',
|
||||
'EDIT': 'MODIFIER',
|
||||
'Edit': 'modifier',
|
||||
'Edit application': "Modifier l'application",
|
||||
'Edit current record': 'Modifier cet entrée',
|
||||
'Edit current record': 'Modifier cette entrée',
|
||||
'Editing Language file': 'Modifier le fichier de langue',
|
||||
'Editing file': 'Modifier le fichier',
|
||||
'Editing file "%s"': 'Modifier le fichier "% s" ',
|
||||
'Enterprise Web Framework': 'Enterprise Web Framework',
|
||||
'Error logs for "%(app)s"': 'Journal d\'erreurs pour "%(app)s"',
|
||||
'Errors': 'erreurs',
|
||||
'Exception instance attributes': 'Exception instance attributes',
|
||||
'Functions with no doctests will result in [passed] tests.': 'Des fonctions sans doctests entraînera tests [passed] .',
|
||||
'Exception instance attributes': "Attributs d'instance Exception",
|
||||
'Functions with no doctests will result in [passed] tests.': 'Des fonctions sans doctests entraîneront des tests [passed] .',
|
||||
'Help': 'aide',
|
||||
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': "Si le rapport ci-dessus contient un numéro de ticket, cela indique une défaillance dans l'exécution du contrôleur, avant toute tentative d'exécuter les doctests. Cela est généralement dû à une erreur d'indentation ou une erreur à l'extérieur du code de la fonction.\r\nUn titre verte indique que tous les tests (si définie) passed. Dans ce cas, les résultats des essais ne sont pas affichées.",
|
||||
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': "Si le rapport ci-dessus contient un numéro de ticket, cela indique une défaillance dans l'exécution du contrôleur, avant toute tentative d'exécuter les doctests. Cela est généralement dû à une erreur d'indentation ou une erreur à l'extérieur du code de la fonction.\r\nUn titre vert indique que tous les tests (si définis) sont passés. Dans ce cas, les résultats des essais ne sont pas affichées.",
|
||||
'Import/Export': 'Importer/Exporter',
|
||||
'Install': 'install',
|
||||
'Installed applications': 'Les applications installées',
|
||||
'Install': 'Installer',
|
||||
'Installed applications': 'Applications installées',
|
||||
'Internal State': 'État Interne',
|
||||
'Invalid Query': 'Requête non valide',
|
||||
'Invalid action': 'Action non valide',
|
||||
'Language files (static strings) updated': 'Fichiers de langue (static strings) Mise à jour ',
|
||||
'Language files (static strings) updated': 'Fichiers de langue (chaînes statiques) mis à jour ',
|
||||
'Languages': 'Langues',
|
||||
'Last saved on:': 'Dernière sauvegarde le:',
|
||||
'License for': 'Licence pour',
|
||||
@@ -79,13 +79,13 @@
|
||||
'Models': 'Modèles',
|
||||
'Modules': 'Modules',
|
||||
'NO': 'NON',
|
||||
'New Record': 'Nouvel Entrée',
|
||||
'New application wizard': 'New application wizard',
|
||||
'New simple application': 'New simple application',
|
||||
'New Record': 'Nouvelle Entrée',
|
||||
'New application wizard': 'Assistant nouvelle application',
|
||||
'New simple application': 'Nouvelle application simple',
|
||||
'No databases in this application': 'Aucune base de données dans cette application',
|
||||
'Original/Translation': 'Original / Traduction',
|
||||
'Overwrite installed app': 'overwrite installed app',
|
||||
'PAM authenticated user, cannot change password here': 'PAM authenticated user, cannot change password here',
|
||||
'Overwrite installed app': "Écraser l'application installée",
|
||||
'PAM authenticated user, cannot change password here': 'Utilisateur authentifié par PAM, vous ne pouvez pas changer le mot de passe ici',
|
||||
'Pack all': 'tout empaqueter',
|
||||
'Pack compiled': 'paquet compilé',
|
||||
'Peeking at file': 'Jeter un oeil au fichier',
|
||||
@@ -97,101 +97,101 @@
|
||||
'Resolve Conflict file': 'Résoudre les conflits de fichiers',
|
||||
'Rows in table': 'Lignes de la table',
|
||||
'Rows selected': 'Lignes sélectionnées',
|
||||
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')",
|
||||
'Save': 'Save',
|
||||
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Lancer les tests dans ce fichier (pour lancer tous les fichiers, vous pouvez également utiliser le bouton nommé 'test')",
|
||||
'Save': 'Enregistrer',
|
||||
'Saved file hash:': 'Hash du Fichier enregistré:',
|
||||
'Site': 'site',
|
||||
'Start wizard': 'start wizard',
|
||||
'Site': 'Site',
|
||||
'Start wizard': "Démarrer l'assistant",
|
||||
'Static files': 'Fichiers statiques',
|
||||
'Sure you want to delete this object?': 'Vous êtes sûr de vouloir supprimer cet objet? ',
|
||||
'TM': 'MD',
|
||||
'Testing application': "Test de l'application",
|
||||
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "requête" est une condition comme "db.table1.field1==\'value\'". Quelque chose comme "db.table1.field1==db.table2.field2" aboutit à un JOIN SQL.',
|
||||
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
|
||||
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
|
||||
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
|
||||
'There are no controllers': "Il n'existe pas de contrôleurs",
|
||||
'There are no models': "Il n'existe pas de modèles",
|
||||
'There are no modules': "Il n'existe pas de modules",
|
||||
'There are no plugins': 'There are no plugins',
|
||||
'There are no static files': "Il n'existe pas de fichiers statiques",
|
||||
'There are no translators, only default language is supported': "Il n'y a pas de traducteurs, est prise en charge uniquement la langue par défaut",
|
||||
'There are no views': "Il n'existe pas de vues",
|
||||
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
|
||||
'The application logic, each URL path is mapped in one exposed function in the controller': "La logique de l'application, chaque chemin d'URL est mappé avec une fonction exposée dans le contrôleur",
|
||||
'The data representation, define database tables and sets': 'La représentation des données, définir les tables et ensembles de la base de données',
|
||||
'The presentations layer, views are also known as templates': "Les couches de présentation, les vues sont également appelées modples",
|
||||
'There are no controllers': "Il n'y a pas de contrôleurs",
|
||||
'There are no models': "Il n'y a pas de modèles",
|
||||
'There are no modules': "Il n'y a pas de modules",
|
||||
'There are no plugins': "Il n'y a pas de plugins",
|
||||
'There are no static files': "Il n'y a pas de fichiers statiques",
|
||||
'There are no translators, only default language is supported': "Il n'y a pas de traducteurs, seule la langue par défaut est prise en charge",
|
||||
'There are no views': "Il n'y a pas de vues",
|
||||
'These files are served without processing, your images go here': 'Ces fichiers sont renvoyés sans traitement, vos images viennent ici',
|
||||
'This is the %(filename)s template': 'Ceci est le modèle %(filename)s ',
|
||||
'Ticket': 'Ticket',
|
||||
'To create a plugin, name a file/folder plugin_[name]': 'Pour créer un plugin, créer un fichier /dossier plugin_[nom]',
|
||||
'Translation strings for the application': 'Translation strings for the application',
|
||||
'Unable to check for upgrades': 'Impossible de vérifier les mises à niveau',
|
||||
'Translation strings for the application': "Chaînes de traduction pour l'application",
|
||||
'Unable to check for upgrades': 'Impossible de vérifier les mises à jour',
|
||||
'Unable to download': 'Impossible de télécharger',
|
||||
'Unable to download app': 'Impossible de télécharger app',
|
||||
'Unable to download app because:': 'Unable to download app because:',
|
||||
'Unable to download because': 'Unable to download because',
|
||||
'Unable to download app': "Impossible de télécharger l'app",
|
||||
'Unable to download app because:': "Impossible de télécharger l'app car:",
|
||||
'Unable to download because': 'Impossible de télécharger car',
|
||||
'Uninstall': 'désinstaller',
|
||||
'Update:': 'Mise à jour:',
|
||||
'Upload & install packed application': 'Upload & install packed application',
|
||||
'Upload a package:': 'Upload a package:',
|
||||
'Upload existing application': 'charger une application existante',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Utilisez (...)&(...) pour AND, (...)|(...) pour OR, et ~(...) pour NOT et construire des requêtes plus complexes. ',
|
||||
'Use an url:': 'Use an url:',
|
||||
'Upload & install packed application': "Charger & installer l'application empaquetée",
|
||||
'Upload a package:': 'Charger un paquet:',
|
||||
'Upload existing application': 'Charger une application existante',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Utilisez (...)&(...) pour AND, (...)|(...) pour OR, et ~(...) pour NOT afin de construire des requêtes plus complexes. ',
|
||||
'Use an url:': 'Utiliser une url:',
|
||||
'Version': 'Version',
|
||||
'Views': 'Vues',
|
||||
'Web Framework': 'Web Framework',
|
||||
'Web Framework': 'Framework Web',
|
||||
'YES': 'OUI',
|
||||
'additional code for your application': 'code supplémentaire pour votre application',
|
||||
'admin disabled because no admin password': 'admin désactivé car aucun mot de passe admin',
|
||||
'admin disabled because not supported on google app engine': 'admin désactivé car non pris en charge sur Google Apps engine',
|
||||
'admin disabled because unable to access password file': "admin désactivé car incapable d'accéder au fichier mot de passe",
|
||||
'administrative interface': 'administrative interface',
|
||||
'admin disabled because no admin password': 'admin désactivée car aucun mot de passe admin',
|
||||
'admin disabled because not supported on google app engine': 'admin désactivée car non prise en charge sur Google Apps engine',
|
||||
'admin disabled because unable to access password file': "admin désactivée car incapable d'accéder au fichier mot de passe",
|
||||
'administrative interface': "interface d'administration",
|
||||
'and rename it (required):': 'et renommez-la (obligatoire):',
|
||||
'and rename it:': 'et renommez-le:',
|
||||
'appadmin': 'appadmin',
|
||||
'appadmin is disabled because insecure channel': 'appadmin est désactivé parce que canal non sécurisé',
|
||||
'application "%s" uninstalled': 'application "%s" désinstallé',
|
||||
'application %(appname)s installed with md5sum: %(digest)s': 'application %(appname)s installed with md5sum: %(digest)s',
|
||||
'application "%s" uninstalled': 'application "%s" désinstallée',
|
||||
'application %(appname)s installed with md5sum: %(digest)s': 'application %(appname)s installée avec md5sum: %(digest)s',
|
||||
'application compiled': 'application compilée',
|
||||
'application is compiled and cannot be designed': "l'application est compilée et ne peut être désigné",
|
||||
'application is compiled and cannot be designed': "l'application est compilée et ne peut être modifiée",
|
||||
'arguments': 'arguments',
|
||||
'back': 'retour',
|
||||
'cache': 'cache',
|
||||
'cache, errors and sessions cleaned': 'cache, erreurs et sessions nettoyé',
|
||||
'cannot create file': 'ne peu pas créer de fichier',
|
||||
'cannot upload file "%(filename)s"': 'ne peu pas charger le fichier "%(filename)s"',
|
||||
'check all': 'tous vérifier ',
|
||||
'click to check for upgrades': 'Cliquez pour vérifier les mises à niveau',
|
||||
'cache, errors and sessions cleaned': 'cache, erreurs et sessions nettoyés',
|
||||
'cannot create file': 'ne peut pas créer de fichier',
|
||||
'cannot upload file "%(filename)s"': 'ne peut pas charger le fichier "%(filename)s"',
|
||||
'check all': 'tout vérifier ',
|
||||
'click to check for upgrades': 'Cliquez pour vérifier les mises jour',
|
||||
'code': 'code',
|
||||
'collapse/expand all': 'collapse/expand all',
|
||||
'compiled application removed': 'application compilée enlevé',
|
||||
'collapse/expand all': 'tout réduire/agrandir',
|
||||
'compiled application removed': 'application compilée enlevée',
|
||||
'controllers': 'contrôleurs',
|
||||
'create file with filename:': 'créer un fichier avec nom de fichier:',
|
||||
'create new application:': 'créer une nouvelle application:',
|
||||
'created by': 'créé par',
|
||||
'crontab': 'crontab',
|
||||
'currently running': 'currently running',
|
||||
'currently saved or': 'actuellement enregistrés ou',
|
||||
'currently running': 'tourne actuellement',
|
||||
'currently saved or': 'actuellement enregistré ou',
|
||||
'data uploaded': 'données chargées',
|
||||
'database': 'base de données',
|
||||
'database %s select': 'base de données %s sélectionner',
|
||||
'database administration': 'administration base de données',
|
||||
'db': 'db',
|
||||
'db': 'bdd',
|
||||
'defines tables': 'définit les tables',
|
||||
'delete': 'supprimer',
|
||||
'delete all checked': 'supprimer tout ce qui est cocher',
|
||||
'delete plugin': ' supprimer plugin',
|
||||
'delete all checked': 'supprimer tout ce qui est coché',
|
||||
'delete plugin': ' supprimer le plugin',
|
||||
'design': 'conception',
|
||||
'direction: ltr': 'direction: ltr',
|
||||
'docs': 'docs',
|
||||
'done!': 'fait!',
|
||||
'download layouts': 'download layouts',
|
||||
'download plugins': 'download plugins',
|
||||
'download layouts': 'télécharger layouts',
|
||||
'download plugins': 'télécharger plugins',
|
||||
'edit controller': 'modifier contrôleur',
|
||||
'edit views:': 'edit views:',
|
||||
'export as csv file': 'exportation au format CSV',
|
||||
'edit views:': 'modifier vues:',
|
||||
'export as csv file': 'export au format CSV',
|
||||
'exposes': 'expose',
|
||||
'exposes:': 'exposes:',
|
||||
'exposes:': 'expose:',
|
||||
'extends': 'étend',
|
||||
'failed to reload module': 'impossible de recharger le module',
|
||||
'failed to reload module because:': 'failed to reload module because:',
|
||||
'failed to reload module because:': 'impossible de recharger le module car:',
|
||||
'file "%(filename)s" created': 'fichier "%(filename)s" créé',
|
||||
'file "%(filename)s" deleted': 'fichier "%(filename)s" supprimé',
|
||||
'file "%(filename)s" uploaded': 'fichier "%(filename)s" chargé',
|
||||
@@ -200,7 +200,7 @@
|
||||
'file does not exist': "fichier n'existe pas",
|
||||
'file saved on %(time)s': 'fichier enregistré le %(time)s',
|
||||
'file saved on %s': 'fichier enregistré le %s',
|
||||
'filter': 'filter',
|
||||
'filter': 'filtre',
|
||||
'htmledit': 'edition html',
|
||||
'includes': 'inclus',
|
||||
'index': 'index',
|
||||
@@ -219,14 +219,14 @@
|
||||
'modules': 'modules',
|
||||
'new application "%s" created': 'nouvelle application "%s" créée',
|
||||
'new plugin installed': 'nouveau plugin installé',
|
||||
'new record inserted': 'nouvelle entrée inséré',
|
||||
'new record inserted': 'nouvelle entrée insérée',
|
||||
'next 100 rows': '100 lignes suivantes',
|
||||
'no match': 'no match',
|
||||
'no match': 'aucune correspondance',
|
||||
'or import from csv file': 'ou importer depuis un fichier CSV ',
|
||||
'or provide app url:': 'or provide app url:',
|
||||
'or provide app url:': "ou fournir l'URL de l'app:",
|
||||
'or provide application url:': "ou fournir l'URL de l'application:",
|
||||
'pack plugin': 'paquet plugin',
|
||||
'password changed': 'password changed',
|
||||
'password changed': 'mot de passe modifié',
|
||||
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" supprimé',
|
||||
'plugins': 'plugins',
|
||||
'previous 100 rows': '100 lignes précédentes',
|
||||
@@ -245,9 +245,9 @@
|
||||
'submit': 'envoyer',
|
||||
'table': 'table',
|
||||
'test': 'tester',
|
||||
'the application logic, each URL path is mapped in one exposed function in the controller': "la logique de l'application, chaque route URL est mappé dans une fonction exposée dans le contrôleur",
|
||||
'the data representation, define database tables and sets': 'la représentation des données, défini les tables de bases de données et sets',
|
||||
'the presentations layer, views are also known as templates': 'la couche des présentations, les vues sont également connus en tant que modèles',
|
||||
'the application logic, each URL path is mapped in one exposed function in the controller': "la logique de l'application, chaque chemin d'URL est mappé dans une fonction exposée dans le contrôleur",
|
||||
'the data representation, define database tables and sets': 'La représentation des données, définir les tables et ensembles de la base de données',
|
||||
'the presentations layer, views are also known as templates': 'la couche de présentation, les vues sont également appelées modèles',
|
||||
'these files are served without processing, your images go here': 'ces fichiers sont servis sans transformation, vos images vont ici',
|
||||
'to previous version.': 'à la version précédente.',
|
||||
'translation strings for the application': "chaînes de traduction de l'application",
|
||||
@@ -258,22 +258,22 @@
|
||||
'unable to delete file plugin "%(plugin)s"': 'impossible de supprimer le plugin "%(plugin)s"',
|
||||
'unable to parse csv file': "impossible d'analyser les fichiers CSV",
|
||||
'unable to uninstall "%s"': 'impossible de désinstaller "%s"',
|
||||
'unable to upgrade because "%s"': 'unable to upgrade because "%s"',
|
||||
'unable to upgrade because "%s"': 'impossible de mettre à jour car "%s"',
|
||||
'uncheck all': 'tout décocher',
|
||||
'update': 'mettre à jour',
|
||||
'update all languages': 'mettre à jour toutes les langues',
|
||||
'upgrade now': 'upgrade now',
|
||||
'upgrade web2py now': 'upgrade web2py now',
|
||||
'upload': 'upload',
|
||||
'upgrade now': 'mettre à jour maintenant',
|
||||
'upgrade web2py now': 'mettre à jour web2py maintenant',
|
||||
'upload': 'charger',
|
||||
'upload application:': "charger l'application:",
|
||||
'upload file:': 'charger le fichier:',
|
||||
'upload plugin file:': 'charger fichier plugin:',
|
||||
'user': 'user',
|
||||
'user': 'utilisateur',
|
||||
'variables': 'variables',
|
||||
'versioning': 'versioning',
|
||||
'view': 'vue',
|
||||
'views': 'vues',
|
||||
'web2py Recent Tweets': 'web2py Tweets récentes',
|
||||
'web2py Recent Tweets': 'Tweets récents sur web2py ',
|
||||
'web2py is up to date': 'web2py est à jour',
|
||||
'web2py upgraded; please restart it': 'web2py upgraded; please restart it',
|
||||
'web2py upgraded; please restart it': 'web2py mis à jour; veuillez le redémarrer',
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ div.flash {
|
||||
position:fixed;
|
||||
padding:10px;
|
||||
top:48px;
|
||||
right:50px;
|
||||
right:250px;
|
||||
min-width:280px;
|
||||
opacity:0.95;
|
||||
margin:0px 0px 10px 10px;
|
||||
@@ -191,6 +191,7 @@ div.error {
|
||||
.web2py_grid {width:100%}
|
||||
.web2py_grid table {width:100%}
|
||||
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
|
||||
.web2py_grid .web2py_form td {vertical-align: top;}
|
||||
|
||||
.web2py_grid thead th,.web2py_grid tfoot td {
|
||||
background-color:#EAEAEA;
|
||||
|
||||
@@ -8,21 +8,28 @@ jQuery(function(){
|
||||
if(jQuery(this).find('ul').length)
|
||||
jQuery(this).addClass('dropdown-submenu');
|
||||
});
|
||||
function adjust_height_of_collapsed_nav() {
|
||||
var cn = jQuery('div.collapse');
|
||||
if (cn.get(0)) {
|
||||
var cnh = cn.get(0).style.height;
|
||||
if (cnh>'0px'){
|
||||
cn.css('height','auto');
|
||||
}
|
||||
}
|
||||
}
|
||||
function hoverMenu(){
|
||||
var wid = document.documentElement.clientWidth; //faster than $(window).width() and cross browser
|
||||
if (wid>=980){
|
||||
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
|
||||
mi = jQuery(this).addClass('open');
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
|
||||
}, function(){
|
||||
mi = jQuery(this);
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
|
||||
});
|
||||
};
|
||||
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
|
||||
adjust_height_of_collapsed_nav();
|
||||
mi = jQuery(this).addClass('open');
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
|
||||
}, function(){
|
||||
mi = jQuery(this);
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
|
||||
});
|
||||
}
|
||||
hoverMenu(); // first page load
|
||||
jQuery(window).resize(hoverMenu); // on resize event
|
||||
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
|
||||
// make all buttons bootstrap buttons
|
||||
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,16 @@
|
||||
{{
|
||||
def shortcut(combo, description):
|
||||
return XML('<li><span class="teletype-text">%s</span><span>%s</span></li>' % (combo, description))
|
||||
def listfiles(app, dir, regexp='.*\.py$'):
|
||||
files = sorted(
|
||||
listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
|
||||
files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')]
|
||||
return files
|
||||
|
||||
def editfile(path,file,vars={}):
|
||||
args=(path,file) if 'app' in vars else (app,path,file)
|
||||
return A(file, _class='', _href=URL('edit', args=args, vars=vars), _style='word-wrap: break-word;')
|
||||
|
||||
}}
|
||||
{{if TEXT_EDITOR == 'amy':}}
|
||||
{{include 'default/amy_ajax.html'}}
|
||||
@@ -128,8 +138,31 @@ jQuery(document).ready(function(){
|
||||
<a class="button btn" href="http://www.web2py.com/examples/static/epydoc/index.html" target="_blank"><span>{{=T('docs')}}</span></a>
|
||||
</p>
|
||||
</div>
|
||||
<div id="editor_area">
|
||||
<form action="{{=URL('edit',args=filename)}}" method="post" name="editform" id="editform">
|
||||
<div id="editor_area" class="row-fluid">
|
||||
|
||||
<ul class="nav nav-list span2 well" rel="pagebookmark">
|
||||
{{dirs=[{'name':'models', 'reg':'.*\.py$'},
|
||||
{'name':'controllers', 'reg':'.*\.py$'},
|
||||
{'name':'views', 'reg':'[\w/\-]+(\.\w+)+$'},
|
||||
{'name':'modules', 'reg':'.*\.py$'},
|
||||
{'name':'private', 'reg': '[^\.#].*'}]}}
|
||||
{{for dir in dirs:}}
|
||||
<li class="nav-header component" onclick="collapse('{{="%s_files" % dir['name']}}');">{{=dir['name']}}</li>
|
||||
<li id="{{="%s_files" % dir['name']}}">
|
||||
<ul class="nav nav-list">
|
||||
{{for f in listfiles(app, dir['name'], regexp=dir['reg'] ):}}
|
||||
{{id="%s__" % dir['name'] + f.replace('.','__')}}
|
||||
{{current_file = request.args(len(request.args) - 1)}}
|
||||
<li class="{{='active' if current_file==f else ''}}">
|
||||
{{=editfile(dir['name'], f, dict(id=id))}}
|
||||
</li>
|
||||
{{pass}}
|
||||
</ul>
|
||||
</li>
|
||||
{{pass}}
|
||||
</ul>
|
||||
|
||||
<form action="{{=URL('edit',args=filename)}}" method="post" name="editform" id="editform" class="span10">
|
||||
<div class="editor-bar-column">
|
||||
<label>{{=T('Save file:')}}</label>
|
||||
<a value="save" name="save" onclick="return doClickSave();" class="icon saveicon" style="background-image: -webkit-linear-gradient(top,white,#E6E6E6);">
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
<title>{{=response.title or URL()}}</title>
|
||||
{{
|
||||
response.files.append(URL('static','css/bootstrap.min.css'))
|
||||
#response.files.append(URL('static','css/styles.css'))
|
||||
response.files.append(URL('static','css/bootstrap_essentials.css'))
|
||||
# response.files.append(URL('static','css/bootstrap_adapters.css'))
|
||||
response.files.append(URL('static','css/bootstrap-responsive.min.css'))
|
||||
}}
|
||||
{{include 'web2py_ajax.html'}}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
var w2p_ajax_confirm_message = "{{=T('Are you sure you want to delete this object?')}}";
|
||||
var w2p_ajax_date_format = "{{=T('%Y-%m-%d')}}";
|
||||
var w2p_ajax_datetime_format = "{{=T('%Y-%m-%d %H:%M:%S')}}";
|
||||
var ajax_error_500 = '{{=XML(T('An error occured, please %s the page') % A(T('reload'), _href=URL(args=request.args, vars=request.vars))) }}'
|
||||
var ajax_error_500 = '{{=XML(T('An error occured, please %s the page') % A(T('reload'), _href=URL(args=request.args, vars=request.get_vars))) }}'
|
||||
//--></script>
|
||||
{{
|
||||
response.files.insert(0,URL('static','js/jquery.js'))
|
||||
|
||||
@@ -279,14 +279,15 @@ def update():
|
||||
(db, table) = get_table(request)
|
||||
keyed = hasattr(db[table], '_primarykey')
|
||||
record = None
|
||||
db[table]._common_filter = None
|
||||
if keyed:
|
||||
key = [f for f in request.vars if f in db[table]._primarykey]
|
||||
if key:
|
||||
record = db(db[table][key[0]] == request.vars[key[
|
||||
0]], ignore_common_filters=True).select().first()
|
||||
0]]).select().first()
|
||||
else:
|
||||
record = db(db[table].id == request.args(
|
||||
2), ignore_common_filters=True).select().first()
|
||||
2)).select().first()
|
||||
|
||||
if not record:
|
||||
qry = query_by_table_type(table, db)
|
||||
|
||||
@@ -92,7 +92,7 @@ div.flash {
|
||||
position:fixed;
|
||||
padding:10px;
|
||||
top:48px;
|
||||
right:50px;
|
||||
right:250px;
|
||||
min-width:280px;
|
||||
opacity:0.95;
|
||||
margin:0px 0px 10px 10px;
|
||||
@@ -191,6 +191,7 @@ div.error {
|
||||
.web2py_grid {width:100%}
|
||||
.web2py_grid table {width:100%}
|
||||
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
|
||||
.web2py_grid .web2py_form td {vertical-align: top;}
|
||||
|
||||
.web2py_grid thead th,.web2py_grid tfoot td {
|
||||
background-color:#EAEAEA;
|
||||
|
||||
@@ -8,21 +8,28 @@ jQuery(function(){
|
||||
if(jQuery(this).find('ul').length)
|
||||
jQuery(this).addClass('dropdown-submenu');
|
||||
});
|
||||
function adjust_height_of_collapsed_nav() {
|
||||
var cn = jQuery('div.collapse');
|
||||
if (cn.get(0)) {
|
||||
var cnh = cn.get(0).style.height;
|
||||
if (cnh>'0px'){
|
||||
cn.css('height','auto');
|
||||
}
|
||||
}
|
||||
}
|
||||
function hoverMenu(){
|
||||
var wid = document.documentElement.clientWidth; //faster than $(window).width() and cross browser
|
||||
if (wid>=980){
|
||||
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
|
||||
mi = jQuery(this).addClass('open');
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
|
||||
}, function(){
|
||||
mi = jQuery(this);
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
|
||||
});
|
||||
};
|
||||
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
|
||||
adjust_height_of_collapsed_nav();
|
||||
mi = jQuery(this).addClass('open');
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
|
||||
}, function(){
|
||||
mi = jQuery(this);
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
|
||||
});
|
||||
}
|
||||
hoverMenu(); // first page load
|
||||
jQuery(window).resize(hoverMenu); // on resize event
|
||||
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
|
||||
// make all buttons bootstrap buttons
|
||||
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@
|
||||
var w2p_ajax_confirm_message = "{{=T('Are you sure you want to delete this object?')}}";
|
||||
var w2p_ajax_date_format = "{{=T('%Y-%m-%d')}}";
|
||||
var w2p_ajax_datetime_format = "{{=T('%Y-%m-%d %H:%M:%S')}}";
|
||||
var ajax_error_500 = '{{=XML(T('An error occured, please %s the page') % A(T('reload'), _href=URL(args=request.args, vars=request.vars))) }}'
|
||||
var ajax_error_500 = '{{=XML(T('An error occured, please %s the page') % A(T('reload'), _href=URL(args=request.args, vars=request.get_vars))) }}'
|
||||
//--></script>
|
||||
{{
|
||||
response.files.insert(0,URL('static','js/jquery.js'))
|
||||
|
||||
@@ -279,14 +279,15 @@ def update():
|
||||
(db, table) = get_table(request)
|
||||
keyed = hasattr(db[table], '_primarykey')
|
||||
record = None
|
||||
db[table]._common_filter = None
|
||||
if keyed:
|
||||
key = [f for f in request.vars if f in db[table]._primarykey]
|
||||
if key:
|
||||
record = db(db[table][key[0]] == request.vars[key[
|
||||
0]], ignore_common_filters=True).select().first()
|
||||
0]]).select().first()
|
||||
else:
|
||||
record = db(db[table].id == request.args(
|
||||
2), ignore_common_filters=True).select().first()
|
||||
2)).select().first()
|
||||
|
||||
if not record:
|
||||
qry = query_by_table_type(table, db)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# this file is released under public domain and you can use without limitations
|
||||
|
||||
#########################################################################
|
||||
## This is a samples controller
|
||||
## This is a sample controller
|
||||
## - index is the default action of any application
|
||||
## - user is required for authentication and authorization
|
||||
## - download is for downloading files uploaded in the db (does streaming)
|
||||
@@ -39,6 +39,7 @@ def user():
|
||||
return dict(form=auth())
|
||||
|
||||
|
||||
@cache.action()
|
||||
def download():
|
||||
"""
|
||||
allows downloading of uploaded files
|
||||
|
||||
@@ -62,12 +62,12 @@
|
||||
'Client IP': 'IP del Cliente',
|
||||
'Community': 'Comunidad',
|
||||
'compile': 'compilar',
|
||||
'compiled application removed': 'aplicación compilada removida',
|
||||
'compiled application removed': 'aplicación compilada eliminada',
|
||||
'Components and Plugins': 'Componentes y Plugins',
|
||||
'Controller': 'Controlador',
|
||||
'Controllers': 'Controladores',
|
||||
'controllers': 'controladores',
|
||||
'Copyright': 'Derechos de autor',
|
||||
'Copyright': 'Copyright',
|
||||
'create file with filename:': 'cree archivo con nombre:',
|
||||
'Create new application': 'Cree una nueva aplicación',
|
||||
'create new application:': 'nombre de la nueva aplicación:',
|
||||
@@ -122,7 +122,7 @@
|
||||
'export as csv file': 'exportar como archivo CSV',
|
||||
'exposes': 'expone',
|
||||
'extends': 'extiende',
|
||||
'failed to reload module': 'recarga del módulo ha fallado',
|
||||
'failed to reload module': 'la recarga del módulo ha fallado',
|
||||
'FAQ': 'FAQ',
|
||||
'file "%(filename)s" created': 'archivo "%(filename)s" creado',
|
||||
'file "%(filename)s" deleted': 'archivo "%(filename)s" eliminado',
|
||||
@@ -142,7 +142,7 @@
|
||||
'Hello World': 'Hola Mundo',
|
||||
'help': 'ayuda',
|
||||
'Home': 'Home',
|
||||
'How did you get here?': '¿Cómo llegasta hasta allá?',
|
||||
'How did you get here?': '¿Cómo llegaste aquí?',
|
||||
'htmledit': 'htmledit',
|
||||
'import': 'importar',
|
||||
'Import/Export': 'Importar/Exportar',
|
||||
@@ -228,7 +228,7 @@
|
||||
'register': 'registrese',
|
||||
'Registration key': 'Llave de Registro',
|
||||
'remove compiled': 'eliminar compiladas',
|
||||
'Reset Password key': 'Restaurar LLavel de la Contraseña',
|
||||
'Reset Password key': 'Restaurar Llave de la Contraseña',
|
||||
'Resolve Conflict file': 'archivo Resolución de Conflicto',
|
||||
'restore': 'restaurar',
|
||||
'revert': 'revertir',
|
||||
@@ -260,7 +260,7 @@
|
||||
'the application logic, each URL path is mapped in one exposed function in the controller': 'la lógica de la aplicación, cada ruta URL se mapea en una función expuesta en el controlador',
|
||||
'The Core': 'El Núcleo',
|
||||
'the data representation, define database tables and sets': 'la representación de datos, define tablas y conjuntos de base de datos',
|
||||
'The output of the file is a dictionary that was rendered by the view %s': 'La salida del archivo es un diccionario escenificado por la vista %s',
|
||||
'The output of the file is a dictionary that was rendered by the view %s': 'La salida de dicha función es un diccionario que es desplegado por la vista %s',
|
||||
'the presentations layer, views are also known as templates': 'la capa de presentación, las vistas también son llamadas plantillas',
|
||||
'The Views': 'Las Vistas',
|
||||
'There are no controllers': 'No hay controladores',
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
'!langcode!': 'fr',
|
||||
'!langname!': 'Français',
|
||||
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" est une expression optionnelle comme "champ1=\'nouvellevaleur\'". Vous ne pouvez mettre à jour ou supprimer les résultats d\'un JOIN',
|
||||
'%s %%{row} deleted': '%s rangées supprimées',
|
||||
'%s %%{row} updated': '%s rangées mises à jour',
|
||||
'%s %%{row} deleted': '%s lignes supprimées',
|
||||
'%s %%{row} updated': '%s lignes mises à jour',
|
||||
'%s selected': '%s sélectionné',
|
||||
'%Y-%m-%d': '%Y-%m-%d',
|
||||
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
|
||||
'About': 'À propos',
|
||||
'Access Control': "Contrôle d'accès",
|
||||
'Administrative Interface': 'Administrative Interface',
|
||||
'Administrative Interface': "Interface d'administration",
|
||||
'Administrative interface': "Interface d'administration",
|
||||
'Ajax Recipes': 'Recettes Ajax',
|
||||
'appadmin is disabled because insecure channel': "appadmin est désactivée parce que le canal n'est pas sécurisé",
|
||||
@@ -20,38 +20,40 @@
|
||||
'Buy this book': 'Acheter ce livre',
|
||||
'cache': 'cache',
|
||||
'Cache': 'Cache',
|
||||
'Cache Keys': 'Cache Keys',
|
||||
'Cache Keys': 'Clés de cache',
|
||||
'Cannot be empty': 'Ne peut pas être vide',
|
||||
'change password': 'changer le mot de passe',
|
||||
'Check to delete': 'Cliquez pour supprimer',
|
||||
'Check to delete:': 'Cliquez pour supprimer:',
|
||||
'Clear CACHE?': 'Clear CACHE?',
|
||||
'Clear DISK': 'Clear DISK',
|
||||
'Clear RAM': 'Clear RAM',
|
||||
'Clear CACHE?': 'Vider le CACHE?',
|
||||
'Clear DISK': 'Vider le DISQUE',
|
||||
'Clear RAM': 'Vider la RAM',
|
||||
'Client IP': 'IP client',
|
||||
'Community': 'Communauté',
|
||||
'Components and Plugins': 'Components and Plugins',
|
||||
'Components and Plugins': 'Composants et Plugins',
|
||||
'Controller': 'Contrôleur',
|
||||
'Copyright': 'Copyright',
|
||||
'Created By': 'Créé par',
|
||||
'Created On': 'Créé le',
|
||||
'Current request': 'Demande actuelle',
|
||||
'Current response': 'Réponse actuelle',
|
||||
'Current session': 'Session en cours',
|
||||
'customize me!': 'personnalisez-moi!',
|
||||
'data uploaded': 'données téléchargées',
|
||||
'Database': 'base de données',
|
||||
'Database %s select': 'base de données %s select',
|
||||
'db': 'db',
|
||||
'DB Model': 'Modèle DB',
|
||||
'Database %s select': 'base de données %s selectionnée',
|
||||
'db': 'bdd',
|
||||
'DB Model': 'Modèle BDD',
|
||||
'Delete:': 'Supprimer:',
|
||||
'Demo': 'Démo',
|
||||
'Deployment Recipes': 'Recettes de déploiement',
|
||||
'Description': 'Description',
|
||||
'design': 'design',
|
||||
'DISK': 'DISK',
|
||||
'Disk Cache Keys': 'Disk Cache Keys',
|
||||
'Disk Cleared': 'Disk Cleared',
|
||||
'DISK': 'DISQUE',
|
||||
'Disk Cache Keys': 'Clés de cache du disque',
|
||||
'Disk Cleared': 'Disque vidé',
|
||||
'Documentation': 'Documentation',
|
||||
"Don't know what to do?": "Don't know what to do?",
|
||||
"Don't know what to do?": 'Vous ne savez pas quoi faire?',
|
||||
'done!': 'fait!',
|
||||
'Download': 'Téléchargement',
|
||||
'E-mail': 'E-mail',
|
||||
@@ -59,8 +61,8 @@
|
||||
'Edit current record': "Modifier l'enregistrement courant",
|
||||
'edit profile': 'modifier le profil',
|
||||
'Edit This App': 'Modifier cette application',
|
||||
'Email and SMS': 'Email and SMS',
|
||||
'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g',
|
||||
'Email and SMS': 'Email et SMS',
|
||||
'enter an integer between %(min)g and %(max)g': 'entrez un entier entre %(min)g et %(max)g',
|
||||
'Errors': 'Erreurs',
|
||||
'export as csv file': 'exporter sous forme de fichier csv',
|
||||
'FAQ': 'FAQ',
|
||||
@@ -69,10 +71,10 @@
|
||||
'Free Applications': 'Applications gratuites',
|
||||
'Function disabled': 'Fonction désactivée',
|
||||
'Group ID': 'Groupe ID',
|
||||
'Groups': 'Groups',
|
||||
'Groups': 'Groupes',
|
||||
'Hello World': 'Bonjour le monde',
|
||||
'Home': 'Accueil',
|
||||
'How did you get here?': 'How did you get here?',
|
||||
'How did you get here?': 'Comment êtes-vous arrivé ici?',
|
||||
'import': 'import',
|
||||
'Import/Export': 'Importer/Exporter',
|
||||
'Index': 'Index',
|
||||
@@ -83,48 +85,51 @@
|
||||
'Invalid email': 'E-mail invalide',
|
||||
'Invalid Query': 'Requête Invalide',
|
||||
'invalid request': 'requête invalide',
|
||||
'Key': 'Key',
|
||||
'Is Active': 'Est actif',
|
||||
'Key': 'Clé',
|
||||
'Last name': 'Nom',
|
||||
'Layout': 'Mise en page',
|
||||
'Layout Plugins': 'Layout Plugins',
|
||||
'Layouts': 'Layouts',
|
||||
'Live chat': 'Chat live',
|
||||
'Live Chat': 'Live Chat',
|
||||
'Layout Plugins': 'Plugins de mise en page',
|
||||
'Layouts': 'Mises en page',
|
||||
'Live chat': 'Chat en direct',
|
||||
'Live Chat': 'Chat en direct',
|
||||
'login': 'connectez-vous',
|
||||
'Login': 'Connectez-vous',
|
||||
'logout': 'déconnectez-vous',
|
||||
'lost password': 'mot de passe perdu',
|
||||
'Lost Password': 'Mot de passe perdu',
|
||||
'Lost password?': 'Lost password?',
|
||||
'Lost password?': 'Mot de passe perdu?',
|
||||
'lost password?': 'mot de passe perdu?',
|
||||
'Main Menu': 'Menu principal',
|
||||
'Manage Cache': 'Manage Cache',
|
||||
'Manage Cache': 'Gérer le Cache',
|
||||
'Menu Model': 'Menu modèle',
|
||||
'My Sites': 'My Sites',
|
||||
'Modified By': 'Modifié par',
|
||||
'Modified On': 'Modifié le',
|
||||
'My Sites': 'Mes sites',
|
||||
'Name': 'Nom',
|
||||
'New Record': 'Nouvel enregistrement',
|
||||
'new record inserted': 'nouvel enregistrement inséré',
|
||||
'next 100 rows': '100 prochaines lignes',
|
||||
'No databases in this application': "Cette application n'a pas de bases de données",
|
||||
'Object or table name': 'Object or table name',
|
||||
'Object or table name': 'Objet ou nom de table',
|
||||
'Online examples': 'Exemples en ligne',
|
||||
'or import from csv file': "ou importer d'un fichier CSV",
|
||||
'Origin': 'Origine',
|
||||
'Other Plugins': 'Other Plugins',
|
||||
'Other Plugins': 'Autres Plugins',
|
||||
'Other Recipes': 'Autres recettes',
|
||||
'Overview': 'Présentation',
|
||||
'Password': 'Mot de passe',
|
||||
"Password fields don't match": 'Les mots de passe ne correspondent pas',
|
||||
'Plugins': 'Plugiciels',
|
||||
'Plugins': 'Plugins',
|
||||
'Powered by': 'Alimenté par',
|
||||
'Preface': 'Préface',
|
||||
'previous 100 rows': '100 lignes précédentes',
|
||||
'Python': 'Python',
|
||||
'Query:': 'Requête:',
|
||||
'Quick Examples': 'Examples Rapides',
|
||||
'Quick Examples': 'Exemples Rapides',
|
||||
'RAM': 'RAM',
|
||||
'RAM Cache Keys': 'RAM Cache Keys',
|
||||
'Ram Cleared': 'Ram Cleared',
|
||||
'RAM Cache Keys': 'Clés de cache de la RAM',
|
||||
'Ram Cleared': 'Ram vidée',
|
||||
'Readme': 'Lisez-moi',
|
||||
'Recipes': 'Recettes',
|
||||
'Record': 'enregistrement',
|
||||
@@ -133,7 +138,7 @@
|
||||
'Record id': "id d'enregistrement",
|
||||
'Register': "S'inscrire",
|
||||
'register': "s'inscrire",
|
||||
'Registration identifier': 'Registration identifier',
|
||||
'Registration identifier': "Identifiant d'enregistrement",
|
||||
'Registration key': "Clé d'enregistrement",
|
||||
'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)',
|
||||
'Request reset password': 'Demande de réinitialiser le mot clé',
|
||||
@@ -144,42 +149,42 @@
|
||||
'Rows selected': 'Lignes sélectionnées',
|
||||
'Semantic': 'Sémantique',
|
||||
'Services': 'Services',
|
||||
'Size of cache:': 'Size of cache:',
|
||||
'Size of cache:': 'Taille du cache:',
|
||||
'state': 'état',
|
||||
'Statistics': 'Statistics',
|
||||
'Statistics': 'Statistiques',
|
||||
'Stylesheet': 'Feuille de style',
|
||||
'submit': 'submit',
|
||||
'submit': 'soumettre',
|
||||
'Submit': 'Soumettre',
|
||||
'Support': 'Support',
|
||||
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
|
||||
'Table': 'tableau',
|
||||
'Table name': 'Nom du tableau',
|
||||
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "query" est une condition comme "db.table1.champ1==\'valeur\'". Quelque chose comme "db.table1.champ1==db.table2.champ2" résulte en un JOIN SQL.',
|
||||
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "requête" est une condition comme "db.table1.champ1==\'valeur\'". Quelque chose comme "db.table1.champ1==db.table2.champ2" résulte en un JOIN SQL.',
|
||||
'The Core': 'Le noyau',
|
||||
'The output of the file is a dictionary that was rendered by the view %s': 'La sortie de ce fichier est un dictionnaire qui été restitué par la vue %s',
|
||||
'The Views': 'Les Vues',
|
||||
'This App': 'Cette Appli',
|
||||
'This is a copy of the scaffolding application': "Ceci est une copie de l'application échafaudage",
|
||||
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
|
||||
'Time in Cache (h:m:s)': 'Temps en Cache (h:m:s)',
|
||||
'Timestamp': 'Horodatage',
|
||||
'Twitter': 'Twitter',
|
||||
'unable to parse csv file': "incapable d'analyser le fichier cvs",
|
||||
'Update:': 'Mise à jour:',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Employez (...)&(...) pour AND, (...)|(...) pour OR, and ~(...) pour NOT pour construire des requêtes plus complexes.',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Employez (...)&(...) pour AND, (...)|(...) pour OR, and ~(...) pour NOT afin de construire des requêtes plus complexes.',
|
||||
'User %(id)s Logged-in': 'Utilisateur %(id)s connecté',
|
||||
'User %(id)s Registered': 'Utilisateur %(id)s enregistré',
|
||||
'User ID': 'ID utilisateur',
|
||||
'User Voice': 'User Voice',
|
||||
'User Voice': "Voix de l'utilisateur",
|
||||
'Verify Password': 'Vérifiez le mot de passe',
|
||||
'Videos': 'Vidéos',
|
||||
'View': 'Présentation',
|
||||
'Web2py': 'Web2py',
|
||||
'Welcome': 'Bienvenu',
|
||||
'Welcome': 'Bienvenue',
|
||||
'Welcome %s': 'Bienvenue %s',
|
||||
'Welcome to web2py': 'Bienvenue à web2py',
|
||||
'Welcome to web2py!': 'Welcome to web2py!',
|
||||
'Welcome to web2py!': 'Bienvenue à web2py!',
|
||||
'Which called the function %s located in the file %s': 'Qui a appelé la fonction %s se trouvant dans le fichier %s',
|
||||
'You are successfully running web2py': 'Vous roulez avec succès web2py',
|
||||
'You are successfully running web2py': 'Vous exécutez avec succès web2py',
|
||||
'You can modify this application and adapt it to your needs': "Vous pouvez modifier cette application et l'adapter à vos besoins",
|
||||
'You visited the url %s': "Vous avez visité l'URL %s",
|
||||
}
|
||||
|
||||
Executable
+272
@@ -0,0 +1,272 @@
|
||||
# coding: utf8
|
||||
{
|
||||
'!langcode!': 'id',
|
||||
'!langname!': 'Indonesian',
|
||||
'%d days ago': '%d hari yang lalu',
|
||||
'%d hours ago': '%d jam yang lalu',
|
||||
'%d minutes ago': '%d menit yang lalu',
|
||||
'%d months ago': '%d bulan yang lalu',
|
||||
'%d seconds ago': '%d detik yang lalu',
|
||||
'%d seconds from now': '%d detik dari sekarang',
|
||||
'%d weeks ago': '%d minggu yang lalu',
|
||||
'%d years ago': '%d tahun yang lalu',
|
||||
'%s %%{row} deleted': '%s %%{row} dihapus',
|
||||
'%s %%{row} updated': '%s %%{row} diperbarui',
|
||||
'%s selected': '%s dipilih',
|
||||
'%Y-%m-%d': '%d-%m-%Y',
|
||||
'%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S',
|
||||
'(requires internet access, experimental)': '(membutuhkan akses internet, eksperimental)',
|
||||
'(something like "it-it")': '(sesuatu seperti "it-it")',
|
||||
'1 day ago': '1 hari yang lalu',
|
||||
'1 hour ago': '1 jam yang lalu',
|
||||
'1 minute ago': '1 menit yang lalu',
|
||||
'1 month ago': '1 bulan yang lalu',
|
||||
'1 second ago': '1 detik yang lalu',
|
||||
'1 week ago': '1 minggu yang lalu',
|
||||
'1 year ago': '1 tahun yang lalu',
|
||||
'< Previous': '< Sebelumnya',
|
||||
'About': 'Tentang',
|
||||
'About application': 'Tentang Aplikasi',
|
||||
'Add': 'Tambah',
|
||||
'Additional code for your application': 'Tambahan kode untuk aplikasi Anda',
|
||||
'Address': 'Alamat',
|
||||
'Admin language': 'Bahasa Admin',
|
||||
'administrative interface': 'antarmuka administrative',
|
||||
'Administrator Password:': 'Administrator Kata Sandi:',
|
||||
'Ajax Recipes': 'Resep Ajax',
|
||||
'An error occured, please %s the page': 'Terjadi kesalahan, silakan %s halaman',
|
||||
'And': 'Dan',
|
||||
'and rename it:': 'dan memberi nama baru itu:',
|
||||
'Answer': 'Jawaban',
|
||||
'appadmin is disabled because insecure channel': 'AppAdmin dinonaktifkan karena kanal tidak aman',
|
||||
'application "%s" uninstalled': 'applikasi "%s" dihapus',
|
||||
'application compiled': 'aplikasi dikompilasi',
|
||||
'Application name:': 'Nama Applikasi:',
|
||||
'are not used yet': 'tidak digunakan lagi',
|
||||
'Are you sure you want to delete this object?': 'Apakah Anda yakin ingin menghapus ini?',
|
||||
'Are you sure you want to uninstall application "%s"?': 'Apakah Anda yakin ingin menghapus aplikasi "%s"?',
|
||||
'Available Databases and Tables': 'Database dan Tabel yang tersedia',
|
||||
'Back': 'Kembali',
|
||||
'Buy this book': 'Beli buku ini',
|
||||
'cache, errors and sessions cleaned': 'cache, kesalahan dan sesi dibersihkan',
|
||||
'can be a git repo': 'bisa menjadi repo git',
|
||||
'Cancel': 'Batalkan',
|
||||
'Cannot be empty': 'Tidak boleh kosong',
|
||||
'Change admin password': 'Ubah kata sandi admin',
|
||||
'Change password': 'Ubah kata sandi',
|
||||
'Check for upgrades': 'Periksa upgrade',
|
||||
'Check to delete': 'Centang untuk menghapus',
|
||||
'Checking for upgrades...': 'Memeriksa untuk upgrade...',
|
||||
'Clean': 'Bersih',
|
||||
'Clear': 'Hapus',
|
||||
'Clear CACHE?': 'Hapus CACHE?',
|
||||
'Clear DISK': 'Hapus DISK',
|
||||
'Clear RAM': 'Hapus RAM',
|
||||
'Click row to expand traceback': 'Klik baris untuk memperluas traceback',
|
||||
'Close': 'Tutup',
|
||||
'collapse/expand all': 'kempis / memperluas semua',
|
||||
'Community': 'Komunitas',
|
||||
'Compile': 'Kompilasi',
|
||||
'compiled application removed': 'aplikasi yang dikompilasi dihapus',
|
||||
'Components and Plugins': 'Komponen dan Plugin',
|
||||
'contains': 'mengandung',
|
||||
'Controllers': 'Kontrolir',
|
||||
'controllers': 'kontrolir',
|
||||
'Copyright': 'Hak Cipta',
|
||||
'Count': 'Hitung',
|
||||
'Create': 'Buat',
|
||||
'create file with filename:': 'buat file dengan nama:',
|
||||
'created by': 'dibuat oleh',
|
||||
'CSV (hidden cols)': 'CSV (kolom tersembunyi)',
|
||||
'currently running': 'sedang berjalan',
|
||||
'data uploaded': 'data diunggah',
|
||||
'Database %s select': 'Memilih Database %s',
|
||||
'database administration': 'administrasi database',
|
||||
'defines tables': 'mendefinisikan tabel',
|
||||
'Delete': 'Hapus',
|
||||
'delete all checked': 'menghapus semua yang di centang',
|
||||
'Delete this file (you will be asked to confirm deletion)': 'Hapus file ini (Anda akan diminta untuk mengkonfirmasi penghapusan)',
|
||||
'Delete:': 'Hapus:',
|
||||
'Description': 'Keterangan',
|
||||
'design': 'disain',
|
||||
'direction: ltr': 'petunjuk: ltr',
|
||||
'Disk Cleared': 'Disk Dihapus',
|
||||
'Documentation': 'Dokumentasi',
|
||||
"Don't know what to do?": 'Tidak tahu apa yang harus dilakukan?',
|
||||
'done!': 'selesai!',
|
||||
'Download': 'Unduh',
|
||||
'Download .w2p': 'Unduh .w2p',
|
||||
'download layouts': 'unduh layouts',
|
||||
'download plugins': 'unduh plugins',
|
||||
'Duration': 'Durasi',
|
||||
'Edit': 'Mengedit',
|
||||
'Edit application': 'Mengedit Aplikasi',
|
||||
'Email sent': 'Email dikirim',
|
||||
'enter a valid email address': 'masukkan alamat email yang benar',
|
||||
'enter a valid URL': 'masukkan URL yang benar',
|
||||
'enter a value': 'masukkan data',
|
||||
'Error': 'Kesalahan',
|
||||
'Error logs for "%(app)s"': 'Catatan kesalahan untuk "%(app)s"',
|
||||
'Errors': 'Kesalahan',
|
||||
'export as csv file': 'ekspor sebagai file csv',
|
||||
'Export:': 'Ekspor:',
|
||||
'exposes': 'menghadapkan',
|
||||
'extends': 'meluaskan',
|
||||
'filter': 'menyaring',
|
||||
'First Name': 'Nama Depan',
|
||||
'Forgot username?': 'Lupa nama pengguna?',
|
||||
'Free Applications': 'Aplikasi Gratis',
|
||||
'Gender': 'Jenis Kelamin',
|
||||
'Group %(group_id)s created': 'Grup %(group_id)s dibuat',
|
||||
'Group uniquely assigned to user %(id)s': 'Grup unik yang diberikan kepada pengguna %(id)s',
|
||||
'Groups': 'Grup',
|
||||
'Guest': 'Tamu',
|
||||
'Hello World': 'Halo Dunia',
|
||||
'Help': 'Bantuan',
|
||||
'Home': 'Halaman Utama',
|
||||
'How did you get here?': 'Bagaimana kamu bisa di sini?',
|
||||
'Image': 'Gambar',
|
||||
'import': 'impor',
|
||||
'Import/Export': 'Impor/Ekspor',
|
||||
'includes': 'termasuk',
|
||||
'Install': 'Memasang',
|
||||
'Installation': 'Instalasi',
|
||||
'Installed applications': 'Aplikasi yang diinstal',
|
||||
'Introduction': 'Pengenalan',
|
||||
'Invalid email': 'Email tidak benar',
|
||||
'Language': 'Bahasa',
|
||||
'languages': 'bahasa',
|
||||
'Languages': 'Bahasa',
|
||||
'Last Name': 'Nama Belakang',
|
||||
'License for': 'Lisensi untuk',
|
||||
'loading...': 'sedang memuat...',
|
||||
'Logged in': 'Masuk',
|
||||
'Logged out': 'Keluar',
|
||||
'Login': 'Masuk',
|
||||
'Login to the Administrative Interface': 'Masuk ke antarmuka Administrasi',
|
||||
'Logout': 'Keluar',
|
||||
'Lost Password': 'Lupa Kata Sandi',
|
||||
'Lost password?': 'Lupa kata sandi?',
|
||||
'Maintenance': 'Pemeliharaan',
|
||||
'Manage': 'Mengelola',
|
||||
'Manage Cache': 'Mengelola Cache',
|
||||
'models': 'model',
|
||||
'Models': 'Model',
|
||||
'Modules': 'Modul',
|
||||
'modules': 'modul',
|
||||
'My Sites': 'Situs Saya',
|
||||
'New': 'Baru',
|
||||
'new application "%s" created': 'aplikasi baru "%s" dibuat',
|
||||
'New password': 'Kata sandi baru',
|
||||
'New simple application': 'Aplikasi baru sederhana',
|
||||
'News': 'Berita',
|
||||
'next 100 rows': '100 baris berikutnya',
|
||||
'Next >': 'Berikutnya >',
|
||||
'Next Page': 'Halaman Berikutnya',
|
||||
'No databases in this application': 'Tidak ada database dalam aplikasi ini',
|
||||
'No ticket_storage.txt found under /private folder': 'Tidak ditemukan ticket_storage.txt dalam folder /private',
|
||||
'not a Zip Code': 'bukan Kode Pos',
|
||||
'Note': 'Catatan',
|
||||
'Old password': 'Kata sandi lama',
|
||||
'Online examples': 'Contoh Online',
|
||||
'Or': 'Atau',
|
||||
'or alternatively': 'atau alternatif',
|
||||
'Or Get from URL:': 'Atau Dapatkan dari URL:',
|
||||
'or import from csv file': 'atau impor dari file csv',
|
||||
'Other Plugins': 'Plugin Lainnya',
|
||||
'Other Recipes': 'Resep Lainnya',
|
||||
'Overview': 'Ikhtisar',
|
||||
'Overwrite installed app': 'Ikhtisar app yang terinstall',
|
||||
'Pack all': 'Pak semua',
|
||||
'Pack compiled': 'Pak yang telah dikompilasi',
|
||||
'Pack custom': 'Pak secara kustomisasi',
|
||||
'Password': 'Kata sandi',
|
||||
'Password changed': 'Kata sandi berubah',
|
||||
"Password fields don't match": 'Kata sandi tidak sama',
|
||||
'please input your password again': 'silahkan masukan kata sandi anda lagi',
|
||||
'plugins': 'plugin',
|
||||
'Plugins': 'Plugin',
|
||||
'Plural-Forms:': 'Bentuk-Jamak:',
|
||||
'Powered by': 'Didukung oleh',
|
||||
'Preface': 'Pendahuluan',
|
||||
'previous 100 rows': '100 baris sebelumnya',
|
||||
'Previous Page': 'Halaman Sebelumnya',
|
||||
'private files': 'file pribadi',
|
||||
'Private files': 'File pribadi',
|
||||
'Profile': 'Profil',
|
||||
'Profile updated': 'Profil diperbarui',
|
||||
'Project Progress': 'Perkembangan Proyek',
|
||||
'Quick Examples': 'Contoh Cepat',
|
||||
'Ram Cleared': 'Ram Dihapus',
|
||||
'Recipes': 'Resep',
|
||||
'Register': 'Daftar',
|
||||
'Registration successful': 'Pendaftaran berhasil',
|
||||
'reload': 'memuat kembali',
|
||||
'Reload routes': 'Memuat rute kembali',
|
||||
'Remember me (for 30 days)': 'Ingat saya (selama 30 hari)',
|
||||
'Remove compiled': 'Hapus Kompilasi',
|
||||
'Request reset password': 'Meminta reset kata sandi',
|
||||
'Rows in Table': 'Baris dalam Tabel',
|
||||
'Rows selected': 'Baris dipilih',
|
||||
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Jalankan tes di file ini (untuk menjalankan semua file, Anda juga dapat menggunakan tombol berlabel 'test')",
|
||||
'Running on %s': 'Berjalan di %s',
|
||||
'Save model as...': 'Simpan model sebagai ...',
|
||||
'Save profile': 'Simpan profil',
|
||||
'Search': 'Cari',
|
||||
'Select Files to Package': 'Pilih Berkas untuk Paket',
|
||||
'Send Email': 'Kirim Email',
|
||||
'Service': 'Layanan',
|
||||
'Site': 'Situs',
|
||||
'Size of cache:': 'Ukuran cache:',
|
||||
'starts with': 'dimulai dengan',
|
||||
'static': 'statis',
|
||||
'Static': 'Statis',
|
||||
'Statistics': 'Statistik',
|
||||
'Support': 'Mendukung',
|
||||
'Table': 'Tabel',
|
||||
'test': 'tes',
|
||||
'The application logic, each URL path is mapped in one exposed function in the controller': 'Logika aplikasi, setiap jalur URL dipetakan dalam satu fungsi terpapar di kontrolir',
|
||||
'The data representation, define database tables and sets': 'Representasi data, mendefinisikan tabel database dan set',
|
||||
'There are no plugins': 'Tidak ada plugin',
|
||||
'There are no private files': 'Tidak ada file pribadi',
|
||||
'These files are not served, they are only available from within your app': 'File-file ini tidak dilayani, mereka hanya tersedia dari dalam aplikasi Anda',
|
||||
'These files are served without processing, your images go here': 'File-file ini disajikan tanpa pengolahan, gambar Anda di sini',
|
||||
'This App': 'App Ini',
|
||||
'Time in Cache (h:m:s)': 'Waktu di Cache (h: m: s)',
|
||||
'To create a plugin, name a file/folder plugin_[name]': 'Untuk membuat sebuah plugin, nama file / folder plugin_ [nama]',
|
||||
'too short': 'terlalu pendek',
|
||||
'Translation strings for the application': 'Terjemahan string untuk aplikasi',
|
||||
'Try the mobile interface': 'Coba antarmuka ponsel',
|
||||
'Unable to download because:': 'Tidak dapat mengunduh karena:',
|
||||
'unable to parse csv file': 'tidak mampu mengurai file csv',
|
||||
'update all languages': 'memperbarui semua bahasa',
|
||||
'Update:': 'Perbarui:',
|
||||
'Upload': 'Unggah',
|
||||
'Upload a package:': 'Unggah sebuah paket:',
|
||||
'Upload and install packed application': 'Upload dan pasang aplikasi yang dikemas',
|
||||
'upload file:': 'unggah file:',
|
||||
'upload plugin file:': 'unggah file plugin:',
|
||||
'User %(id)s Logged-in': 'Pengguna %(id)s Masuk',
|
||||
'User %(id)s Logged-out': 'Pengguna %(id)s Keluar',
|
||||
'User %(id)s Password changed': 'Pengguna %(id)s Kata Sandi berubah',
|
||||
'User %(id)s Password reset': 'Pengguna %(id)s Kata Sandi telah direset',
|
||||
'User %(id)s Profile updated': 'Pengguna %(id)s Profil diperbarui',
|
||||
'User %(id)s Registered': 'Pengguna %(id)s Terdaftar',
|
||||
'value already in database or empty': 'data sudah ada dalam database atau kosong',
|
||||
'value not allowed': 'data tidak benar',
|
||||
'value not in database': 'data tidak ada dalam database',
|
||||
'Verify Password': 'Verifikasi Kata Sandi',
|
||||
'Version': 'Versi',
|
||||
'View': 'Lihat',
|
||||
'Views': 'Lihat',
|
||||
'views': 'lihat',
|
||||
'Web Framework': 'Kerangka Web',
|
||||
'web2py is up to date': 'web2py terbaru',
|
||||
'web2py Recent Tweets': 'Tweet web2py terbaru',
|
||||
'Website': 'Situs Web',
|
||||
'Welcome': 'Selamat Datang',
|
||||
'Welcome to web2py!': 'Selamat Datang di web2py!',
|
||||
'You are successfully running web2py': 'Anda berhasil menjalankan web2py',
|
||||
'You can modify this application and adapt it to your needs': 'Anda dapat memodifikasi aplikasi ini dan menyesuaikan dengan kebutuhan Anda',
|
||||
'You visited the url %s': 'Anda mengunjungi url %s',
|
||||
}
|
||||
Executable
+217
@@ -0,0 +1,217 @@
|
||||
# coding: utf8
|
||||
{
|
||||
'!langcode!': 'my',
|
||||
'!langname!': 'Malay',
|
||||
'%d days ago': '%d hari yang lalu',
|
||||
'%d hours ago': '%d jam yang lalu',
|
||||
'%d minutes ago': '%d minit yang lalu',
|
||||
'%d months ago': '%d bulan yang lalu',
|
||||
'%d seconds ago': '%d saat yang lalu',
|
||||
'%d seconds from now': '%d saat dari sekarang',
|
||||
'%d weeks ago': '%d minggu yang lalu',
|
||||
'%d years ago': '%d tahun yang lalu',
|
||||
'%s %%{row} deleted': '%s %%{row} dihapuskan',
|
||||
'%s %%{row} updated': '%s %%{row} dikemas kini',
|
||||
'%s selected': '%s dipilih',
|
||||
'%Y-%m-%d': '%d-%m-%Y',
|
||||
'%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S',
|
||||
'(requires internet access, experimental)': '(memerlukan akses internet, percubaan)',
|
||||
'(something like "it-it")': '(sesuatu seperti "it-it")',
|
||||
'1 day ago': '1 hari yang lalu',
|
||||
'1 hour ago': '1 jam yang lalu',
|
||||
'1 minute ago': '1 minit yang lalu',
|
||||
'1 month ago': '1 bulan yang lalu',
|
||||
'1 second ago': '1 saat yang lalu',
|
||||
'1 week ago': '1 minggu yang lalu',
|
||||
'1 year ago': '1 tahun yang lalu',
|
||||
'< Previous': '< Sebelumnya',
|
||||
'About': 'Mengenai',
|
||||
'Add': 'Tambah',
|
||||
'Admin language': 'Bahasa admin',
|
||||
'Administrator Password:': 'Kata laluan Administrator:',
|
||||
'Ajax Recipes': 'Resipi Ajax',
|
||||
'An error occured, please %s the page': 'Kesilapan telah berlaku, sila %s laman',
|
||||
'And': 'Dan',
|
||||
'and rename it:': 'dan menamakan itu:',
|
||||
'are not used yet': 'tidak digunakan lagi',
|
||||
'Are you sure you want to delete this object?': 'Apakah anda yakin anda mahu memadam ini?',
|
||||
'Back': 'Kembali',
|
||||
'Buy this book': 'Beli buku ini',
|
||||
'cache, errors and sessions cleaned': 'cache, kesilapan dan sesi dibersihkan',
|
||||
'Cancel': 'Batal',
|
||||
'Cannot be empty': 'Tidak boleh kosong',
|
||||
'Change admin password': 'Tukar kata laluan admin',
|
||||
'Change password': 'Tukar kata laluan',
|
||||
'Clean': 'Bersihkan',
|
||||
'Clear': 'Hapus',
|
||||
'Clear CACHE?': 'Hapus CACHE?',
|
||||
'Clear DISK': 'Hapus DISK',
|
||||
'Clear RAM': 'Hapus RAM',
|
||||
'Click row to expand traceback': 'Klik baris untuk mengembangkan traceback',
|
||||
'Close': 'Tutup',
|
||||
'Community': 'Komuniti',
|
||||
'Components and Plugins': 'Komponen dan Plugin',
|
||||
'contains': 'mengandung',
|
||||
'Copyright': 'Hak Cipta',
|
||||
'Create': 'Buat',
|
||||
'create file with filename:': 'mencipta fail dengan nama:',
|
||||
'created by': 'dicipta oleh',
|
||||
'currently running': 'sedang berjalan',
|
||||
'data uploaded': 'data diunggah',
|
||||
'Delete': 'Hapus',
|
||||
'Delete this file (you will be asked to confirm deletion)': 'Padam fail ini (anda akan diminta untuk mengesahkan pemadaman)',
|
||||
'Delete:': 'Hapus:',
|
||||
'design': 'disain',
|
||||
'direction: ltr': 'arah: ltr',
|
||||
'Disk Cleared': 'Disk Dihapuskan',
|
||||
'Documentation': 'Dokumentasi',
|
||||
"Don't know what to do?": 'Tidak tahu apa yang perlu dilakukan?',
|
||||
'done!': 'selesai!',
|
||||
'Download': 'Unduh',
|
||||
'Duration': 'Tempoh',
|
||||
'Email : ': 'Emel : ',
|
||||
'Email sent': 'Emel dihantar',
|
||||
'enter a valid email address': 'masukkan alamat emel yang benar',
|
||||
'enter a valid URL': 'masukkan URL yang benar',
|
||||
'enter a value': 'masukkan data',
|
||||
'Error': 'Kesalahan',
|
||||
'Errors': 'Kesalahan',
|
||||
'export as csv file': 'eksport sebagai file csv',
|
||||
'Export:': 'Eksport:',
|
||||
'File': 'Fail',
|
||||
'filter': 'menapis',
|
||||
'First Name': 'Nama Depan',
|
||||
'Forgot username?': 'Lupa nama pengguna?',
|
||||
'Free Applications': 'Aplikasi Percuma',
|
||||
'Gender': 'Jenis Kelamin',
|
||||
'Group %(group_id)s created': 'Kumpulan %(group_id)s dicipta',
|
||||
'Group uniquely assigned to user %(id)s': 'Kumpulan unik yang diberikan kepada pengguna %(id)s',
|
||||
'Groups': 'Kumpulan',
|
||||
'Hello World': 'Halo Dunia',
|
||||
'Help': 'Bantuan',
|
||||
'Home': 'Laman Utama',
|
||||
'How did you get here?': 'Bagaimana kamu boleh di sini?',
|
||||
'Image': 'Gambar',
|
||||
'import': 'import',
|
||||
'Import/Export': 'Import/Eksport',
|
||||
'includes': 'termasuk',
|
||||
'Install': 'Pasang',
|
||||
'Installation': 'Pemasangan',
|
||||
'Introduction': 'Pengenalan',
|
||||
'Invalid email': 'Emel tidak benar',
|
||||
'Language': 'Bahasa',
|
||||
'languages': 'bahasa',
|
||||
'Languages': 'Bahasa',
|
||||
'Last Name': 'Nama Belakang',
|
||||
'License for': 'lesen untuk',
|
||||
'loading...': 'sedang memuat...',
|
||||
'Logged in': 'Masuk',
|
||||
'Logged out': 'Keluar',
|
||||
'Login': 'Masuk',
|
||||
'Logout': 'Keluar',
|
||||
'Lost Password': 'Lupa Kata Laluan',
|
||||
'Lost password?': 'Lupa kata laluan?',
|
||||
'Maintenance': 'Penyelenggaraan',
|
||||
'Manage': 'Menguruskan',
|
||||
'Manage Cache': 'Menguruskan Cache',
|
||||
'models': 'model',
|
||||
'Models': 'Model',
|
||||
'Modules': 'Modul',
|
||||
'modules': 'modul',
|
||||
'My Sites': 'Laman Saya',
|
||||
'New': 'Baru',
|
||||
'New password': 'Kata laluan baru',
|
||||
'next 100 rows': '100 baris seterusnya',
|
||||
'Next >': 'Seterusnya >',
|
||||
'Next Page': 'Laman Seterusnya',
|
||||
'No ticket_storage.txt found under /private folder': 'Ticket_storage.txt tidak dijumpai di bawah folder /private',
|
||||
'not a Zip Code': 'bukan Pos',
|
||||
'Old password': 'Kata laluan lama',
|
||||
'Online examples': 'Contoh Online',
|
||||
'Or': 'Atau',
|
||||
'or alternatively': 'atau sebagai alternatif',
|
||||
'Or Get from URL:': 'Atau Dapatkan dari URL:',
|
||||
'or import from csv file': 'atau import dari file csv',
|
||||
'Other Plugins': 'Plugin Lain',
|
||||
'Other Recipes': 'Resipi Lain',
|
||||
'Overview': 'Tinjauan',
|
||||
'Pack all': 'Mengemaskan semua',
|
||||
'Password': 'Kata laluan',
|
||||
'Password changed': 'Kata laluan berubah',
|
||||
"Password fields don't match": 'Kata laluan tidak sama',
|
||||
'please input your password again': 'sila masukan kata laluan anda lagi',
|
||||
'plugins': 'plugin',
|
||||
'Plugins': 'Plugin',
|
||||
'Powered by': 'Disokong oleh',
|
||||
'Preface': 'Pendahuluan',
|
||||
'previous 100 rows': '100 baris sebelumnya',
|
||||
'Previous Page': 'Laman Sebelumnya',
|
||||
'private files': 'fail peribadi',
|
||||
'Private files': 'Fail peribadi',
|
||||
'Profile': 'Profil',
|
||||
'Profile updated': 'Profil dikemaskini',
|
||||
'Project Progress': 'Kemajuan Projek',
|
||||
'Quick Examples': 'Contoh Cepat',
|
||||
'Ram Cleared': 'Ram Dihapuskan',
|
||||
'Recipes': 'Resipi',
|
||||
'Register': 'Daftar',
|
||||
'Registration successful': 'Pendaftaran berjaya',
|
||||
'reload': 'memuat kembali',
|
||||
'Reload routes': 'Memuat laluan kembali',
|
||||
'Remember me (for 30 days)': 'Ingat saya (selama 30 hari)',
|
||||
'Request reset password': 'Meminta reset kata laluan',
|
||||
'Rows selected': 'Baris dipilih',
|
||||
'Running on %s': 'Berjalan pada %s',
|
||||
'Save model as...': 'Simpan model sebagai ...',
|
||||
'Save profile': 'Simpan profil',
|
||||
'Search': 'Cari',
|
||||
'Select Files to Package': 'Pilih Fail untuk Pakej',
|
||||
'Send Email': 'Kirim Emel',
|
||||
'Size of cache:': 'Saiz cache:',
|
||||
'Solution': 'Penyelesaian',
|
||||
'starts with': 'bermula dengan',
|
||||
'static': 'statik',
|
||||
'Static': 'Statik',
|
||||
'Statistics': 'Statistik',
|
||||
'Support': 'Menyokong',
|
||||
'test': 'ujian',
|
||||
'There are no plugins': 'Tiada plugin',
|
||||
'There are no private files': 'Tiada fail peribadi',
|
||||
'These files are not served, they are only available from within your app': 'Fail-fail ini tidak disampaikan, mereka hanya boleh didapati dari dalam aplikasi anda',
|
||||
'These files are served without processing, your images go here': 'Ini fail disampaikan tanpa pemprosesan, imej anda di sini',
|
||||
'This App': 'App Ini',
|
||||
'Time in Cache (h:m:s)': 'Waktu di Cache (h: m: s)',
|
||||
'Title': 'Judul',
|
||||
'To create a plugin, name a file/folder plugin_[name]': 'Untuk mencipta plugin, nama fail/folder plugin_ [nama]',
|
||||
'too short': 'terlalu pendek',
|
||||
'Unable to download because:': 'Tidak dapat memuat turun kerana:',
|
||||
'unable to parse csv file': 'tidak mampu mengurai file csv',
|
||||
'update all languages': 'mengemaskini semua bahasa',
|
||||
'Update:': 'Kemas kini:',
|
||||
'Upgrade': 'Menaik taraf',
|
||||
'Upload': 'Unggah',
|
||||
'Upload a package:': 'Unggah pakej:',
|
||||
'upload file:': 'unggah fail:',
|
||||
'upload plugin file:': 'unggah fail plugin:',
|
||||
'User %(id)s Logged-in': 'Pengguna %(id)s Masuk',
|
||||
'User %(id)s Logged-out': 'Pengguna %(id)s Keluar',
|
||||
'User %(id)s Password changed': 'Pengguna %(id)s Kata Laluan berubah',
|
||||
'User %(id)s Password reset': 'Pengguna %(id)s Kata Laluan telah direset',
|
||||
'User %(id)s Profile updated': 'Pengguna %(id)s Profil dikemaskini',
|
||||
'User %(id)s Registered': 'Pengguna %(id)s Didaftarkan',
|
||||
'value not allowed': 'data tidak benar',
|
||||
'Verify Password': 'Pengesahan Kata Laluan',
|
||||
'Version': 'Versi',
|
||||
'Versioning': 'Pembuatan Sejarah',
|
||||
'View': 'Lihat',
|
||||
'Views': 'Lihat',
|
||||
'views': 'Lihat',
|
||||
'Web Framework': 'Rangka Kerja Web',
|
||||
'web2py Recent Tweets': 'Tweet terbaru web2py',
|
||||
'Website': 'Laman Web',
|
||||
'Welcome': 'Selamat Datang',
|
||||
'Welcome to web2py!': 'Selamat Datang di web2py!',
|
||||
'You are successfully running web2py': 'Anda berjaya menjalankan web2py',
|
||||
'You can modify this application and adapt it to your needs': 'Anda boleh mengubah suai aplikasi ini dan menyesuaikan dengan keperluan anda',
|
||||
'You visited the url %s': 'Anda melawat url %s',
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
# coding: utf8
|
||||
{
|
||||
'!langcode!': 'tr',
|
||||
'!langname!': 'Türkçe',
|
||||
'%s %%(shop)': '%s %%(shop)',
|
||||
'%s %%(shop[0])': '%s %%(shop[0])',
|
||||
'%s %%{quark[0]}': '%s %%{quark[0]}',
|
||||
'%s %%{shop[0]}': '%s %%{shop[0]}',
|
||||
'%s %%{shop}': '%s %%{shop}',
|
||||
'%Y-%m-%d': '%Y-%m-%d',
|
||||
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
|
||||
'@markmin\x01**Hello World**': '**Merhaba Dünya**',
|
||||
'About': 'Hakkında',
|
||||
'Access Control': 'Erişim Denetimi',
|
||||
'Administrative Interface': 'Yönetim Arayüzü',
|
||||
'Ajax Recipes': 'Ajax Tarifleri',
|
||||
'An error occured, please %s the page': 'Bir hata meydana geldi, lütfen sayfayı %s',
|
||||
'Are you sure you want to delete this object?': 'Bu nesneyi silmek istediğinden emin misin?',
|
||||
'Buy this book': 'Bu kitabı satın alın',
|
||||
'Cannot be empty': 'Boş bırakılamaz',
|
||||
'Check to delete': 'Silmek için denetle',
|
||||
'Client IP': ' IP',
|
||||
'Community': 'Topluluk',
|
||||
'Components and Plugins': 'Bileşenler ve Eklentiler',
|
||||
'Controller': 'Denetçi',
|
||||
'Copyright': 'Telif',
|
||||
'Created By': 'Tasarlayan',
|
||||
'Created On': 'Oluşturma tarihi',
|
||||
'customize me!': 'burayı değiştir!',
|
||||
'Database': 'Veritabanı',
|
||||
'DB Model': 'DB Model',
|
||||
'Demo': 'Demo',
|
||||
'Deployment Recipes': 'Yayınlama tarifleri',
|
||||
'Description': 'Açıklama',
|
||||
'Documentation': 'Kitap',
|
||||
"Don't know what to do?": 'Neleri nasıl yapacağını bilmiyor musun?',
|
||||
'Download': 'İndir',
|
||||
'E-mail': 'E-posta',
|
||||
'Email and SMS': 'E-posta ve kısa mesaj (SMS)',
|
||||
'enter an integer between %(min)g and %(max)g': '%(min)g ve %(max)g arasında bir sayı girin',
|
||||
'enter date and time as %(format)s': 'tarih ve saati %(format)s biçiminde girin',
|
||||
'Errors': 'Hatalar',
|
||||
'FAQ': 'SSS',
|
||||
'First name': 'Ad',
|
||||
'Forgot username?': 'Kullanıcı adını mı unuttun?',
|
||||
'Forms and Validators': 'Biçimler ve Doğrulayıcılar',
|
||||
'Free Applications': 'Ücretsiz uygulamalar',
|
||||
'Giriş': 'Giriş',
|
||||
'Group %(group_id)s created': '%(group_id)s takımı oluşturuldu',
|
||||
'Group ID': 'Takım ID',
|
||||
'Group uniquely assigned to user %(id)s': 'Group uniquely assigned to user %(id)s',
|
||||
'Groups': 'Topluluklar',
|
||||
'Hello World': 'Merhaba Dünyalı',
|
||||
'Hello World ## comment': 'Merhaba Dünyalı ',
|
||||
'Hello World## comment': 'Merhaba Dünyalı',
|
||||
'Home': 'Anasayfa',
|
||||
'How did you get here?': 'Bu sayfayı görüntüleme uğruna neler mi oldu?',
|
||||
'Introduction': 'Giriş',
|
||||
'Invalid email': 'Yanlış eposta',
|
||||
'Is Active': 'Etkin',
|
||||
'Kayıt ol': 'Kayıt ol',
|
||||
'Last name': 'Soyad',
|
||||
'Layout': 'Şablon',
|
||||
'Layout Plugins': 'Şablon Eklentileri',
|
||||
'Layouts': 'Şablonlar',
|
||||
'Live Chat': 'Canlı Sohbet',
|
||||
'Logged in': 'Giriş yapıldı',
|
||||
'Logged out': 'Çıkış yapıldı',
|
||||
'Login': 'Giriş',
|
||||
'Logout': 'Terket',
|
||||
'Lost Password': 'Şifremi unuttum',
|
||||
'Lost password?': 'Şifrenizimi unuttunuz?',
|
||||
'Menu Model': 'Menu Model',
|
||||
'Modified By': 'Değiştiren',
|
||||
'Modified On': 'Değiştirilme tarihi',
|
||||
'My Sites': 'Sitelerim',
|
||||
'Name': 'İsim',
|
||||
'Object or table name': 'Nesne ya da tablo adı',
|
||||
'Online examples': 'Canlı örnekler',
|
||||
'Origin': 'Origin',
|
||||
'Other Plugins': 'Diğer eklentiler',
|
||||
'Other Recipes': 'Diğer Tarifler',
|
||||
'Overview': 'Göz gezdir',
|
||||
'Password': 'Şifre',
|
||||
"Password fields don't match": 'Şifreler uyuşmuyor',
|
||||
'please input your password again': 'lütfen şifrenizi tekrar girin',
|
||||
'Plugins': 'Eklentiler',
|
||||
'Powered by': 'Yazılım Temeli',
|
||||
'Preface': 'Preface',
|
||||
'Profile': 'Hakkımda',
|
||||
'Python': 'Python',
|
||||
'Quick Examples': 'Hızlı Örnekler',
|
||||
'Recipes': 'Recipes',
|
||||
'Record ID': 'Kayıt ID',
|
||||
'Register': 'Kayıt ol',
|
||||
'Registration identifier': 'Registration identifier',
|
||||
'Registration key': 'Kayıt anahtarı',
|
||||
'Registration successful': 'Kayıt başarılı',
|
||||
'reload': 'reload',
|
||||
'Remember me (for 30 days)': 'Beni hatırla (30 gün)',
|
||||
'Request reset password': 'Şifre sıfırla',
|
||||
'Reset Password key': 'Şifre anahtarını sıfırla',
|
||||
'Role': 'Role',
|
||||
'Semantic': 'Semantik',
|
||||
'Services': 'Hizmetler',
|
||||
'Stylesheet': 'Stil Şablonu',
|
||||
'Support': 'Destek',
|
||||
'The Core': 'Çekirdek',
|
||||
'The output of the file is a dictionary that was rendered by the view %s': 'Son olarak fonksiyonların vs. işlenip %s dosyasıyla tasarıma yedirilmesiyle sayfayı görüntüledin',
|
||||
'The Views': 'The Views',
|
||||
'This App': 'Bu Uygulama',
|
||||
'Timestamp': 'Zaman damgası',
|
||||
'Twitter': 'Twitter',
|
||||
'User %(id)s Logged-in': '%(id)s Giriş yaptı',
|
||||
'User %(id)s Logged-out': '%(id)s çıkış yaptı',
|
||||
'User %(id)s Password reset': 'User %(id)s Password reset',
|
||||
'User %(id)s Registered': '%(id)s Kayıt oldu',
|
||||
'User ID': 'Kullanıcı ID',
|
||||
'value already in database or empty': 'değer boş ya da veritabanında zaten mevcut',
|
||||
'Verify Password': 'Şifreni Onayla',
|
||||
'Videos': 'Videolar',
|
||||
'View': 'View',
|
||||
'Welcome': 'Hoşgeldin',
|
||||
'Welcome to web2py!': 'Welcome to web2py!',
|
||||
'Which called the function %s located in the file %s': 'Bu ziyaretle %s fonksiyonunu %s dosyasından çağırmış oldun ',
|
||||
'You are successfully running web2py': 'web2py çatısını çalıştırmayı başardın',
|
||||
'You can modify this application and adapt it to your needs': 'Artık uygulamayı kafana göre düzenleyebilirsin!',
|
||||
'You visited the url %s': '%s adresini ziyaret ettin',
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
# coding: utf8
|
||||
{
|
||||
'!langcode!': 'zh-cn',
|
||||
'!langname!': '中文',
|
||||
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" 应为选择表达式, 格式如 "field1=\'value\'". 但是对 JOIN 的结果不可以使用 update 或者 delete"',
|
||||
'%s %%{row} deleted': '已删除 %s 笔',
|
||||
'%s %%{row} updated': '已更新 %s 笔',
|
||||
'%s selected': '%s 已选择',
|
||||
'%Y-%m-%d': '%Y-%m-%d',
|
||||
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
|
||||
'(something like "it-it")': '(格式类似 "zh-tw")',
|
||||
'A new version of web2py is available': '新版 web2py 已推出',
|
||||
'A new version of web2py is available: %s': '新版 web2py 已推出: %s',
|
||||
'about': '关于',
|
||||
'About': '关于',
|
||||
'About application': '关于本应用程序',
|
||||
'Access Control': 'Access Control',
|
||||
'Admin is disabled because insecure channel': '管理功能(Admin)在非安全连接环境下自动关闭',
|
||||
'Admin is disabled because unsecure channel': '管理功能(Admin)在非安全连接环境下自动关闭',
|
||||
'Administrative Interface': 'Administrative Interface',
|
||||
'Administrative interface': '点击进入管理界面',
|
||||
'Administrator Password:': '管理员密码:',
|
||||
'Ajax Recipes': 'Ajax Recipes',
|
||||
'An error occured, please %s the page': 'An error occured, please %s the page',
|
||||
'appadmin is disabled because insecure channel': '管理界面在非安全通道下被禁用',
|
||||
'Are you sure you want to delete file "%s"?': '确定要删除文件"%s"?',
|
||||
'Are you sure you want to delete this object?': '确定要删除该对象么?',
|
||||
'Are you sure you want to uninstall application "%s"': '确定要删除应用程序 "%s"',
|
||||
'Are you sure you want to uninstall application "%s"?': '确定要删除应用程序 "%s"',
|
||||
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': '注意: 登录管理账号需要安全连接(HTTPS)或是在本地连接(localhost).',
|
||||
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': '注意: 因为在测试模式不保证多线程安全性,所以不可同时执行多个测试案例',
|
||||
'ATTENTION: you cannot edit the running application!': '注意:不可编辑正在执行的应用程序!',
|
||||
'Authentication': '验证',
|
||||
'Available Databases and Tables': '可提供的数据库和数据表',
|
||||
'Buy this book': '购买本书',
|
||||
'cache': '高速缓存',
|
||||
'Cache': 'Cache',
|
||||
'Cache Keys': 'Cache Keys',
|
||||
'Cannot be empty': '不可空白',
|
||||
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': '编译失败:应用程序有错误,请排除错误后再尝试编译.',
|
||||
'Change Password': '修改密码',
|
||||
'change password': '修改密码',
|
||||
'Check to delete': '打勾以示删除',
|
||||
'Check to delete:': '打勾以示删除:',
|
||||
'Clear CACHE?': 'Clear CACHE?',
|
||||
'Clear DISK': 'Clear DISK',
|
||||
'Clear RAM': 'Clear RAM',
|
||||
'Client IP': '客户端网址(IP)',
|
||||
'Community': 'Community',
|
||||
'Components and Plugins': 'Components and Plugins',
|
||||
'Controller': '控件',
|
||||
'Controllers': '控件',
|
||||
'Copyright': '版权所有',
|
||||
'Create new application': '创建应用程序',
|
||||
'Created By': 'Created By',
|
||||
'Created On': 'Created On',
|
||||
'Current request': '当前网络要求(request)',
|
||||
'Current response': '当前网络响应(response)',
|
||||
'Current session': '当前网络连接信息(session)',
|
||||
'customize me!': '请调整我!',
|
||||
'data uploaded': '数据已上传',
|
||||
'Database': '数据库',
|
||||
'Database %s select': '已选择 %s 数据库',
|
||||
'Date and Time': '日期和时间',
|
||||
'db': 'db',
|
||||
'DB Model': '数据库模型',
|
||||
'Delete': '删除',
|
||||
'Delete:': '删除:',
|
||||
'Demo': 'Demo',
|
||||
'Deploy on Google App Engine': '发布到 Google App Engine',
|
||||
'Deployment Recipes': 'Deployment Recipes',
|
||||
'Description': '描述',
|
||||
'DESIGN': '设计',
|
||||
'design': '设计',
|
||||
'Design for': '设计用于',
|
||||
'DISK': 'DISK',
|
||||
'Disk Cache Keys': 'Disk Cache Keys',
|
||||
'Disk Cleared': 'Disk Cleared',
|
||||
'Documentation': 'Documentation',
|
||||
"Don't know what to do?": "Don't know what to do?",
|
||||
'done!': '完成!',
|
||||
'Download': '下载',
|
||||
'E-mail': '电子邮件',
|
||||
'EDIT': '编辑',
|
||||
'Edit': '编辑',
|
||||
'Edit application': '编辑应用程序',
|
||||
'Edit current record': '编辑当前记录',
|
||||
'edit profile': '编辑配置文件',
|
||||
'Edit Profile': '编辑配置文件',
|
||||
'Edit This App': '编辑本应用程序',
|
||||
'Editing file': '编辑文件',
|
||||
'Editing file "%s"': '编辑文件"%s"',
|
||||
'Email and SMS': 'Email and SMS',
|
||||
'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g',
|
||||
'Error logs for "%(app)s"': '"%(app)s"的错误记录',
|
||||
'Errors': 'Errors',
|
||||
'export as csv file': '以CSV格式导出',
|
||||
'FAQ': 'FAQ',
|
||||
'First name': '名',
|
||||
'Forgot username?': '忘记用户名?',
|
||||
'Forms and Validators': 'Forms and Validators',
|
||||
'Free Applications': 'Free Applications',
|
||||
'Functions with no doctests will result in [passed] tests.': '沒有 doctests 的函数会显示 [passed].',
|
||||
'Group ID': '群组编号',
|
||||
'Groups': 'Groups',
|
||||
'Hello World': 'Hello World',
|
||||
'Home': 'Home',
|
||||
'How did you get here?': 'How did you get here?',
|
||||
'import': 'import',
|
||||
'Import/Export': '导入/导出',
|
||||
'Index': '索引',
|
||||
'insert new': '插入新纪录',
|
||||
'insert new %s': '插入新纪录 %s',
|
||||
'Installed applications': '已安裝应用程序',
|
||||
'Internal State': '內部状态',
|
||||
'Introduction': 'Introduction',
|
||||
'Invalid action': '非法操作(action)',
|
||||
'Invalid email': '不符合电子邮件格式',
|
||||
'Invalid Query': '无效的查询请求',
|
||||
'invalid request': '网络要求无效',
|
||||
'Is Active': 'Is Active',
|
||||
'Key': 'Key',
|
||||
'Language files (static strings) updated': '语言文件已更新',
|
||||
'Languages': '各国语言',
|
||||
'Last name': '姓',
|
||||
'Last saved on:': '最后保存时间:',
|
||||
'Layout': '网页布局',
|
||||
'Layout Plugins': 'Layout Plugins',
|
||||
'Layouts': 'Layouts',
|
||||
'License for': '软件授权',
|
||||
'Live Chat': 'Live Chat',
|
||||
'login': '登录',
|
||||
'Login': '登录',
|
||||
'Login to the Administrative Interface': '登录到管理员界面',
|
||||
'logout': '登出',
|
||||
'Logout': '登出',
|
||||
'Lost Password': '忘记密码',
|
||||
'Lost password?': '忘记密码?',
|
||||
'Main Menu': '主菜单',
|
||||
'Manage Cache': 'Manage Cache',
|
||||
'Menu Model': '菜单模型(menu)',
|
||||
'Models': '数据模型',
|
||||
'Modified By': '修改者',
|
||||
'Modified On': '修改时间',
|
||||
'Modules': '程序模块',
|
||||
'My Sites': 'My Sites',
|
||||
'Name': '名字',
|
||||
'New Record': '新记录',
|
||||
'new record inserted': '已插入新记录',
|
||||
'next 100 rows': '往后 100 笔',
|
||||
'NO': '否',
|
||||
'No databases in this application': '该应用程序不含数据库',
|
||||
'Object or table name': 'Object or table name',
|
||||
'Online examples': '点击进入在线例子',
|
||||
'or import from csv file': '或导入CSV文件',
|
||||
'Origin': '原文',
|
||||
'Original/Translation': '原文/翻译',
|
||||
'Other Plugins': 'Other Plugins',
|
||||
'Other Recipes': 'Other Recipes',
|
||||
'Overview': '概览',
|
||||
'Password': '密码',
|
||||
"Password fields don't match": '密码不匹配',
|
||||
'Peeking at file': '选择文件',
|
||||
'Plugins': 'Plugins',
|
||||
'Powered by': '基于下列技术构建:',
|
||||
'Preface': 'Preface',
|
||||
'previous 100 rows': '往前 100 笔',
|
||||
'Python': 'Python',
|
||||
'Query:': '查询:',
|
||||
'Quick Examples': 'Quick Examples',
|
||||
'RAM': 'RAM',
|
||||
'RAM Cache Keys': 'RAM Cache Keys',
|
||||
'Ram Cleared': 'Ram Cleared',
|
||||
'Recipes': 'Recipes',
|
||||
'Record': '记录',
|
||||
'record does not exist': '记录不存在',
|
||||
'Record ID': '记录编号',
|
||||
'Record id': '记录编号',
|
||||
'Register': '注册',
|
||||
'register': '注册',
|
||||
'Registration identifier': 'Registration identifier',
|
||||
'Registration key': '注册密钥',
|
||||
'reload': 'reload',
|
||||
'Remember me (for 30 days)': '记住我(30 天)',
|
||||
'Reset Password key': '重置密码',
|
||||
'Resolve Conflict file': '解决冲突文件',
|
||||
'Role': '角色',
|
||||
'Rows in Table': '在数据表里的记录',
|
||||
'Rows selected': '笔记录被选择',
|
||||
'Saved file hash:': '已保存文件的哈希值:',
|
||||
'Semantic': 'Semantic',
|
||||
'Services': 'Services',
|
||||
'Size of cache:': 'Size of cache:',
|
||||
'state': '状态',
|
||||
'Static files': '静态文件',
|
||||
'Statistics': '统计数据',
|
||||
'Stylesheet': '网页样式表',
|
||||
'submit': '提交',
|
||||
'Submit': '提交',
|
||||
'Support': 'Support',
|
||||
'Sure you want to delete this object?': '确定要删除此对象?',
|
||||
'Table': '数据表',
|
||||
'Table name': '数据表名称',
|
||||
'Testing application': '测试中的应用程序',
|
||||
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"query"应是类似 "db.table1.field1==\'value\'" 的条件表达式. "db.table1.field1==db.table2.field2"的形式则代表执行 JOIN SQL.',
|
||||
'The Core': 'The Core',
|
||||
'The output of the file is a dictionary that was rendered by the view %s': 'The output of the file is a dictionary that was rendered by the view %s',
|
||||
'The Views': '视图',
|
||||
'There are no controllers': '沒有控件(controllers)',
|
||||
'There are no models': '沒有数据库模型(models)',
|
||||
'There are no modules': '沒有程序模块(modules)',
|
||||
'There are no static files': '沒有静态文件',
|
||||
'There are no translators, only default language is supported': '沒有对应的语言文件,仅支持原始语言',
|
||||
'There are no views': '沒有视图',
|
||||
'This App': '该应用',
|
||||
'This is the %(filename)s template': '这是%(filename)s文件的模板(template)',
|
||||
'Ticket': '问题清单',
|
||||
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
|
||||
'Timestamp': '时间戳',
|
||||
'Twitter': 'Twitter',
|
||||
'Unable to check for upgrades': '查询新版本失败',
|
||||
'Unable to download': '无法下载',
|
||||
'Unable to download app': '无法下载应用程序',
|
||||
'unable to parse csv file': '无法解析CSV文件',
|
||||
'Update:': '更新:',
|
||||
'Upload existing application': '上传已有应用程序',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': '使用下列方式可得到更复杂的条件表达式, (...)&(...) 代表必须都满足, (...)|(...) 代表其一, ~(...)则代表否.',
|
||||
'User %(id)s Logged-in': '用户 %(id)s 已登录',
|
||||
'User %(id)s Registered': '用户 %(id)s 已注册',
|
||||
'User ID': '用户编号',
|
||||
'Verify Password': '验证密码',
|
||||
'Videos': '视频',
|
||||
'View': '查看',
|
||||
'Views': '视图',
|
||||
'Welcome': '欢迎',
|
||||
'Welcome %s': '欢迎 %s',
|
||||
'Welcome to web2py': '欢迎使用 web2py',
|
||||
'Welcome to web2py!': '欢迎使用 web2py!',
|
||||
'Which called the function %s located in the file %s': 'Which called the function %s located in the file %s',
|
||||
'YES': '是',
|
||||
'You are successfully running web2py': '您已成功运行 web2py',
|
||||
'You can modify this application and adapt it to your needs': '请根据您的需要修改本程序',
|
||||
'You visited the url %s': 'You visited the url %s',
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
# coding: utf8
|
||||
{
|
||||
'!langcode!': 'zh-cn',
|
||||
'!langname!': '中文',
|
||||
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"更新" 是選擇性的條件式, 格式就像 "欄位1=\'值\'". 但是 JOIN 的資料不可以使用 update 或是 delete"',
|
||||
'%s %%{row} deleted': '已刪除 %s 筆',
|
||||
'%s %%{row} updated': '已更新 %s 筆',
|
||||
'%s selected': '%s 已選擇',
|
||||
'%Y-%m-%d': '%Y-%m-%d',
|
||||
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
|
||||
'(something like "it-it")': '(格式類似 "zh-tw")',
|
||||
'A new version of web2py is available': '新版的 web2py 已發行',
|
||||
'A new version of web2py is available: %s': '新版的 web2py 已發行: %s',
|
||||
'about': '關於',
|
||||
'About': '關於',
|
||||
'About application': '關於本應用程式',
|
||||
'Access Control': 'Access Control',
|
||||
'Admin is disabled because insecure channel': '管理功能(Admin)在不安全連線環境下自動關閉',
|
||||
'Admin is disabled because unsecure channel': '管理功能(Admin)在不安全連線環境下自動關閉',
|
||||
'Administrative Interface': 'Administrative Interface',
|
||||
'Administrative interface': '點此處進入管理介面',
|
||||
'Administrator Password:': '管理員密碼:',
|
||||
'Ajax Recipes': 'Ajax Recipes',
|
||||
'An error occured, please %s the page': 'An error occured, please %s the page',
|
||||
'appadmin is disabled because insecure channel': '因為來自非安全通道,管理介面關閉',
|
||||
'Are you sure you want to delete file "%s"?': '確定要刪除檔案"%s"?',
|
||||
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
|
||||
'Are you sure you want to uninstall application "%s"': '確定要移除應用程式 "%s"',
|
||||
'Are you sure you want to uninstall application "%s"?': '確定要移除應用程式 "%s"',
|
||||
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': '注意: 登入管理帳號需要安全連線(HTTPS)或是在本機連線(localhost).',
|
||||
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': '注意: 因為在測試模式不保證多執行緒安全性,也就是說不可以同時執行多個測試案例',
|
||||
'ATTENTION: you cannot edit the running application!': '注意:不可編輯正在執行的應用程式!',
|
||||
'Authentication': '驗證',
|
||||
'Available Databases and Tables': '可提供的資料庫和資料表',
|
||||
'Buy this book': 'Buy this book',
|
||||
'cache': '快取記憶體',
|
||||
'Cache': 'Cache',
|
||||
'Cache Keys': 'Cache Keys',
|
||||
'Cannot be empty': '不可空白',
|
||||
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': '無法編譯:應用程式中含有錯誤,請除錯後再試一次.',
|
||||
'Change Password': '變更密碼',
|
||||
'change password': '變更密碼',
|
||||
'Check to delete': '打勾代表刪除',
|
||||
'Check to delete:': '點選以示刪除:',
|
||||
'Clear CACHE?': 'Clear CACHE?',
|
||||
'Clear DISK': 'Clear DISK',
|
||||
'Clear RAM': 'Clear RAM',
|
||||
'Client IP': '客戶端網址(IP)',
|
||||
'Community': 'Community',
|
||||
'Components and Plugins': 'Components and Plugins',
|
||||
'Controller': '控件',
|
||||
'Controllers': '控件',
|
||||
'Copyright': '版權所有',
|
||||
'Create new application': '創建應用程式',
|
||||
'Created By': 'Created By',
|
||||
'Created On': 'Created On',
|
||||
'Current request': '目前網路資料要求(request)',
|
||||
'Current response': '目前網路資料回應(response)',
|
||||
'Current session': '目前網路連線資訊(session)',
|
||||
'customize me!': '請調整我!',
|
||||
'data uploaded': '資料已上傳',
|
||||
'Database': '資料庫',
|
||||
'Database %s select': '已選擇 %s 資料庫',
|
||||
'Date and Time': '日期和時間',
|
||||
'db': 'db',
|
||||
'DB Model': '資料庫模組',
|
||||
'Delete': '刪除',
|
||||
'Delete:': '刪除:',
|
||||
'Demo': 'Demo',
|
||||
'Deploy on Google App Engine': '配置到 Google App Engine',
|
||||
'Deployment Recipes': 'Deployment Recipes',
|
||||
'Description': '描述',
|
||||
'DESIGN': '設計',
|
||||
'design': '設計',
|
||||
'Design for': '設計為了',
|
||||
'DISK': 'DISK',
|
||||
'Disk Cache Keys': 'Disk Cache Keys',
|
||||
'Disk Cleared': 'Disk Cleared',
|
||||
'Documentation': 'Documentation',
|
||||
"Don't know what to do?": "Don't know what to do?",
|
||||
'done!': '完成!',
|
||||
'Download': 'Download',
|
||||
'E-mail': '電子郵件',
|
||||
'EDIT': '編輯',
|
||||
'Edit': '編輯',
|
||||
'Edit application': '編輯應用程式',
|
||||
'Edit current record': '編輯當前紀錄',
|
||||
'edit profile': '編輯設定檔',
|
||||
'Edit Profile': '編輯設定檔',
|
||||
'Edit This App': '編輯本應用程式',
|
||||
'Editing file': '編輯檔案',
|
||||
'Editing file "%s"': '編輯檔案"%s"',
|
||||
'Email and SMS': 'Email and SMS',
|
||||
'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g',
|
||||
'Error logs for "%(app)s"': '"%(app)s"的錯誤紀錄',
|
||||
'Errors': 'Errors',
|
||||
'export as csv file': '以逗號分隔檔(csv)格式匯出',
|
||||
'FAQ': 'FAQ',
|
||||
'First name': '名',
|
||||
'Forgot username?': 'Forgot username?',
|
||||
'Forms and Validators': 'Forms and Validators',
|
||||
'Free Applications': 'Free Applications',
|
||||
'Functions with no doctests will result in [passed] tests.': '沒有 doctests 的函式會顯示 [passed].',
|
||||
'Group ID': '群組編號',
|
||||
'Groups': 'Groups',
|
||||
'Hello World': '嗨! 世界',
|
||||
'Home': 'Home',
|
||||
'How did you get here?': 'How did you get here?',
|
||||
'import': 'import',
|
||||
'Import/Export': '匯入/匯出',
|
||||
'Index': '索引',
|
||||
'insert new': '插入新資料',
|
||||
'insert new %s': '插入新資料 %s',
|
||||
'Installed applications': '已安裝應用程式',
|
||||
'Internal State': '內部狀態',
|
||||
'Introduction': 'Introduction',
|
||||
'Invalid action': '不合法的動作(action)',
|
||||
'Invalid email': '不合法的電子郵件',
|
||||
'Invalid Query': '不合法的查詢',
|
||||
'invalid request': '不合法的網路要求(request)',
|
||||
'Is Active': 'Is Active',
|
||||
'Key': 'Key',
|
||||
'Language files (static strings) updated': '語言檔已更新',
|
||||
'Languages': '各國語言',
|
||||
'Last name': '姓',
|
||||
'Last saved on:': '最後儲存時間:',
|
||||
'Layout': '網頁配置',
|
||||
'Layout Plugins': 'Layout Plugins',
|
||||
'Layouts': 'Layouts',
|
||||
'License for': '軟體版權為',
|
||||
'Live Chat': 'Live Chat',
|
||||
'login': '登入',
|
||||
'Login': '登入',
|
||||
'Login to the Administrative Interface': '登入到管理員介面',
|
||||
'logout': '登出',
|
||||
'Logout': '登出',
|
||||
'Lost Password': '密碼遺忘',
|
||||
'Lost password?': 'Lost password?',
|
||||
'Main Menu': '主選單',
|
||||
'Manage Cache': 'Manage Cache',
|
||||
'Menu Model': '選單模組(menu)',
|
||||
'Models': '資料模組',
|
||||
'Modified By': 'Modified By',
|
||||
'Modified On': 'Modified On',
|
||||
'Modules': '程式模組',
|
||||
'My Sites': 'My Sites',
|
||||
'Name': '名字',
|
||||
'New Record': '新紀錄',
|
||||
'new record inserted': '已插入新紀錄',
|
||||
'next 100 rows': '往後 100 筆',
|
||||
'NO': '否',
|
||||
'No databases in this application': '這應用程式不含資料庫',
|
||||
'Object or table name': 'Object or table name',
|
||||
'Online examples': '點此處進入線上範例',
|
||||
'or import from csv file': '或是從逗號分隔檔(CSV)匯入',
|
||||
'Origin': '原文',
|
||||
'Original/Translation': '原文/翻譯',
|
||||
'Other Plugins': 'Other Plugins',
|
||||
'Other Recipes': 'Other Recipes',
|
||||
'Overview': 'Overview',
|
||||
'Password': '密碼',
|
||||
"Password fields don't match": '密碼欄不匹配',
|
||||
'Peeking at file': '選擇檔案',
|
||||
'Plugins': 'Plugins',
|
||||
'Powered by': '基於以下技術構建:',
|
||||
'Preface': 'Preface',
|
||||
'previous 100 rows': '往前 100 筆',
|
||||
'Python': 'Python',
|
||||
'Query:': '查詢:',
|
||||
'Quick Examples': 'Quick Examples',
|
||||
'RAM': 'RAM',
|
||||
'RAM Cache Keys': 'RAM Cache Keys',
|
||||
'Ram Cleared': 'Ram Cleared',
|
||||
'Recipes': 'Recipes',
|
||||
'Record': '紀錄',
|
||||
'record does not exist': '紀錄不存在',
|
||||
'Record ID': '紀錄編號',
|
||||
'Record id': '紀錄編號',
|
||||
'Register': '註冊',
|
||||
'register': '註冊',
|
||||
'Registration identifier': 'Registration identifier',
|
||||
'Registration key': '註冊金鑰',
|
||||
'reload': 'reload',
|
||||
'Remember me (for 30 days)': '記住我(30 天)',
|
||||
'Reset Password key': '重設密碼',
|
||||
'Resolve Conflict file': '解決衝突檔案',
|
||||
'Role': '角色',
|
||||
'Rows in Table': '在資料表裏的資料',
|
||||
'Rows selected': '筆資料被選擇',
|
||||
'Saved file hash:': '檔案雜湊值已紀錄:',
|
||||
'Semantic': 'Semantic',
|
||||
'Services': 'Services',
|
||||
'Size of cache:': 'Size of cache:',
|
||||
'state': '狀態',
|
||||
'Static files': '靜態檔案',
|
||||
'Statistics': 'Statistics',
|
||||
'Stylesheet': '網頁風格檔',
|
||||
'submit': 'submit',
|
||||
'Submit': '傳送',
|
||||
'Support': 'Support',
|
||||
'Sure you want to delete this object?': '確定要刪除此物件?',
|
||||
'Table': '資料表',
|
||||
'Table name': '資料表名稱',
|
||||
'Testing application': '測試中的應用程式',
|
||||
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"查詢"是一個像 "db.表1.欄位1==\'值\'" 的條件式. 以"db.表1.欄位1==db.表2.欄位2"方式則相當於執行 JOIN SQL.',
|
||||
'The Core': 'The Core',
|
||||
'The output of the file is a dictionary that was rendered by the view %s': 'The output of the file is a dictionary that was rendered by the view %s',
|
||||
'The Views': 'The Views',
|
||||
'There are no controllers': '沒有控件(controllers)',
|
||||
'There are no models': '沒有資料庫模組(models)',
|
||||
'There are no modules': '沒有程式模組(modules)',
|
||||
'There are no static files': '沒有靜態檔案',
|
||||
'There are no translators, only default language is supported': '沒有翻譯檔,只支援原始語言',
|
||||
'There are no views': '沒有視圖',
|
||||
'This App': 'This App',
|
||||
'This is the %(filename)s template': '這是%(filename)s檔案的樣板(template)',
|
||||
'Ticket': '問題單',
|
||||
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
|
||||
'Timestamp': '時間標記',
|
||||
'Twitter': 'Twitter',
|
||||
'Unable to check for upgrades': '無法做升級檢查',
|
||||
'Unable to download': '無法下載',
|
||||
'Unable to download app': '無法下載應用程式',
|
||||
'unable to parse csv file': '無法解析逗號分隔檔(csv)',
|
||||
'Update:': '更新:',
|
||||
'Upload existing application': '更新存在的應用程式',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': '使用下列方式來組合更複雜的條件式, (...)&(...) 代表同時存在的條件, (...)|(...) 代表擇一的條件, ~(...)則代表反向條件.',
|
||||
'User %(id)s Logged-in': '使用者 %(id)s 已登入',
|
||||
'User %(id)s Registered': '使用者 %(id)s 已註冊',
|
||||
'User ID': '使用者編號',
|
||||
'Verify Password': '驗證密碼',
|
||||
'Videos': 'Videos',
|
||||
'View': '視圖',
|
||||
'Views': '視圖',
|
||||
'Welcome': 'Welcome',
|
||||
'Welcome %s': '歡迎 %s',
|
||||
'Welcome to web2py': '歡迎使用 web2py',
|
||||
'Welcome to web2py!': 'Welcome to web2py!',
|
||||
'Which called the function %s located in the file %s': 'Which called the function %s located in the file %s',
|
||||
'YES': '是',
|
||||
'You are successfully running web2py': 'You are successfully running web2py',
|
||||
'You can modify this application and adapt it to your needs': 'You can modify this application and adapt it to your needs',
|
||||
'You visited the url %s': 'You visited the url %s',
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# coding: utf8
|
||||
{
|
||||
'!langcode!': 'zh-cn',
|
||||
'!langcode!': 'zh-tw',
|
||||
'!langname!': '中文',
|
||||
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"更新" 是選擇性的條件式, 格式就像 "欄位1=\'值\'". 但是 JOIN 的資料不可以使用 update 或是 delete"',
|
||||
'%s %%{row} deleted': '已刪除 %s 筆',
|
||||
|
||||
@@ -136,3 +136,5 @@ def _():
|
||||
]
|
||||
)]
|
||||
if DEVELOPMENT_MENU: _()
|
||||
|
||||
if "auth" in locals(): auth.wikimenu()
|
||||
|
||||
@@ -92,7 +92,7 @@ div.flash {
|
||||
position:fixed;
|
||||
padding:10px;
|
||||
top:48px;
|
||||
right:50px;
|
||||
right:250px;
|
||||
min-width:280px;
|
||||
opacity:0.95;
|
||||
margin:0px 0px 10px 10px;
|
||||
@@ -191,6 +191,7 @@ div.error {
|
||||
.web2py_grid {width:100%}
|
||||
.web2py_grid table {width:100%}
|
||||
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
|
||||
.web2py_grid .web2py_form td {vertical-align: top;}
|
||||
|
||||
.web2py_grid thead th,.web2py_grid tfoot td {
|
||||
background-color:#EAEAEA;
|
||||
|
||||
@@ -8,21 +8,28 @@ jQuery(function(){
|
||||
if(jQuery(this).find('ul').length)
|
||||
jQuery(this).addClass('dropdown-submenu');
|
||||
});
|
||||
function adjust_height_of_collapsed_nav() {
|
||||
var cn = jQuery('div.collapse');
|
||||
if (cn.get(0)) {
|
||||
var cnh = cn.get(0).style.height;
|
||||
if (cnh>'0px'){
|
||||
cn.css('height','auto');
|
||||
}
|
||||
}
|
||||
}
|
||||
function hoverMenu(){
|
||||
var wid = document.documentElement.clientWidth; //faster than $(window).width() and cross browser
|
||||
if (wid>=980){
|
||||
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
|
||||
mi = jQuery(this).addClass('open');
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
|
||||
}, function(){
|
||||
mi = jQuery(this);
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
|
||||
});
|
||||
};
|
||||
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
|
||||
adjust_height_of_collapsed_nav();
|
||||
mi = jQuery(this).addClass('open');
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
|
||||
}, function(){
|
||||
mi = jQuery(this);
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
|
||||
});
|
||||
}
|
||||
hoverMenu(); // first page load
|
||||
jQuery(window).resize(hoverMenu); // on resize event
|
||||
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
|
||||
// make all buttons bootstrap buttons
|
||||
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
|
||||
});
|
||||
});
|
||||
@@ -91,8 +91,8 @@
|
||||
|
||||
<div class="container">
|
||||
<!-- Masthead ================================================== -->
|
||||
{{if response.title:}}
|
||||
<header class="mastheader row" id="header">
|
||||
{{if response.title:}}
|
||||
<div class="span12">
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
@@ -101,8 +101,8 @@
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
{{pass}}
|
||||
</header>
|
||||
{{pass}}
|
||||
|
||||
<section id="main" class="main row">
|
||||
{{if left_sidebar_enabled:}}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
var w2p_ajax_confirm_message = "{{=T('Are you sure you want to delete this object?')}}";
|
||||
var w2p_ajax_date_format = "{{=T('%Y-%m-%d')}}";
|
||||
var w2p_ajax_datetime_format = "{{=T('%Y-%m-%d %H:%M:%S')}}";
|
||||
var ajax_error_500 = '{{=XML(T('An error occured, please %s the page') % A(T('reload'), _href=URL(args=request.args, vars=request.vars))) }}'
|
||||
var ajax_error_500 = '{{=XML(T('An error occured, please %s the page') % A(T('reload'), _href=URL(args=request.args, vars=request.get_vars))) }}'
|
||||
//--></script>
|
||||
{{
|
||||
response.files.insert(0,URL('static','js/jquery.js'))
|
||||
|
||||
+82
-50
@@ -437,7 +437,7 @@ class Cache(object):
|
||||
# been accounted for
|
||||
logger.warning('no cache.disk (AttributeError)')
|
||||
|
||||
def client(self, time_expire=DEFAULT_TIME_EXPIRE, cache_model=None,
|
||||
def action(self, time_expire=DEFAULT_TIME_EXPIRE, cache_model=None,
|
||||
prefix=None, session=False, vars=True, lang=True,
|
||||
user_agent=False, public=True, valid_statuses=None,
|
||||
quick=None):
|
||||
@@ -461,60 +461,92 @@ class Cache(object):
|
||||
fast overrides with initial strings, e.g. 'SVLP' or 'VLP', or 'VLP'
|
||||
"""
|
||||
from gluon import current
|
||||
from gluon.http import HTTP
|
||||
def wrap(func):
|
||||
def wrapped_f():
|
||||
if current.request.env.request_method == 'GET':
|
||||
if time_expire:
|
||||
cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire)
|
||||
if quick:
|
||||
session_ = True if 'S' in quick else False
|
||||
vars_ = True if 'V' in quick else False
|
||||
lang_ = True if 'L' in quick else False
|
||||
user_agent_ = True if 'U' in quick else False
|
||||
public_ = True if 'P' in quick else False
|
||||
else:
|
||||
session_, vars_, lang_, user_agent_, public_ = session, vars, lang, user_agent, public
|
||||
if not session_ and public_:
|
||||
cache_control += ', public'
|
||||
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
vary = None
|
||||
else:
|
||||
cache_control += ', private'
|
||||
expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
|
||||
if cache_model:
|
||||
cache_key = [current.request.env.path_info, current.response.view]
|
||||
if session_:
|
||||
cache_key.append(current.response.session_id)
|
||||
elif user_agent_:
|
||||
if user_agent_ is True:
|
||||
cache_key.append("%(is_mobile)s_%(is_tablet)s" % current.request.user_agent())
|
||||
else:
|
||||
cache_key.append(str(user_agent_.items()))
|
||||
if vars_:
|
||||
cache_key.append(current.request.env.query_string)
|
||||
if lang_:
|
||||
cache_key.append(current.T.accepted_language)
|
||||
cache_key = hashlib.md5('__'.join(cache_key)).hexdigest()
|
||||
if prefix:
|
||||
cache_key = prefix + cache_key
|
||||
rtn = cache_model(cache_key, lambda : func(), time_expire=time_expire)
|
||||
if current.request.env.request_method != 'GET':
|
||||
return func()
|
||||
if time_expire:
|
||||
cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire)
|
||||
if quick:
|
||||
session_ = True if 'S' in quick else False
|
||||
vars_ = True if 'V' in quick else False
|
||||
lang_ = True if 'L' in quick else False
|
||||
user_agent_ = True if 'U' in quick else False
|
||||
public_ = True if 'P' in quick else False
|
||||
else:
|
||||
session_, vars_, lang_, user_agent_, public_ = session, vars, lang, user_agent, public
|
||||
if not session_ and public_:
|
||||
cache_control += ', public'
|
||||
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
vary = None
|
||||
else:
|
||||
cache_control += ', private'
|
||||
expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
|
||||
if cache_model:
|
||||
#figure out the correct cache key
|
||||
cache_key = [current.request.env.path_info, current.response.view]
|
||||
if session_:
|
||||
cache_key.append(current.response.session_id)
|
||||
elif user_agent_:
|
||||
if user_agent_ is True:
|
||||
cache_key.append("%(is_mobile)s_%(is_tablet)s" % current.request.user_agent())
|
||||
else:
|
||||
cache_key.append(str(user_agent_.items()))
|
||||
if vars_:
|
||||
cache_key.append(current.request.env.query_string)
|
||||
if lang_:
|
||||
cache_key.append(current.T.accepted_language)
|
||||
cache_key = hashlib.md5('__'.join(cache_key)).hexdigest()
|
||||
if prefix:
|
||||
cache_key = prefix + cache_key
|
||||
try:
|
||||
#action returns something
|
||||
rtn = cache_model(cache_key, lambda : func(), time_expire=time_expire)
|
||||
http, status = None, current.response.status
|
||||
except HTTP, e:
|
||||
#action raises HTTP (can still be valid)
|
||||
rtn = cache_model(cache_key, lambda : e.body, time_expire=time_expire)
|
||||
http, status = HTTP(e.status, rtn, **e.headers), e.status
|
||||
else:
|
||||
#action raised a generic exception
|
||||
http = None
|
||||
else:
|
||||
#no server-cache side involved
|
||||
try:
|
||||
#action returns something
|
||||
rtn = func()
|
||||
send_headers = False
|
||||
if isinstance(valid_statuses, list):
|
||||
if current.response.status in valid_statuses:
|
||||
send_headers = True
|
||||
elif valid_statuses is None:
|
||||
if str(current.response.status)[0] in '123':
|
||||
send_headers = True
|
||||
http, status = None, current.response.status
|
||||
except HTTP, e:
|
||||
#action raises HTTP (can still be valid)
|
||||
status = e.status
|
||||
http = HTTP(e.status, e.body, **e.headers)
|
||||
else:
|
||||
#action raised a generic exception
|
||||
http = None
|
||||
send_headers = False
|
||||
if http and isinstance(valid_statuses, list):
|
||||
if status in valid_statuses:
|
||||
send_headers = True
|
||||
elif valid_statuses is None:
|
||||
if str(status)[0] in '123':
|
||||
send_headers = True
|
||||
if send_headers:
|
||||
headers = {
|
||||
'Pragma' : None,
|
||||
'Expires' : expires,
|
||||
'Cache-Control' : cache_control
|
||||
}
|
||||
current.response.headers.update(headers)
|
||||
if cache_model and not send_headers:
|
||||
#we cached already the value, but the status is not valid
|
||||
#so we need to delete the cached value
|
||||
cache_model(cache_key, None)
|
||||
if http:
|
||||
if send_headers:
|
||||
current.response.headers['Pragma'] = None
|
||||
current.response.headers['Expires'] = expires
|
||||
current.response.headers['Cache-Control'] = cache_control
|
||||
if cache_model and not send_headers:
|
||||
cache_model(cache_key, None)
|
||||
return rtn
|
||||
return func()
|
||||
http.headers.update(current.response.headers)
|
||||
raise http
|
||||
return rtn
|
||||
wrapped_f.__name__ = func.__name__
|
||||
wrapped_f.__doc__ = func.__doc__
|
||||
return wrapped_f
|
||||
|
||||
@@ -443,10 +443,10 @@ CONTENT_TYPE = {
|
||||
'.odp': 'application/vnd.oasis.opendocument.presentation',
|
||||
'.ods': 'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'.odt': 'application/vnd.oasis.opendocument.text',
|
||||
'.oga': 'audio/x-speex+ogg',
|
||||
'.ogg': 'video/x-theora+ogg',
|
||||
'.oga': 'audio/ogg',
|
||||
'.ogg': 'application/ogg',
|
||||
'.ogm': 'video/x-ogm+ogg',
|
||||
'.ogv': 'video/x-theora+ogg',
|
||||
'.ogv': 'video/ogg',
|
||||
'.ogx': 'application/ogg',
|
||||
'.old': 'application/x-trash',
|
||||
'.oleo': 'application/x-oleo',
|
||||
@@ -518,7 +518,7 @@ CONTENT_TYPE = {
|
||||
'.pln': 'application/x-planperfect',
|
||||
'.pls': 'audio/x-scpls',
|
||||
'.pm': 'application/x-perl',
|
||||
'.png': 'image/x-apple-ios-png',
|
||||
'.png': 'image/png',
|
||||
'.pnm': 'image/x-portable-anymap',
|
||||
'.pntg': 'image/x-macpaint',
|
||||
'.po': 'text/x-gettext-translation',
|
||||
|
||||
@@ -815,7 +815,7 @@ class Connection(object):
|
||||
outrec = Record(FCGI_UNKNOWN_TYPE)
|
||||
outrec.contentData = struct.pack(FCGI_UnknownTypeBody, inrec.type)
|
||||
outrec.contentLength = FCGI_UnknownTypeBody_LEN
|
||||
self.writeRecord(rec)
|
||||
self.writeRecord(outrec)
|
||||
|
||||
class MultiplexedConnection(Connection):
|
||||
"""
|
||||
|
||||
@@ -51,7 +51,7 @@ def THUMB(image, nx=120, ny=120, gae=False, name='thumb'):
|
||||
request = current.request
|
||||
from PIL import Image
|
||||
import os
|
||||
img = Image.open(request.folder + 'uploads/' + image)
|
||||
img = Image.open(os.path.join(request.folder,'uploads',image))
|
||||
img.thumbnail((nx, ny), Image.ANTIALIAS)
|
||||
root, ext = os.path.splitext(image)
|
||||
thumb = '%s_%s%s' % (root, name, ext)
|
||||
|
||||
@@ -94,9 +94,8 @@ class DropboxAccount(object):
|
||||
return form
|
||||
|
||||
def logout_url(self, next="/"):
|
||||
current.session.dropbox_request_token = None
|
||||
self.sess.unlink()
|
||||
current.session.auth = None
|
||||
redirect('https://www.dropbox.com/logout')
|
||||
return next
|
||||
|
||||
def get_client(self):
|
||||
@@ -106,15 +105,15 @@ class DropboxAccount(object):
|
||||
|
||||
def put(self, filename, file):
|
||||
if not hasattr(self,'client'): self.get_client()
|
||||
return json.loads(self.client.put_file(filename, file))['bytes']
|
||||
return self.client.put_file(filename, file)['bytes']
|
||||
|
||||
def get(self, filename, file):
|
||||
def get(self, filename):
|
||||
if not hasattr(self,'client'): self.get_client()
|
||||
return self.client.get_file(filename)
|
||||
|
||||
def dir(self, path):
|
||||
if not hasattr(self,'client'): self.get_client()
|
||||
return json.loads(self.client.metadata(path))
|
||||
return self.client.metadata(path)
|
||||
|
||||
|
||||
def use_dropbox(auth, filename='private/dropbox.key', **kwargs):
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf8
|
||||
|
||||
"""
|
||||
LoginRadius Authentication for web2py
|
||||
Developed by Nathan Freeze (Copyright © 2013)
|
||||
Email <nathan@freezable.com>
|
||||
|
||||
This file contains code to allow using loginradius.com
|
||||
authentication services with web2py
|
||||
"""
|
||||
|
||||
import os
|
||||
from gluon import *
|
||||
from gluon.storage import Storage
|
||||
from gluon.contrib.simplejson import JSONDecodeError
|
||||
from gluon.tools import fetch
|
||||
import gluon.contrib.simplejson as json
|
||||
|
||||
|
||||
class LoginRadiusAccount(object):
|
||||
"""
|
||||
from gluon.contrib.login_methods.loginradius_account import LoginRadiusAccount
|
||||
auth.settings.actions_disabled=['register','change_password',
|
||||
'request_reset_password']
|
||||
auth.settings.login_form = LoginRadiusAccount(request,
|
||||
api_key="...",
|
||||
api_secret="...",
|
||||
url = "http://localhost:8000/%s/default/user/login" % request.application)
|
||||
"""
|
||||
|
||||
def __init__(self, request, api_key="", api_secret="",
|
||||
url=None, on_login_failure=None):
|
||||
|
||||
self.request = request
|
||||
self.api_key = api_key
|
||||
self.api_secret = api_secret
|
||||
self.url = url
|
||||
self.auth_base_url = "https://hub.loginradius.com/UserProfile.ashx/"
|
||||
self.profile = None
|
||||
self.on_login_failure = on_login_failure
|
||||
self.mappings = Storage()
|
||||
|
||||
def defaultmapping(profile):
|
||||
first_name = profile.get('FirstName')
|
||||
last_name = profile.get('LastName')
|
||||
email = profile.get('Email', [{}])[0].get('Value')
|
||||
reg_id = profile.get('ID', '')
|
||||
username = profile.get('ProfileName', email)
|
||||
|
||||
return dict(registration_id=reg_id, username=username, email=email,
|
||||
first_name=first_name, last_name=last_name)
|
||||
|
||||
self.mappings.default = defaultmapping
|
||||
|
||||
def get_user(self):
|
||||
request = self.request
|
||||
user = None
|
||||
if request.vars.token:
|
||||
try:
|
||||
auth_url = self.auth_base_url + self.api_secret + "/" + request.vars.token
|
||||
json_data = fetch(auth_url, headers={'User-Agent': "LoginRadius - Python - SDK"})
|
||||
self.profile = json.loads(json_data)
|
||||
provider = self.profile['Provider']
|
||||
mapping = self.mappings.get(provider, self.mappings['default'])
|
||||
user = mapping(self.profile)
|
||||
except (JSONDecodeError, KeyError):
|
||||
pass
|
||||
if user is None and self.on_login_failure:
|
||||
redirect(self.on_login_failure)
|
||||
return user
|
||||
|
||||
def login_form(self):
|
||||
loginradius_url = "https://hub.loginradius.com/include/js/LoginRadius.js"
|
||||
loginradius_lib = SCRIPT(_src=loginradius_url, _type='text/javascript')
|
||||
container = DIV(_id="interfacecontainerdiv", _class='interfacecontainerdiv')
|
||||
widget = SCRIPT("""var options={}; options.login=true;
|
||||
LoginRadius_SocialLogin.util.ready(function () {
|
||||
$ui = LoginRadius_SocialLogin.lr_login_settings;
|
||||
$ui.interfacesize = "";$ui.apikey = "%s";
|
||||
$ui.callback=""; $ui.lrinterfacecontainer ="interfacecontainerdiv";
|
||||
LoginRadius_SocialLogin.init(options); });""" % self.api_key)
|
||||
form = DIV(container, loginradius_lib, widget)
|
||||
return form
|
||||
|
||||
|
||||
def use_loginradius(auth, filename='private/loginradius.key', **kwargs):
|
||||
path = os.path.join(current.request.folder, filename)
|
||||
if os.path.exists(path):
|
||||
request = current.request
|
||||
domain, public_key, private_key = open(path, 'r').read().strip().split(':')
|
||||
url = URL('default', 'user', args='login', scheme=True)
|
||||
auth.settings.actions_disabled = \
|
||||
['register', 'change_password', 'request_reset_password']
|
||||
auth.settings.login_form = LoginRadiusAccount(
|
||||
request, api_key=public_key, api_secret=private_key,
|
||||
url=url, **kwargs)
|
||||
@@ -176,7 +176,7 @@ class OAuthAccount(object):
|
||||
|
||||
HTTP = self.globals['HTTP']
|
||||
|
||||
raise HTTP(307,
|
||||
raise HTTP(302,
|
||||
"You are not authenticated: you are being redirected to the <a href='" + auth_request_url + "'> authentication server</a>",
|
||||
Location=auth_request_url)
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ server for requests. It can be used for the optional"scope" parameters for Face
|
||||
if self.args:
|
||||
data.update(self.args)
|
||||
auth_request_url = self.auth_url + "?" + urlencode(data)
|
||||
raise HTTP(307,
|
||||
raise HTTP(302,
|
||||
"You are not authenticated: you are being redirected to the <a href='" + auth_request_url + "'> authentication server</a>",
|
||||
Location=auth_request_url)
|
||||
return
|
||||
|
||||
@@ -544,10 +544,10 @@ regex_list=re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$')
|
||||
regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
|
||||
regex_tq=re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
|
||||
regex_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+)', re.M)
|
||||
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.]+)',re.M)
|
||||
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w)',re.M)
|
||||
regex_link=re.compile(r'('+LINK+r')|\[\[(?P<s>.+?)\]\]',re.S)
|
||||
regex_link_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$',re.S)
|
||||
regex_media_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio)(?:\s+(?P<w>\d+px))?\s*$',re.S)
|
||||
regex_media_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',re.S)
|
||||
|
||||
regex_markmin_escape = re.compile(r"(\\*)(['`:*~\\[\]{}@\$+\-.#\n])")
|
||||
regex_backslash = re.compile(r"\\(['`:*~\\[\]{}@\$+\-.#\n])")
|
||||
@@ -859,6 +859,8 @@ def render(text,
|
||||
if autolinks=="default": autolinks = autolinks_simple
|
||||
if protolinks=="default": protolinks = protolinks_simple
|
||||
pp='\n' if pretty_print else ''
|
||||
if isinstance(text,unicode):
|
||||
text = text.encode('utf8')
|
||||
text = str(text or '')
|
||||
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
|
||||
text = text.replace('\x05','') # concatenate strings separeted by \\n
|
||||
@@ -1242,8 +1244,18 @@ def render(text,
|
||||
if p == 'center':
|
||||
p_begin = '<p style="text-align:center">'
|
||||
p_end = '</p>'+pp
|
||||
elif p == 'blockleft':
|
||||
p_begin = '<p style="text-align:left">'
|
||||
p_end = '</p>'+pp
|
||||
elif p == 'blockright':
|
||||
p_begin = '<p style="text-align:right">'
|
||||
p_end = '</p>'+pp
|
||||
elif p in ('left','right'):
|
||||
style = ('float:%s' % p)+(';%s' % style if style else '')
|
||||
if t and regex_auto.match(t):
|
||||
p_begin = p_begin + '<a href="%s">' % t
|
||||
p_end = '</a>' + p_end
|
||||
t = ''
|
||||
if style:
|
||||
style = ' style="%s"' % style
|
||||
if p in ('video','audio'):
|
||||
|
||||
@@ -80,7 +80,7 @@ class MemcacheClientObj(Client):
|
||||
newKey = self.__keyFormat__(key)
|
||||
obj = Client.get(self, newKey)
|
||||
if obj:
|
||||
if isinstance(obj,(int,double,long)):
|
||||
if isinstance(obj,(int,float,long)):
|
||||
return Client.incr(self, newKey, value)
|
||||
else:
|
||||
value += obj[1]
|
||||
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf8 -*-
|
||||
# Plural-Forms for id (Indonesian)
|
||||
|
||||
nplurals=2 # Indonesian language has 2 forms:
|
||||
# 1 singular and 1 plural
|
||||
|
||||
# Determine plural_id for number *n* as sequence of positive
|
||||
# integers: 0,1,...
|
||||
# NOTE! For singular form ALWAYS return plural_id = 0
|
||||
get_plural_id = lambda n: int(n != 1)
|
||||
|
||||
# Construct and return plural form of *word* using
|
||||
# *plural_id* (which ALWAYS>0). This function will be executed
|
||||
# for words (or phrases) not found in plural_dict dictionary
|
||||
# construct_plural_form = lambda word, plural_id: (word + 'suffix')
|
||||
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf8 -*-
|
||||
# Plural-Forms for id (Malay)
|
||||
|
||||
nplurals=2 # Malay language has 2 forms:
|
||||
# 1 singular and 1 plural
|
||||
|
||||
# Determine plural_id for number *n* as sequence of positive
|
||||
# integers: 0,1,...
|
||||
# NOTE! For singular form ALWAYS return plural_id = 0
|
||||
get_plural_id = lambda n: int(n != 1)
|
||||
|
||||
# Construct and return plural form of *word* using
|
||||
# *plural_id* (which ALWAYS>0). This function will be executed
|
||||
# for words (or phrases) not found in plural_dict dictionary
|
||||
# construct_plural_form = lambda word, plural_id: (word + 'suffix')
|
||||
|
||||
+83
-26
File diff suppressed because one or more lines are too long
+137
-43
@@ -1,8 +1,7 @@
|
||||
"""
|
||||
Developed by 616d41631bff906704951934ffe4015e
|
||||
Developed by niphlod@gmail.com
|
||||
Released under web2py license because includes gluon/cache.py source code
|
||||
"""
|
||||
|
||||
import redis
|
||||
from redis.exceptions import ConnectionError
|
||||
from gluon import current
|
||||
@@ -12,6 +11,8 @@ import time
|
||||
import re
|
||||
import logging
|
||||
import thread
|
||||
import random
|
||||
|
||||
|
||||
logger = logging.getLogger("web2py.cache.redis")
|
||||
|
||||
@@ -23,15 +24,42 @@ def RedisCache(*args, **vars):
|
||||
Usage example: put in models
|
||||
|
||||
from gluon.contrib.redis_cache import RedisCache
|
||||
cache.redis = RedisCache('localhost:6379',db=None, debug=True)
|
||||
cache.redis = RedisCache('localhost:6379',db=None, debug=True, with_lock=True)
|
||||
|
||||
:param db: redis db to use (0..16)
|
||||
:param debug: if True adds to stats() the total_hits and misses
|
||||
:param with_lock: sets the default locking mode for creating new keys.
|
||||
By default is False (usualy when you choose Redis you do it
|
||||
for performances reason)
|
||||
When True, only one thread/process can set a value concurrently
|
||||
|
||||
When you use cache.redis directly you can use
|
||||
value = cache.redis('mykey', lambda: time.time(), with_lock=True)
|
||||
to enforce locking. The with_lock parameter overrides the one set in the
|
||||
cache.redis instance creation
|
||||
|
||||
cache.redis.stats()
|
||||
returns a dictionary with statistics of Redis server
|
||||
with one additional key ('w2p_keys') showing all keys currently set
|
||||
from web2py with their TTL
|
||||
|
||||
return a dictionary with statistics of Redis server
|
||||
with one additional key ('w2p_keys') showing all keys currently set
|
||||
from web2py with their TTL
|
||||
if debug=True additional tracking is activate and another key is added
|
||||
('w2p_stats') showing total_hits and misses
|
||||
A little wording on how keys are stored (and why the cache_it() function
|
||||
and the clear() one look a little bit convoluted): there are a lot of
|
||||
libraries that just store values and then use the KEYS command to delete it.
|
||||
Until recent releases of this module, that technique was used here too.
|
||||
In the need of deleting specific keys in a database with zillions keys in it
|
||||
(other web2py apps, other applications in the need of a Redis stack) the
|
||||
KEYS command is slow (it needs to scan every key in the database).
|
||||
So, we use Redis 'sets' to store keys in "buckets"...
|
||||
- every key created gets "indexed" in a bucket
|
||||
- all buckets are indexed in a fixed key that never expires
|
||||
- all keys generated within the same minute go in the same bucket
|
||||
- every bucket is then set to expire when every key within it is expired
|
||||
When we need to clear() cached keys:
|
||||
- we tell Redis to SUNION all buckets
|
||||
- gives us just the keys that are not expired yet
|
||||
- buckets that are expired are removed from the fixed set
|
||||
- we scan the keys and then delete them
|
||||
"""
|
||||
|
||||
locker.acquire()
|
||||
@@ -49,13 +77,15 @@ class RedisClient(object):
|
||||
MAX_RETRIES = 5
|
||||
RETRIES = 0
|
||||
|
||||
def __init__(self, server='localhost:6379', db=None, debug=False):
|
||||
def __init__(self, server='localhost:6379', db=None, debug=False, with_lock=False):
|
||||
self.server = server
|
||||
self.db = db or 0
|
||||
host, port = (self.server.split(':') + ['6379'])[:2]
|
||||
port = int(port)
|
||||
self.request = current.request
|
||||
self.debug = debug
|
||||
self.with_lock = with_lock
|
||||
self.prefix = "w2p:%s:" % (self.request.application)
|
||||
if self.request:
|
||||
app = self.request.application
|
||||
else:
|
||||
@@ -70,67 +100,112 @@ class RedisClient(object):
|
||||
else:
|
||||
self.storage = self.meta_storage[app]
|
||||
|
||||
self.cache_set_key = 'w2p:%s:___cache_set' % (self.request.application)
|
||||
|
||||
self.r_server = redis.Redis(host=host, port=port, db=self.db)
|
||||
|
||||
def __call__(self, key, f, time_expire=300):
|
||||
def __call__(self, key, f, time_expire=300, with_lock=None):
|
||||
if with_lock is None:
|
||||
with_lock = self.with_lock
|
||||
if time_expire is None:
|
||||
time_expire = 24 * 60 * 60
|
||||
newKey = self.__keyFormat__(key)
|
||||
value = None
|
||||
ttl = 0
|
||||
try:
|
||||
if time_expire is None:
|
||||
time_expire = 24 * 60 * 60
|
||||
newKey = self.__keyFormat__(key)
|
||||
value = None
|
||||
#is there a value
|
||||
obj = self.r_server.get(newKey)
|
||||
ttl = self.r_server.ttl(newKey) or 0
|
||||
#what's its ttl
|
||||
if obj:
|
||||
ttl = self.r_server.ttl(newKey)
|
||||
if ttl > time_expire:
|
||||
obj = None
|
||||
if obj:
|
||||
#was cached
|
||||
if self.debug:
|
||||
self.r_server.incr('web2py_cache_statistics:hit_total')
|
||||
value = pickle.loads(obj)
|
||||
elif f is None:
|
||||
#delete and never look back
|
||||
self.r_server.delete(newKey)
|
||||
else:
|
||||
if self.debug:
|
||||
self.r_server.incr('web2py_cache_statistics:misses')
|
||||
value = f()
|
||||
if time_expire == 0:
|
||||
time_expire = 1
|
||||
self.r_server.setex(newKey, pickle.dumps(value), time_expire)
|
||||
#naive distributed locking
|
||||
if with_lock:
|
||||
lock_key = '%s:__lock' % newKey
|
||||
try:
|
||||
while True:
|
||||
lock = self.r_server.setnx(lock_key, 1)
|
||||
if lock:
|
||||
value = self.cache_it(newKey, f, time_expire)
|
||||
break
|
||||
else:
|
||||
time.sleep(0.2)
|
||||
#did someone else create it in the meanwhile ?
|
||||
obj = self.r_server.get(newKey)
|
||||
if obj:
|
||||
value = pickle.loads(obj)
|
||||
break
|
||||
finally:
|
||||
self.r_server.delete(lock_key)
|
||||
else:
|
||||
#without distributed locking
|
||||
value = self.cache_it(newKey, f, time_expire)
|
||||
return value
|
||||
except ConnectionError:
|
||||
return self.retry_call(key, f, time_expire)
|
||||
return self.retry_call(key, f, time_expire, with_lock)
|
||||
|
||||
def retry_call(self, key, f, time_expire):
|
||||
def cache_it(self, key, f, time_expire):
|
||||
if self.debug:
|
||||
self.r_server.incr('web2py_cache_statistics:misses')
|
||||
cache_set_key = self.cache_set_key
|
||||
expireat = int(time.time() + time_expire) + 120
|
||||
bucket_key = "%s:%s" % (cache_set_key, expireat / 60)
|
||||
value = f()
|
||||
value_ = pickle.dumps(value)
|
||||
if time_expire == 0:
|
||||
time_expire = 1
|
||||
self.r_server.setex(key, value_, time_expire)
|
||||
#print '%s will expire on %s: it goes in bucket %s' % (key, time.ctime(expireat))
|
||||
#print 'that will expire on %s' % (bucket_key, time.ctime(((expireat/60) + 1)*60))
|
||||
p = self.r_server.pipeline()
|
||||
#add bucket to the fixed set
|
||||
p.sadd(cache_set_key, bucket_key)
|
||||
#sets the key
|
||||
p.setex(key, value_, time_expire)
|
||||
#add the key to the bucket
|
||||
p.sadd(bucket_key, key)
|
||||
#expire the bucket properly
|
||||
p.expireat(bucket_key, ((expireat/60) + 1)*60)
|
||||
p.execute()
|
||||
return value
|
||||
|
||||
def retry_call(self, key, f, time_expire, with_locking):
|
||||
self.RETRIES += 1
|
||||
if self.RETRIES <= self.MAX_RETRIES:
|
||||
logger.error("sleeping %s seconds before reconnecting" %
|
||||
(2 * self.RETRIES))
|
||||
time.sleep(2 * self.RETRIES)
|
||||
self.__init__(self.server, self.db, self.debug)
|
||||
return self.__call__(key, f, time_expire)
|
||||
self.__init__(self.server, self.db, self.debug, self.with_lock)
|
||||
return self.__call__(key, f, time_expire, with_locking)
|
||||
else:
|
||||
self.RETRIES = 0
|
||||
raise ConnectionError('Redis instance is unavailable at %s' % (
|
||||
self.server))
|
||||
|
||||
def increment(self, key, value=1, time_expire=300):
|
||||
def increment(self, key, value=1):
|
||||
try:
|
||||
newKey = self.__keyFormat__(key)
|
||||
obj = self.r_server.get(newKey)
|
||||
if obj:
|
||||
return self.r_server.incr(newKey, value)
|
||||
else:
|
||||
self.r_server.setex(newKey, value, time_expire)
|
||||
return value
|
||||
return self.r_server.incr(newKey, value)
|
||||
except ConnectionError:
|
||||
return self.retry_increment(key, value, time_expire)
|
||||
return self.retry_increment(key, value)
|
||||
|
||||
def retry_increment(self, key, value, time_expire):
|
||||
def retry_increment(self, key, value):
|
||||
self.RETRIES += 1
|
||||
if self.RETRIES <= self.MAX_RETRIES:
|
||||
logger.error("sleeping some seconds before reconnecting")
|
||||
time.sleep(2 * self.RETRIES)
|
||||
self.__init__(self.server, self.db, self.debug)
|
||||
return self.increment(key, value, time_expire)
|
||||
self.__init__(self.server, self.db, self.debug, self.with_lock)
|
||||
return self.increment(key, value)
|
||||
else:
|
||||
self.RETRIES = 0
|
||||
raise ConnectionError('Redis instance is unavailable at %s' % (
|
||||
@@ -142,14 +217,34 @@ class RedisClient(object):
|
||||
clear cache entries
|
||||
"""
|
||||
r = re.compile(regex)
|
||||
prefix = "w2p:%s:" % (self.request.application)
|
||||
#get all buckets
|
||||
buckets = self.r_server.smembers(self.cache_set_key)
|
||||
#get all keys in buckets
|
||||
if buckets:
|
||||
keys = self.r_server.sunion(buckets)
|
||||
else:
|
||||
return
|
||||
prefix = self.prefix
|
||||
pipe = self.r_server.pipeline()
|
||||
for a in self.r_server.keys("%s*" %
|
||||
(prefix)):
|
||||
for a in keys:
|
||||
if r.match(str(a).replace(prefix, '', 1)):
|
||||
pipe.delete(a)
|
||||
if random.randrange(0,100) < 10:
|
||||
#do this just once in a while (10% chance)
|
||||
self.clear_buckets(buckets)
|
||||
pipe.execute()
|
||||
|
||||
def clear_buckets(self, buckets):
|
||||
p = self.r_server.pipeline()
|
||||
for b in buckets:
|
||||
if not self.r_server.exists(b):
|
||||
p.srem(self.cache_set_key, b)
|
||||
p.execute()
|
||||
|
||||
def delete(self, key):
|
||||
newKey = self.__keyFormat__(key)
|
||||
return self.r_server.delete(newKey)
|
||||
|
||||
def stats(self):
|
||||
statscollector = self.r_server.info()
|
||||
if self.debug:
|
||||
@@ -159,12 +254,11 @@ class RedisClient(object):
|
||||
misses=self.r_server.get('web2py_cache_statistics:misses')
|
||||
)
|
||||
statscollector['w2p_keys'] = dict()
|
||||
|
||||
for a in self.r_server.keys("w2p:%s:*" % (
|
||||
self.request.application)):
|
||||
statscollector['w2p_keys']["%s_expire_in_sec" % (a)] = \
|
||||
self.r_server.ttl(a)
|
||||
statscollector['w2p_keys']["%s_expire_in_sec" % (a)] = self.r_server.ttl(a)
|
||||
return statscollector
|
||||
|
||||
def __keyFormat__(self, key):
|
||||
return 'w2p:%s:%s' % (self.request.application,
|
||||
key.replace(' ', '_'))
|
||||
return '%s%s' % (self.prefix, key.replace(' ', '_'))
|
||||
|
||||
@@ -29,11 +29,12 @@ def RedisSession(*args, **vars):
|
||||
|
||||
locker.acquire()
|
||||
try:
|
||||
if not hasattr(RedisSession, 'redis_instance'):
|
||||
RedisSession.redis_instance = RedisClient(*args, **vars)
|
||||
instance_name = 'redis_instance_' + current.request.application
|
||||
if not hasattr(RedisSession, instance_name):
|
||||
setattr(RedisSession, instance_name, RedisClient(*args, **vars))
|
||||
return getattr(RedisSession, instance_name)
|
||||
finally:
|
||||
locker.release()
|
||||
return RedisSession.redis_instance
|
||||
|
||||
|
||||
class RedisClient(object):
|
||||
@@ -41,8 +42,10 @@ class RedisClient(object):
|
||||
meta_storage = {}
|
||||
MAX_RETRIES = 5
|
||||
RETRIES = 0
|
||||
_release_script = None
|
||||
|
||||
def __init__(self, server='localhost:6379', db=None, debug=False, session_expiry=False):
|
||||
def __init__(self, server='localhost:6379', db=None, debug=False,
|
||||
session_expiry=False, with_lock=False):
|
||||
"""session_expiry can be an integer, in seconds, to set the default expiration
|
||||
of sessions. The corresponding record will be deleted from the redis instance,
|
||||
and there's virtually no need to run sessions2trash.py
|
||||
@@ -57,8 +60,12 @@ class RedisClient(object):
|
||||
else:
|
||||
self.app = ''
|
||||
self.r_server = redis.Redis(host=host, port=port, db=self.db)
|
||||
if with_lock:
|
||||
RedisClient._release_script = \
|
||||
self.r_server.register_script(_LUA_RELEASE_LOCK)
|
||||
self.tablename = None
|
||||
self.session_expiry = session_expiry
|
||||
self.with_lock = with_lock
|
||||
|
||||
def get(self, what, default):
|
||||
return self.tablename
|
||||
@@ -70,7 +77,8 @@ class RedisClient(object):
|
||||
def define_table(self, tablename, *fields, **args):
|
||||
if not self.tablename:
|
||||
self.tablename = MockTable(
|
||||
self, self.r_server, tablename, self.session_expiry)
|
||||
self, self.r_server, tablename, self.session_expiry,
|
||||
self.with_lock)
|
||||
return self.tablename
|
||||
|
||||
def __getitem__(self, key):
|
||||
@@ -87,7 +95,7 @@ class RedisClient(object):
|
||||
|
||||
class MockTable(object):
|
||||
|
||||
def __init__(self, db, r_server, tablename, session_expiry):
|
||||
def __init__(self, db, r_server, tablename, session_expiry, with_lock=False):
|
||||
self.db = db
|
||||
self.r_server = r_server
|
||||
self.tablename = tablename
|
||||
@@ -100,15 +108,14 @@ class MockTable(object):
|
||||
self.id_idx = "%s:id_idx" % self.keyprefix
|
||||
#remember the session_expiry setting
|
||||
self.session_expiry = session_expiry
|
||||
|
||||
def getserial(self):
|
||||
#return an auto-increment id
|
||||
return "%s" % self.r_server.incr(self.serial, 1)
|
||||
self.with_lock = with_lock
|
||||
|
||||
def __getattr__(self, key):
|
||||
if key == 'id':
|
||||
#return a fake query. We need to query it just by id for normal operations
|
||||
self.query = MockQuery(field='id', db=self.r_server, prefix=self.keyprefix, session_expiry=self.session_expiry)
|
||||
self.query = MockQuery(field='id', db=self.r_server,
|
||||
prefix=self.keyprefix, session_expiry=self.session_expiry,
|
||||
with_lock=self.with_lock)
|
||||
return self.query
|
||||
elif key == '_db':
|
||||
#needed because of the calls in sessions2trash.py and globals.py
|
||||
@@ -119,14 +126,21 @@ class MockTable(object):
|
||||
#'locked', 'client_ip','created_datetime','modified_datetime'
|
||||
#'unique_key', 'session_data'
|
||||
#retrieve a new key
|
||||
newid = self.getserial()
|
||||
key = "%s:%s" % (self.keyprefix, newid)
|
||||
#add it to the index
|
||||
self.r_server.sadd(self.id_idx, key)
|
||||
#set a hash key with the Storage
|
||||
self.r_server.hmset(key, kwargs)
|
||||
if self.session_expiry:
|
||||
self.r_server.expire(key, self.session_expiry)
|
||||
newid = str(self.r_server.incr(self.serial))
|
||||
key = self.keyprefix + ':' + newid
|
||||
if self.with_lock:
|
||||
key_lock = key + ':lock'
|
||||
acquire_lock(self.r_server, key_lock, newid)
|
||||
with self.r_server.pipeline() as pipe:
|
||||
#add it to the index
|
||||
pipe.sadd(self.id_idx, key)
|
||||
#set a hash key with the Storage
|
||||
pipe.hmset(key, kwargs)
|
||||
if self.session_expiry:
|
||||
pipe.expire(key, self.session_expiry)
|
||||
pipe.execute()
|
||||
if self.with_lock:
|
||||
release_lock(self.r_server, key_lock, newid)
|
||||
return newid
|
||||
|
||||
|
||||
@@ -134,13 +148,15 @@ class MockQuery(object):
|
||||
"""a fake Query object that supports querying by id
|
||||
and listing all keys. No other operation is supported
|
||||
"""
|
||||
def __init__(self, field=None, db=None, prefix=None, session_expiry=False):
|
||||
def __init__(self, field=None, db=None, prefix=None, session_expiry=False,
|
||||
with_lock=False):
|
||||
self.field = field
|
||||
self.value = None
|
||||
self.db = db
|
||||
self.keyprefix = prefix
|
||||
self.op = None
|
||||
self.session_expiry = session_expiry
|
||||
self.with_lock = with_lock
|
||||
|
||||
def __eq__(self, value, op='eq'):
|
||||
self.value = value
|
||||
@@ -153,12 +169,11 @@ class MockQuery(object):
|
||||
def select(self):
|
||||
if self.op == 'eq' and self.field == 'id' and self.value:
|
||||
#means that someone wants to retrieve the key self.value
|
||||
rtn = self.db.hgetall("%s:%s" % (self.keyprefix, self.value))
|
||||
if rtn == dict():
|
||||
#return an empty resultset for non existing key
|
||||
return []
|
||||
else:
|
||||
return [Storage(rtn)]
|
||||
key = self.keyprefix + ':' + self.value
|
||||
if self.with_lock:
|
||||
acquire_lock(self.db, key + ':lock', self.value)
|
||||
rtn = self.db.hgetall(key)
|
||||
return [Storage(rtn)] if rtn else []
|
||||
elif self.op == 'ge' and self.field == 'id' and self.value == 0:
|
||||
#means that someone wants the complete list
|
||||
rtn = []
|
||||
@@ -167,13 +182,11 @@ class MockQuery(object):
|
||||
allkeys = self.db.smembers(id_idx)
|
||||
for sess in allkeys:
|
||||
val = self.db.hgetall(sess)
|
||||
if val == dict():
|
||||
if not val:
|
||||
if self.session_expiry:
|
||||
#clean up the idx, because the key expired
|
||||
self.db.srem(id_idx, sess)
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
continue
|
||||
val = Storage(val)
|
||||
#add a delete_record method (necessary for sessions2trash.py)
|
||||
val.delete_record = RecordDeleter(
|
||||
@@ -186,9 +199,14 @@ class MockQuery(object):
|
||||
def update(self, **kwargs):
|
||||
#means that the session has been found and needs an update
|
||||
if self.op == 'eq' and self.field == 'id' and self.value:
|
||||
rtn = self.db.hmset("%s:%s" % (self.keyprefix, self.value), kwargs)
|
||||
if self.session_expiry:
|
||||
self.db.expire(key, self.session.expiry)
|
||||
key = "%s:%s" % (self.keyprefix, self.value)
|
||||
with self.db.pipeline() as pipe:
|
||||
pipe.hmset(key, kwargs)
|
||||
if self.session_expiry:
|
||||
pipe.expire(key, self.session_expiry)
|
||||
rtn = pipe.execute()[0]
|
||||
if self.with_lock:
|
||||
release_lock(self.db, key + ':lock', self.value)
|
||||
return rtn
|
||||
|
||||
|
||||
@@ -204,3 +222,25 @@ class RecordDeleter(object):
|
||||
self.db.srem(id_idx, self.key)
|
||||
#remove the key itself
|
||||
self.db.delete(self.key)
|
||||
|
||||
|
||||
def acquire_lock(conn, lockname, identifier, ltime=10):
|
||||
while True:
|
||||
if conn.set(lockname, identifier, ex=ltime, nx=True):
|
||||
return identifier
|
||||
time.sleep(.01)
|
||||
|
||||
|
||||
_LUA_RELEASE_LOCK = """
|
||||
if redis.call("get", KEYS[1]) == ARGV[1]
|
||||
then
|
||||
return redis.call("del", KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end
|
||||
"""
|
||||
|
||||
|
||||
def release_lock(conn, lockname, identifier):
|
||||
return RedisClient._release_script(keys=[lockname], args=[identifier],
|
||||
client=conn)
|
||||
|
||||
+27
-14
@@ -23,6 +23,10 @@ class Stripe:
|
||||
if paid is True than transaction was processed
|
||||
"""
|
||||
|
||||
URL_CHARGE = 'https://%s:@api.stripe.com/v1/charges'
|
||||
URL_CHECK = 'https://%s:@api.stripe.com/v1/charges/%s'
|
||||
URL_REFUND = 'https://%s:@api.stripe.com/v1/charges/%s/refund'
|
||||
|
||||
def __init__(self, key):
|
||||
self.key = key
|
||||
|
||||
@@ -33,27 +37,36 @@ class Stripe:
|
||||
card_exp_month='5',
|
||||
card_exp_year='2012',
|
||||
card_cvc_check='123',
|
||||
description='test charge'):
|
||||
params = urllib.urlencode({'amount': amount,
|
||||
'currency': currency,
|
||||
'card[number]': card_number,
|
||||
'card[exp_month]': card_exp_month,
|
||||
'card[exp_year]': card_exp_year,
|
||||
'card[cvc_check]': card_cvc_check,
|
||||
'description': description})
|
||||
u = urllib.urlopen('https://%s:@api.stripe.com/v1/charges' %
|
||||
self.key, params)
|
||||
token=None,
|
||||
description='test charge',
|
||||
more=None):
|
||||
if token:
|
||||
d = {'amount': amount,
|
||||
'currency': currency,
|
||||
'card': token,
|
||||
'description': description}
|
||||
else:
|
||||
d = {'amount': amount,
|
||||
'currency': currency,
|
||||
'card[number]': card_number,
|
||||
'card[exp_month]': card_exp_month,
|
||||
'card[exp_year]': card_exp_year,
|
||||
'card[cvc_check]': card_cvc_check,
|
||||
'description': description}
|
||||
if more:
|
||||
d.update(mode)
|
||||
params = urllib.urlencode(d)
|
||||
u = urllib.urlopen(self.URL_CHARGE % self.key, params)
|
||||
return simplejson.loads(u.read())
|
||||
|
||||
def check(self, charge_id):
|
||||
u = urllib.urlopen('https://%s:@api.stripe.com/v1/charges/%s' %
|
||||
(self.key, charge_id))
|
||||
u = urllib.urlopen(self.URL_CHECK % (self.key, charge_id))
|
||||
return simplejson.loads(u.read())
|
||||
|
||||
def refund(self, charge_id):
|
||||
params = urllib.urlencode({})
|
||||
u = urllib.urlopen('https://%s:@api.stripe.com/v1/charges/%s/refund' %
|
||||
(self.key, charge_id), params)
|
||||
u = urllib.urlopen(self.URL_REFUND % (self.key, charge_id),
|
||||
params)
|
||||
return simplejson.loads(u.read())
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+182
-76
@@ -196,7 +196,7 @@ CALLABLETYPES = (types.LambdaType, types.FunctionType,
|
||||
TABLE_ARGS = set(
|
||||
('migrate','primarykey','fake_migrate','format','redefine',
|
||||
'singular','plural','trigger_name','sequence_name',
|
||||
'common_filter','polymodel','table_class','on_define',))
|
||||
'common_filter','polymodel','table_class','on_define','actual_name'))
|
||||
|
||||
SELECT_ARGS = set(
|
||||
('orderby', 'groupby', 'limitby','required', 'cache', 'left',
|
||||
@@ -266,7 +266,7 @@ REGEX_STORE_PATTERN = re.compile('\.(?P<e>\w{1,5})$')
|
||||
REGEX_QUOTES = re.compile("'[^']*'")
|
||||
REGEX_ALPHANUMERIC = re.compile('^[0-9a-zA-Z]\w*$')
|
||||
REGEX_PASSWORD = re.compile('\://([^:@]*)\:')
|
||||
REGEX_NOPASSWD = re.compile('(?<=\:)([^:@/]+)(?=@.+)')
|
||||
REGEX_NOPASSWD = re.compile('\/\/[\w\.\-]+[\:\/](.+)(?=@)') # was '(?<=[\:\/])([^:@/]+)(?=@.+)'
|
||||
|
||||
# list of drivers will be built on the fly
|
||||
# and lists only what is available
|
||||
@@ -457,6 +457,8 @@ def pluralize(singular, rules=PLURALIZE_RULES):
|
||||
if plural: return plural
|
||||
|
||||
def hide_password(uri):
|
||||
if isinstance(uri,(list,tuple)):
|
||||
return [hide_password(item) for item in uri]
|
||||
return REGEX_NOPASSWD.sub('******',uri)
|
||||
|
||||
def OR(a,b):
|
||||
@@ -470,6 +472,11 @@ def IDENTITY(x): return x
|
||||
def varquote_aux(name,quotestr='%s'):
|
||||
return name if REGEX_W.match(name) else quotestr % name
|
||||
|
||||
def quote_keyword(a,keyword='timestamp'):
|
||||
regex = re.compile('\.keyword(?=\w)')
|
||||
a = regex.sub('."%s"' % keyword,a)
|
||||
return a
|
||||
|
||||
if 'google' in DRIVERS:
|
||||
|
||||
is_jdbc = False
|
||||
@@ -583,11 +590,15 @@ class ConnectionPool(object):
|
||||
if the connection is not active (closed by db server) it will loop
|
||||
if not self.pool_size or no active connections in pool makes a new one
|
||||
"""
|
||||
if getattr(self,'connection',None) != None:
|
||||
if getattr(self,'connection', None) != None:
|
||||
return
|
||||
if f is None:
|
||||
f = self.connector
|
||||
|
||||
if not hasattr(self, "driver") or self.driver is None:
|
||||
LOGGER.debug("Skipping connection since there's no driver")
|
||||
return
|
||||
|
||||
if not self.pool_size:
|
||||
self.connection = f()
|
||||
self.cursor = cursor and self.connection.cursor()
|
||||
@@ -917,7 +928,7 @@ class BaseAdapter(ConnectionPool):
|
||||
foreign_key = ', '.join(pkeys),
|
||||
on_delete_action = field.ondelete)
|
||||
|
||||
if hasattr(table,'_primarykey'):
|
||||
if getattr(table,'_primarykey',None):
|
||||
query = "CREATE TABLE %s(\n %s,\n %s) %s" % \
|
||||
(tablename, fields,
|
||||
self.PRIMARY_KEY(', '.join(table._primarykey)),other)
|
||||
@@ -1184,7 +1195,7 @@ class BaseAdapter(ConnectionPool):
|
||||
self.file_delete(table._dbt)
|
||||
logfile.write('success!\n')
|
||||
|
||||
def _insert(self, table, fields):
|
||||
def _insert(self, table, fields):
|
||||
if fields:
|
||||
keys = ','.join(f.name for f, v in fields)
|
||||
values = ','.join(self.expand(v, f.type) for f, v in fields)
|
||||
@@ -1255,7 +1266,7 @@ class BaseAdapter(ConnectionPool):
|
||||
return '(%s LIKE %s)' % (self.expand(first),
|
||||
self.expand('%'+second, 'string'))
|
||||
|
||||
def CONTAINS(self,first,second,case_sensitive=False):
|
||||
def CONTAINS(self,first,second,case_sensitive=False):
|
||||
if first.type in ('string','text', 'json'):
|
||||
second = Expression(None,self.CONCAT('%',Expression(
|
||||
None,self.REPLACE(second,('%','%%'))),'%'))
|
||||
@@ -1306,7 +1317,7 @@ class BaseAdapter(ConnectionPool):
|
||||
ftype.startswith('decimal')
|
||||
|
||||
def REPLACE(self, first, (second, third)):
|
||||
return 'REPLACE(%s,%s,%s)' % (self.expand(first,'string'),
|
||||
return 'REPLACE(%s,%s,%s)' % (self.expand(first,'string'),
|
||||
self.expand(second,'string'),
|
||||
self.expand(third,'string'))
|
||||
|
||||
@@ -1378,13 +1389,16 @@ class BaseAdapter(ConnectionPool):
|
||||
else:
|
||||
return str(expression)
|
||||
|
||||
def table_alias(self,name):
|
||||
return str(name if isinstance(name,Table) else self.db[name])
|
||||
|
||||
def alias(self, table, alias):
|
||||
"""
|
||||
Given a table object, makes a new table object
|
||||
with alias name.
|
||||
"""
|
||||
other = copy.copy(table)
|
||||
other['_ot'] = other._tablename
|
||||
other['_ot'] = other._ot or other._tablename
|
||||
other['ALL'] = SQLALL(other)
|
||||
other['_tablename'] = alias
|
||||
for fieldname in other.fields:
|
||||
@@ -1586,20 +1600,19 @@ class BaseAdapter(ConnectionPool):
|
||||
query = self.common_filter(query,tablenames_for_common_filters)
|
||||
sql_w = ' WHERE ' + self.expand(query) if query else ''
|
||||
|
||||
def alias(t):
|
||||
return str(self.db[t])
|
||||
if inner_join and not left:
|
||||
sql_t = ', '.join([alias(t) for t in iexcluded + \
|
||||
sql_t = ', '.join([self.table_alias(t) for t in iexcluded + \
|
||||
itables_to_merge.keys()])
|
||||
for t in ijoinon:
|
||||
sql_t += ' %s %s' % (icommand, str(t))
|
||||
sql_t += ' %s %s' % (icommand, t)
|
||||
elif not inner_join and left:
|
||||
sql_t = ', '.join([alias(t) for t in excluded + \
|
||||
sql_t = ', '.join([self.table_alias(t) for t in excluded + \
|
||||
tables_to_merge.keys()])
|
||||
if joint:
|
||||
sql_t += ' %s %s' % (command, ','.join([t for t in joint]))
|
||||
sql_t += ' %s %s' % (command,
|
||||
','.join([self.table_alias(t) for t in joint]))
|
||||
for t in joinon:
|
||||
sql_t += ' %s %s' % (command, str(t))
|
||||
sql_t += ' %s %s' % (command, t)
|
||||
elif inner_join and left:
|
||||
all_tables_in_query = set(important_tablenames + \
|
||||
iimportant_tablenames + \
|
||||
@@ -1607,15 +1620,16 @@ class BaseAdapter(ConnectionPool):
|
||||
tables_in_joinon = set(joinont + ijoinont)
|
||||
tables_not_in_joinon = \
|
||||
all_tables_in_query.difference(tables_in_joinon)
|
||||
sql_t = ','.join([alias(t) for t in tables_not_in_joinon])
|
||||
sql_t = ','.join([self.table_alias(t) for t in tables_not_in_joinon])
|
||||
for t in ijoinon:
|
||||
sql_t += ' %s %s' % (icommand, str(t))
|
||||
sql_t += ' %s %s' % (icommand, t)
|
||||
if joint:
|
||||
sql_t += ' %s %s' % (command, ','.join([t for t in joint]))
|
||||
sql_t += ' %s %s' % (command,
|
||||
','.join([self.table_alias(t) for t in joint]))
|
||||
for t in joinon:
|
||||
sql_t += ' %s %s' % (command, str(t))
|
||||
sql_t += ' %s %s' % (command, t)
|
||||
else:
|
||||
sql_t = ', '.join(alias(t) for t in tablenames)
|
||||
sql_t = ', '.join(self.table_alias(t) for t in tablenames)
|
||||
if groupby:
|
||||
if isinstance(groupby, (list, tuple)):
|
||||
groupby = xorify(groupby)
|
||||
@@ -1697,7 +1711,7 @@ class BaseAdapter(ConnectionPool):
|
||||
sql_w = ' WHERE ' + self.expand(query)
|
||||
else:
|
||||
sql_w = ''
|
||||
sql_t = ','.join(tablenames)
|
||||
sql_t = ','.join(self.table_alias(t) for t in tablenames)
|
||||
if distinct:
|
||||
if isinstance(distinct,(list, tuple)):
|
||||
distinct = xorify(distinct)
|
||||
@@ -1755,11 +1769,13 @@ class BaseAdapter(ConnectionPool):
|
||||
def log_execute(self, *a, **b):
|
||||
if not self.connection: return None
|
||||
command = a[0]
|
||||
if hasattr(self,'filter_sql_command'):
|
||||
command = self.filter_sql_command(command)
|
||||
if self.db._debug:
|
||||
LOGGER.debug('SQL: %s' % command)
|
||||
self.db._lastsql = command
|
||||
t0 = time.time()
|
||||
ret = self.cursor.execute(*a, **b)
|
||||
ret = self.cursor.execute(command, *a[1:], **b)
|
||||
self.db._timings.append((command,time.time()-t0))
|
||||
del self.db._timings[:-TIMINGSSIZE]
|
||||
return ret
|
||||
@@ -1803,7 +1819,7 @@ class BaseAdapter(ConnectionPool):
|
||||
else:
|
||||
return self.smart_adapt(self.FALSE)
|
||||
if fieldtype == 'id' or fieldtype == 'integer':
|
||||
return str(int(obj))
|
||||
return str(long(obj))
|
||||
if field_is_type('decimal'):
|
||||
return str(obj)
|
||||
elif field_is_type('reference'): # reference
|
||||
@@ -1811,7 +1827,7 @@ class BaseAdapter(ConnectionPool):
|
||||
return repr(obj)
|
||||
elif isinstance(obj, (Row, Reference)):
|
||||
return str(obj['id'])
|
||||
return str(int(obj))
|
||||
return str(long(obj))
|
||||
elif fieldtype == 'double':
|
||||
return repr(float(obj))
|
||||
if isinstance(obj, unicode):
|
||||
@@ -1840,7 +1856,7 @@ class BaseAdapter(ConnectionPool):
|
||||
if have_serializers:
|
||||
obj = serializers.json(obj)
|
||||
elif simplejson:
|
||||
obj = simplejson.dumps(items)
|
||||
obj = simplejson.dumps(obj)
|
||||
else:
|
||||
raise RuntimeError("missing simplejson")
|
||||
if not isinstance(obj,bytes):
|
||||
@@ -1897,7 +1913,7 @@ class BaseAdapter(ConnectionPool):
|
||||
return value
|
||||
|
||||
def parse_boolean(self, value, field_type):
|
||||
return value == True or str(value)[:1].lower() == 't'
|
||||
return value == self.TRUE or str(value)[:1].lower() == 't'
|
||||
|
||||
def parse_date(self, value, field_type):
|
||||
if isinstance(value, datetime.datetime):
|
||||
@@ -1968,10 +1984,10 @@ class BaseAdapter(ConnectionPool):
|
||||
return value
|
||||
|
||||
def parse_id(self, value, field_type):
|
||||
return int(value)
|
||||
return long(value)
|
||||
|
||||
def parse_integer(self, value, field_type):
|
||||
return int(value)
|
||||
return long(value)
|
||||
|
||||
def parse_double(self, value, field_type):
|
||||
return float(value)
|
||||
@@ -2506,6 +2522,9 @@ class MySQLAdapter(BaseAdapter):
|
||||
self.execute('select last_insert_id();')
|
||||
return int(self.cursor.fetchone()[0])
|
||||
|
||||
def integrity_error_class(self):
|
||||
return self.cursor.IntegrityError
|
||||
|
||||
class PostgreSQLAdapter(BaseAdapter):
|
||||
drivers = ('psycopg2','pg8000')
|
||||
|
||||
@@ -3030,7 +3049,7 @@ class OracleAdapter(BaseAdapter):
|
||||
def lastrowid(self,table):
|
||||
sequence_name = table._sequence_name
|
||||
self.execute('SELECT %s.currval FROM dual;' % sequence_name)
|
||||
return int(self.cursor.fetchone()[0])
|
||||
return long(self.cursor.fetchone()[0])
|
||||
|
||||
#def parse_value(self, value, field_type, blob_decode=True):
|
||||
# if blob_decode and isinstance(value, cx_Oracle.LOB):
|
||||
@@ -3195,7 +3214,7 @@ class MSSQLAdapter(BaseAdapter):
|
||||
def lastrowid(self,table):
|
||||
#self.execute('SELECT @@IDENTITY;')
|
||||
self.execute('SELECT SCOPE_IDENTITY();')
|
||||
return int(self.cursor.fetchone()[0])
|
||||
return long(self.cursor.fetchone()[0])
|
||||
|
||||
def integrity_error_class(self):
|
||||
return pyodbc.IntegrityError
|
||||
@@ -3322,6 +3341,55 @@ class MSSQL2Adapter(MSSQLAdapter):
|
||||
def execute(self,a):
|
||||
return self.log_execute(a.decode('utf8'))
|
||||
|
||||
class VerticaAdapter(MSSQLAdapter):
|
||||
drivers = ('pyodbc',)
|
||||
T_SEP = ' '
|
||||
|
||||
types = {
|
||||
'boolean': 'BOOLEAN',
|
||||
'string': 'VARCHAR(%(length)s)',
|
||||
'text': 'BYTEA',
|
||||
'json': 'VARCHAR(%(length)s)',
|
||||
'password': 'VARCHAR(%(length)s)',
|
||||
'blob': 'BYTEA',
|
||||
'upload': 'VARCHAR(%(length)s)',
|
||||
'integer': 'INT',
|
||||
'bigint': 'BIGINT',
|
||||
'float': 'FLOAT',
|
||||
'double': 'DOUBLE PRECISION',
|
||||
'decimal': 'DECIMAL(%(precision)s,%(scale)s)',
|
||||
'date': 'DATE',
|
||||
'time': 'TIME',
|
||||
'datetime': 'DATETIME',
|
||||
'id': 'IDENTITY',
|
||||
'reference': 'INT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
||||
'list:integer': 'BYTEA',
|
||||
'list:string': 'BYTEA',
|
||||
'list:reference': 'BYTEA',
|
||||
'big-reference': 'BIGINT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
||||
}
|
||||
|
||||
|
||||
def EXTRACT(self, first, what):
|
||||
return "DATE_PART('%s', TIMESTAMP %s)" % (what, self.expand(first))
|
||||
|
||||
def _truncate(self, table, mode=''):
|
||||
tablename = table._tablename
|
||||
return ['TRUNCATE %s %s;' % (tablename, mode or '')]
|
||||
|
||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
||||
if limitby:
|
||||
(lmin, lmax) = limitby
|
||||
sql_o += ' LIMIT %i OFFSET %i' % (lmax - lmin, lmin)
|
||||
return 'SELECT %s %s FROM %s%s%s;' % \
|
||||
(sql_s, sql_f, sql_t, sql_w, sql_o)
|
||||
|
||||
def lastrowid(self,table):
|
||||
self.execute('SELECT LAST_INSERT_ID();')
|
||||
return long(self.cursor.fetchone()[0])
|
||||
|
||||
def execute(self, a):
|
||||
return self.log_execute(a)
|
||||
|
||||
class SybaseAdapter(MSSQLAdapter):
|
||||
drivers = ('Sybase',)
|
||||
@@ -3544,7 +3612,7 @@ class FireBirdAdapter(BaseAdapter):
|
||||
def lastrowid(self,table):
|
||||
sequence_name = table._sequence_name
|
||||
self.execute('SELECT gen_id(%s, 0) FROM rdb$database' % sequence_name)
|
||||
return int(self.cursor.fetchone()[0])
|
||||
return long(self.cursor.fetchone()[0])
|
||||
|
||||
|
||||
class FireBirdEmbeddedAdapter(FireBirdAdapter):
|
||||
@@ -3801,7 +3869,7 @@ class DB2Adapter(BaseAdapter):
|
||||
|
||||
def lastrowid(self,table):
|
||||
self.execute('SELECT DISTINCT IDENTITY_VAL_LOCAL() FROM %s;' % table)
|
||||
return int(self.cursor.fetchone()[0])
|
||||
return long(self.cursor.fetchone()[0])
|
||||
|
||||
def rowslice(self,rows,minimum=0,maximum=None):
|
||||
if maximum is None:
|
||||
@@ -3980,7 +4048,7 @@ class IngresAdapter(BaseAdapter):
|
||||
def lastrowid(self,table):
|
||||
tmp_seqname='%s_iisq' % table
|
||||
self.execute('select current value for %s' % tmp_seqname)
|
||||
return int(self.cursor.fetchone()[0]) # don't really need int type cast here...
|
||||
return long(self.cursor.fetchone()[0]) # don't really need int type cast here...
|
||||
|
||||
def integrity_error_class(self):
|
||||
return self._driver.IntegrityError
|
||||
@@ -4106,7 +4174,7 @@ class SAPDBAdapter(BaseAdapter):
|
||||
|
||||
def lastrowid(self,table):
|
||||
self.execute("select %s.NEXTVAL from dual" % table._sequence_name)
|
||||
return int(self.cursor.fetchone()[0])
|
||||
return long(self.cursor.fetchone()[0])
|
||||
|
||||
class CubridAdapter(MySQLAdapter):
|
||||
drivers = ('cubriddb',)
|
||||
@@ -4719,12 +4787,17 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
|
||||
"text and blob field types not allowed in projection queries")
|
||||
else:
|
||||
projection.append(f.name)
|
||||
elif args_get('filterfields') == True:
|
||||
projection = []
|
||||
for f in fields:
|
||||
projection.append(f.name)
|
||||
|
||||
# projection's can't include 'id'.
|
||||
# real projection's can't include 'id'.
|
||||
# it will be added to the result later
|
||||
query_projection = [
|
||||
p for p in projection if \
|
||||
p != db[tablename]._id.name] if projection \
|
||||
p != db[tablename]._id.name] if projection and \
|
||||
args_get('projection') == True\
|
||||
else None
|
||||
|
||||
cursor = None
|
||||
@@ -4802,6 +4875,11 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
|
||||
what is accepted imposed by GAE: each field must be indexed,
|
||||
projection queries cannot contain blob or text fields, and you
|
||||
cannot use == and also select that same field. see https://developers.google.com/appengine/docs/python/datastore/queries#Query_Projection
|
||||
- optional attribute 'filterfields' when set to True web2py will only
|
||||
parse the explicitly listed fields into the Rows object, even though
|
||||
all fields are returned in the query. This can be used to reduce
|
||||
memory usage in cases where true projection queries are not
|
||||
usable.
|
||||
- optional attribute 'reusecursor' allows use of cursor with queries
|
||||
that have the limitby attribute. Set the attribute to True for the
|
||||
first query, set it to the value of db['_lastcursor'] to continue
|
||||
@@ -4945,7 +5023,7 @@ class CouchDBAdapter(NoSQLAdapter):
|
||||
def represent(self, obj, fieldtype):
|
||||
value = NoSQLAdapter.represent(self, obj, fieldtype)
|
||||
if fieldtype=='id':
|
||||
return repr(str(int(value)))
|
||||
return repr(str(long(value)))
|
||||
elif fieldtype in ('date','time','datetime','boolean'):
|
||||
return serializers.json(value)
|
||||
return repr(not isinstance(value,unicode) and value \
|
||||
@@ -4999,7 +5077,7 @@ class CouchDBAdapter(NoSQLAdapter):
|
||||
def uid(fd):
|
||||
return fd=='id' and '_id' or fd
|
||||
def get(row,fd):
|
||||
return fd=='id' and int(row['_id']) or row.get(fd,None)
|
||||
return fd=='id' and long(row['_id']) or row.get(fd,None)
|
||||
fields = new_fields
|
||||
tablename = self.get_table(query)
|
||||
fieldnames = [f.name for f in (fields or self.db[tablename])]
|
||||
@@ -5212,13 +5290,13 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
def parse_reference(self, value, field_type):
|
||||
# here we have to check for ObjectID before base parse
|
||||
if isinstance(value, self.ObjectId):
|
||||
value = int(str(value), 16)
|
||||
value = long(str(value), 16)
|
||||
return super(MongoDBAdapter,
|
||||
self).parse_reference(value, field_type)
|
||||
|
||||
def parse_id(self, value, field_type):
|
||||
if isinstance(value, self.ObjectId):
|
||||
value = int(str(value), 16)
|
||||
value = long(str(value), 16)
|
||||
return super(MongoDBAdapter,
|
||||
self).parse_id(value, field_type)
|
||||
|
||||
@@ -5277,7 +5355,7 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
else:
|
||||
values[fieldname] = self.represent(v, fieldtype)
|
||||
ctable.insert(values, safe=safe)
|
||||
return int(str(values['_id']), 16)
|
||||
return long(str(values['_id']), 16)
|
||||
|
||||
def create_table(self, table, migrate=True, fake_migrate=False,
|
||||
polymodel=None, isCapped=False):
|
||||
@@ -5290,8 +5368,8 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
if not isinstance(query,Query):
|
||||
raise SyntaxError("Not Supported")
|
||||
tablename = self.get_table(query)
|
||||
return int(self.select(query,[self.db[tablename]._id], {},
|
||||
count=True,snapshot=snapshot)['count'])
|
||||
return long(self.select(query,[self.db[tablename]._id], {},
|
||||
count=True,snapshot=snapshot)['count'])
|
||||
# Maybe it would be faster if we just implemented the pymongo
|
||||
# .count() function which is probably quicker?
|
||||
# therefor call __select() connection[table].find(query).count()
|
||||
@@ -6214,7 +6292,7 @@ class IMAPAdapter(NoSQLAdapter):
|
||||
typ, data = self.connection.uid("fetch", uid, imap_fields)
|
||||
if typ == "OK":
|
||||
fr = {"message": int(data[0][0].split()[0]),
|
||||
"uid": int(uid),
|
||||
"uid": long(uid),
|
||||
"email": email.message_from_string(data[0][1]),
|
||||
"raw_message": data[0][1]}
|
||||
fr["multipart"] = fr["email"].is_multipart()
|
||||
@@ -6644,6 +6722,7 @@ ADAPTERS = {
|
||||
'mssql': MSSQLAdapter,
|
||||
'mssql2': MSSQL2Adapter,
|
||||
'mssql3': MSSQL3Adapter,
|
||||
'vertica': VerticaAdapter,
|
||||
'sybase': SybaseAdapter,
|
||||
'db2': DB2Adapter,
|
||||
'teradata': TeradataAdapter,
|
||||
@@ -6666,7 +6745,6 @@ ADAPTERS = {
|
||||
'imap': IMAPAdapter
|
||||
}
|
||||
|
||||
|
||||
def sqlhtml_validators(field):
|
||||
"""
|
||||
Field type validation, using web2py's validators mechanism.
|
||||
@@ -6774,7 +6852,7 @@ def bar_encode(items):
|
||||
def bar_decode_integer(value):
|
||||
if not hasattr(value,'split') and hasattr(value,'read'):
|
||||
value = value.read()
|
||||
return [int(x) for x in value.split('|') if x.strip()]
|
||||
return [long(x) for x in value.split('|') if x.strip()]
|
||||
|
||||
def bar_decode_string(value):
|
||||
return [x.replace('||', '|') for x in
|
||||
@@ -6851,6 +6929,9 @@ class Row(object):
|
||||
def __int__(self):
|
||||
return object.__getattribute__(self,'id')
|
||||
|
||||
def __long__(self):
|
||||
return long(object.__getattribute__(self,'id'))
|
||||
|
||||
def __eq__(self,other):
|
||||
try:
|
||||
return self.as_dict() == other.as_dict()
|
||||
@@ -6877,7 +6958,7 @@ class Row(object):
|
||||
elif isinstance(v,Row):
|
||||
d[k]=v.as_dict()
|
||||
elif isinstance(v,Reference):
|
||||
d[k]=int(v)
|
||||
d[k]=long(v)
|
||||
elif isinstance(v,decimal.Decimal):
|
||||
d[k]=float(v)
|
||||
elif isinstance(v, (datetime.date, datetime.datetime, datetime.time)):
|
||||
@@ -7653,8 +7734,8 @@ def index():
|
||||
'error':'I\'m a teapot','response':None})
|
||||
try:
|
||||
distinct = vars.get('distinct', False) == 'True'
|
||||
offset = int(vars.get('offset',None) or 0)
|
||||
limits = (offset,int(vars.get('limit',None) or 1000)+offset)
|
||||
offset = long(vars.get('offset',None) or 0)
|
||||
limits = (offset,long(vars.get('limit',None) or 1000)+offset)
|
||||
except ValueError:
|
||||
return Row({'status':400,'error':'invalid limits','response':None})
|
||||
items = dbset.select(db[table][field], distinct=distinct, limitby=limits)
|
||||
@@ -7680,8 +7761,8 @@ def index():
|
||||
fields = [field for field in db[table] if field.readable]
|
||||
count = dbset.count()
|
||||
try:
|
||||
offset = int(vars.get('offset',None) or 0)
|
||||
limits = (offset,int(vars.get('limit',None) or 1000)+offset)
|
||||
offset = long(vars.get('offset',None) or 0)
|
||||
limits = (offset,long(vars.get('limit',None) or 1000)+offset)
|
||||
except ValueError:
|
||||
return Row({'status':400,'error':'invalid limits','response':None})
|
||||
if count > limits[1]-limits[0]:
|
||||
@@ -7975,7 +8056,7 @@ def index():
|
||||
if not field.table==thistable]
|
||||
|
||||
def export_to_csv_file(self, ofile, *args, **kwargs):
|
||||
step = int(kwargs.get('max_fetch_rows,',500))
|
||||
step = long(kwargs.get('max_fetch_rows,',500))
|
||||
write_colnames = kwargs['write_colnames'] = \
|
||||
kwargs.get("write_colnames", True)
|
||||
for table in self.tables:
|
||||
@@ -8034,14 +8115,14 @@ class Reference(long):
|
||||
|
||||
def __allocate(self):
|
||||
if not self._record:
|
||||
self._record = self._table[int(self)]
|
||||
self._record = self._table[long(self)]
|
||||
if not self._record:
|
||||
raise RuntimeError(
|
||||
"Using a recursive select but encountered a broken reference: %s %d"%(self._table, int(self)))
|
||||
"Using a recursive select but encountered a broken reference: %s %d"%(self._table, long(self)))
|
||||
|
||||
def __getattr__(self, key):
|
||||
if key == 'id':
|
||||
return int(self)
|
||||
return long(self)
|
||||
self.__allocate()
|
||||
return self._record.get(key, None)
|
||||
|
||||
@@ -8050,14 +8131,14 @@ class Reference(long):
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key.startswith('_'):
|
||||
int.__setattr__(self, key, value)
|
||||
long.__setattr__(self, key, value)
|
||||
return
|
||||
self.__allocate()
|
||||
self._record[key] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == 'id':
|
||||
return int(self)
|
||||
return long(self)
|
||||
self.__allocate()
|
||||
return self._record.get(key, None)
|
||||
|
||||
@@ -8071,13 +8152,29 @@ def Reference_unpickler(data):
|
||||
|
||||
def Reference_pickler(data):
|
||||
try:
|
||||
marshal_dump = marshal.dumps(int(data))
|
||||
marshal_dump = marshal.dumps(long(data))
|
||||
except AttributeError:
|
||||
marshal_dump = 'i%s' % struct.pack('<i', int(data))
|
||||
marshal_dump = 'i%s' % struct.pack('<i', long(data))
|
||||
return (Reference_unpickler, (marshal_dump,))
|
||||
|
||||
copyreg.pickle(Reference, Reference_pickler, Reference_unpickler)
|
||||
|
||||
class MethodAdder(object):
|
||||
def __init__(self,table):
|
||||
self.table = table
|
||||
def __call__(self):
|
||||
return self.register()
|
||||
def __getattr__(self,method_name):
|
||||
return self.register(method_name)
|
||||
def register(self,method_name=None):
|
||||
def _decorated(f):
|
||||
instance = self.table
|
||||
import types
|
||||
method = types.MethodType(f, instance, instance.__class__)
|
||||
name = method_name or f.func_name
|
||||
setattr(instance, name, method)
|
||||
return f
|
||||
return _decorated
|
||||
|
||||
class Table(object):
|
||||
|
||||
@@ -8111,19 +8208,20 @@ class Table(object):
|
||||
"""
|
||||
self._actual = False # set to True by define_table()
|
||||
self._tablename = tablename
|
||||
self._sequence_name = args.get('sequence_name',None) or \
|
||||
self._ot = args.get('actual_name')
|
||||
self._sequence_name = args.get('sequence_name') or \
|
||||
db and db._adapter.sequence_name(tablename)
|
||||
self._trigger_name = args.get('trigger_name',None) or \
|
||||
self._trigger_name = args.get('trigger_name') or \
|
||||
db and db._adapter.trigger_name(tablename)
|
||||
self._common_filter = args.get('common_filter', None)
|
||||
self._format = args.get('format',None)
|
||||
self._common_filter = args.get('common_filter')
|
||||
self._format = args.get('format')
|
||||
self._singular = args.get(
|
||||
'singular',tablename.replace('_',' ').capitalize())
|
||||
self._plural = args.get(
|
||||
'plural',pluralize(self._singular.lower()).capitalize())
|
||||
# horrible but for backard compatibility of appamdin:
|
||||
if 'primarykey' in args and args['primarykey']:
|
||||
self._primarykey = args.get('primarykey', None)
|
||||
if 'primarykey' in args and args['primarykey'] is not None:
|
||||
self._primarykey = args.get('primarykey')
|
||||
|
||||
self._before_insert = []
|
||||
self._before_update = [Set.delete_uploaded_files]
|
||||
@@ -8132,6 +8230,8 @@ class Table(object):
|
||||
self._after_update = []
|
||||
self._after_delete = []
|
||||
|
||||
self.add_method = MethodAdder(self)
|
||||
|
||||
fieldnames,newfields=set(),[]
|
||||
if hasattr(self,'_primarykey'):
|
||||
if not isinstance(self._primarykey,list):
|
||||
@@ -8417,8 +8517,8 @@ class Table(object):
|
||||
def __repr__(self):
|
||||
return '<Table %s (%s)>' % (self._tablename,','.join(self.fields()))
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self,'_ot') and self._ot is not None:
|
||||
def __str__(self):
|
||||
if self._ot is not None:
|
||||
if 'Oracle' in str(type(self._db._adapter)): # <<< patch
|
||||
return '%s %s' % (self._ot, self._tablename) # <<< patch
|
||||
return '%s AS %s' % (self._ot, self._tablename)
|
||||
@@ -8618,13 +8718,13 @@ class Table(object):
|
||||
if not value.strip():
|
||||
value = None
|
||||
else:
|
||||
value = int(value)
|
||||
value = long(value)
|
||||
elif field.type.startswith('list:string'):
|
||||
value = bar_decode_string(value)
|
||||
elif field.type.startswith(list_reference_s):
|
||||
ref_table = field.type[len(list_reference_s):].strip()
|
||||
if id_map is not None:
|
||||
value = [id_map[ref_table][int(v)] \
|
||||
value = [id_map[ref_table][long(v)] \
|
||||
for v in bar_decode_string(value)]
|
||||
else:
|
||||
value = [v for v in bar_decode_string(value)]
|
||||
@@ -8632,12 +8732,12 @@ class Table(object):
|
||||
value = bar_decode_integer(value)
|
||||
elif id_map and field.type.startswith('reference'):
|
||||
try:
|
||||
value = id_map[field.type[9:].strip()][int(value)]
|
||||
value = id_map[field.type[9:].strip()][long(value)]
|
||||
except KeyError:
|
||||
pass
|
||||
elif id_offset and field.type.startswith('reference'):
|
||||
try:
|
||||
value = id_offset[field.type[9:].strip()]+int(value)
|
||||
value = id_offset[field.type[9:].strip()]+long(value)
|
||||
except KeyError:
|
||||
pass
|
||||
return (field.name, value)
|
||||
@@ -8668,7 +8768,7 @@ class Table(object):
|
||||
for i in cols if colnames[i] in self.fields]
|
||||
|
||||
if not id_map and cid is not None and id_offset is not None and not unique_idx:
|
||||
csv_id = int(line[cid])
|
||||
csv_id = long(line[cid])
|
||||
curr_id = self.insert(**dict(items))
|
||||
if first:
|
||||
first = False
|
||||
@@ -8697,7 +8797,7 @@ class Table(object):
|
||||
else:
|
||||
new_id = self.insert(**dict(items))
|
||||
if id_map and cid is not None:
|
||||
id_map_self[int(line[cid])] = new_id
|
||||
id_map_self[long(line[cid])] = new_id
|
||||
|
||||
def as_dict(self, flat=False, sanitize=True, field_options=True):
|
||||
tablename = str(self)
|
||||
@@ -9575,9 +9675,13 @@ class Query(object):
|
||||
def __and__(self, other):
|
||||
return Query(self.db,self.db._adapter.AND,self,other)
|
||||
|
||||
__rand__ = __and__
|
||||
|
||||
def __or__(self, other):
|
||||
return Query(self.db,self.db._adapter.OR,self,other)
|
||||
|
||||
__ror__ = __or__
|
||||
|
||||
def __invert__(self):
|
||||
if self.op==self.db._adapter.NOT:
|
||||
return self.first
|
||||
@@ -9707,7 +9811,9 @@ class Set(object):
|
||||
return '<Set %s>' % BaseAdapter.expand(self.db._adapter,self.query)
|
||||
|
||||
def __call__(self, query, ignore_common_filters=False):
|
||||
if isinstance(query,Table):
|
||||
if query is None:
|
||||
return self
|
||||
elif isinstance(query,Table):
|
||||
query = self.db._adapter.id_query(query)
|
||||
elif isinstance(query,str):
|
||||
query = Expression(self.db,query)
|
||||
@@ -9833,7 +9939,7 @@ class Set(object):
|
||||
return built
|
||||
|
||||
def isempty(self):
|
||||
return not self.select(limitby=(0,1))
|
||||
return not self.select(limitby=(0,1), orderby_on_limitby=False)
|
||||
|
||||
def count(self,distinct=None, cache=None):
|
||||
db = self.db
|
||||
@@ -10314,7 +10420,7 @@ class Rows(object):
|
||||
elif isinstance(value, unicode):
|
||||
return value.encode('utf8')
|
||||
elif isinstance(value,Reference):
|
||||
return int(value)
|
||||
return long(value)
|
||||
elif hasattr(value, 'isoformat'):
|
||||
return value.isoformat()[:19].replace('T', ' ')
|
||||
elif isinstance(value, (list,tuple)): # for type='list:..'
|
||||
|
||||
+6
-5
@@ -586,7 +586,7 @@ class XML(XmlComponent):
|
||||
return self.text
|
||||
|
||||
def __str__(self):
|
||||
return self.xml()
|
||||
return self.text
|
||||
|
||||
def __add__(self, other):
|
||||
return '%s%s' % (self, other)
|
||||
@@ -600,8 +600,9 @@ class XML(XmlComponent):
|
||||
def __hash__(self):
|
||||
return hash(str(self))
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(str(self), name)
|
||||
# why was this here? Break unpickling in sessions
|
||||
# def __getattr__(self, name):
|
||||
# return getattr(str(self), name)
|
||||
|
||||
def __getitem__(self, i):
|
||||
return str(self)[i]
|
||||
@@ -1991,10 +1992,10 @@ class FORM(DIV):
|
||||
status = True
|
||||
changed = False
|
||||
request_vars = self.request_vars
|
||||
if session:
|
||||
if session is not None:
|
||||
formkey = session.get('_formkey[%s]' % formname, None)
|
||||
# check if user tampering with form and void CSRF
|
||||
if formkey != request_vars._formkey:
|
||||
if not formkey or formkey != request_vars._formkey:
|
||||
status = False
|
||||
if formname != request_vars._formname:
|
||||
status = False
|
||||
|
||||
+6
-5
@@ -123,14 +123,15 @@ class HTTP(BaseException):
|
||||
|
||||
message elements that are not defined are omitted
|
||||
"""
|
||||
msg = '%(status)d'
|
||||
msg = '%(status)s'
|
||||
if self.status in defined_status:
|
||||
msg = '%(status)d %(defined_status)s'
|
||||
msg = '%(status)s %(defined_status)s'
|
||||
if 'web2py_error' in self.headers:
|
||||
msg += ' [%(web2py_error)s]'
|
||||
return msg % dict(status=self.status,
|
||||
defined_status=defined_status.get(self.status),
|
||||
web2py_error=self.headers.get('web2py_error'))
|
||||
return msg % dict(
|
||||
status=self.status,
|
||||
defined_status=defined_status.get(self.status),
|
||||
web2py_error=self.headers.get('web2py_error'))
|
||||
|
||||
def __str__(self):
|
||||
"stringify me"
|
||||
|
||||
+39
-8
@@ -29,6 +29,14 @@ import tempfile
|
||||
import random
|
||||
import string
|
||||
import urllib2
|
||||
try:
|
||||
import simplejson as sj #external installed library
|
||||
except:
|
||||
try:
|
||||
import json as sj #standard installed library
|
||||
except:
|
||||
import contrib.simplejson as sj #pure python library
|
||||
|
||||
from thread import allocate_lock
|
||||
|
||||
from fileutils import abspath, write_file, parse_version, copystream
|
||||
@@ -87,7 +95,7 @@ from settings import global_settings
|
||||
from validators import CRYPT
|
||||
from cache import CacheInRam
|
||||
from html import URL, xmlescape
|
||||
from utils import is_valid_ip_address
|
||||
from utils import is_valid_ip_address, getipaddrinfo
|
||||
from rewrite import load, url_in, THREAD_LOCAL as rwthread, \
|
||||
try_rewrite_on_error, fixup_missing_path_info
|
||||
import newcron
|
||||
@@ -295,6 +303,7 @@ def environ_aux(environ, request):
|
||||
new_environ['wsgi.version'] = 1
|
||||
return new_environ
|
||||
|
||||
ISLE25 = sys.version_info[1] <= 5
|
||||
|
||||
def parse_get_post_vars(request, environ):
|
||||
|
||||
@@ -311,17 +320,36 @@ def parse_get_post_vars(request, environ):
|
||||
request.get_vars[key] = value
|
||||
request.vars[key] = request.get_vars[key]
|
||||
|
||||
# parse POST variables on POST, PUT, BOTH only in post_vars
|
||||
|
||||
try:
|
||||
request.body = body = copystream_progress(request)
|
||||
except IOError:
|
||||
raise HTTP(400, "Bad Request - HTTP body is incomplete")
|
||||
|
||||
#if content-type is application/json, we must read the body
|
||||
is_json = env.get('http_content_type', '')[:16] == 'application/json'
|
||||
|
||||
|
||||
if is_json:
|
||||
try:
|
||||
json_vars = sj.load(body)
|
||||
body.seek(0)
|
||||
except:
|
||||
# incoherent request bodies can still be parsed "ad-hoc"
|
||||
json_vars = {}
|
||||
pass
|
||||
# update vars and get_vars with what was posted as json
|
||||
request.get_vars.update(json_vars)
|
||||
request.vars.update(json_vars)
|
||||
|
||||
|
||||
# parse POST variables on POST, PUT, BOTH only in post_vars
|
||||
if (body and env.request_method in ('POST', 'PUT', 'BOTH')):
|
||||
dpost = cgi.FieldStorage(fp=body, environ=environ, keep_blank_values=1)
|
||||
# The same detection used by FieldStorage to detect multipart POSTs
|
||||
is_multipart = dpost.type[:10] == 'multipart/'
|
||||
body.seek(0)
|
||||
isle25 = sys.version_info[1] <= 5
|
||||
|
||||
|
||||
def listify(a):
|
||||
return (not isinstance(a, list) and [a]) or a
|
||||
@@ -348,7 +376,7 @@ def parse_get_post_vars(request, environ):
|
||||
pvalue = listify(value)
|
||||
if key in request.vars:
|
||||
gvalue = listify(request.vars[key])
|
||||
if isle25:
|
||||
if ISLE25:
|
||||
value = pvalue + gvalue
|
||||
elif is_multipart:
|
||||
pvalue = pvalue[len(gvalue):]
|
||||
@@ -358,6 +386,9 @@ def parse_get_post_vars(request, environ):
|
||||
if len(pvalue):
|
||||
request.post_vars[key] = (len(pvalue) >
|
||||
1 and pvalue) or pvalue[0]
|
||||
if is_json:
|
||||
# update post_vars with what was posted as json
|
||||
request.post_vars.update(json_vars)
|
||||
|
||||
|
||||
def wsgibase(environ, responder):
|
||||
@@ -440,13 +471,13 @@ def wsgibase(environ, responder):
|
||||
local_hosts.add(socket.gethostname())
|
||||
local_hosts.add(fqdn)
|
||||
local_hosts.update([
|
||||
ip[4][0] for ip in socket.getaddrinfo(
|
||||
fqdn, 0)])
|
||||
addrinfo[4][0] for addrinfo
|
||||
in getipaddrinfo(fqdn)])
|
||||
if env.server_name:
|
||||
local_hosts.add(env.server_name)
|
||||
local_hosts.update([
|
||||
ip[4][0] for ip in socket.getaddrinfo(
|
||||
env.server_name, 0)])
|
||||
addrinfo[4][0] for addrinfo
|
||||
in getipaddrinfo(env.server_name)])
|
||||
except (socket.gaierror, TypeError):
|
||||
pass
|
||||
global_settings.local_hosts = list(local_hosts)
|
||||
|
||||
@@ -12,10 +12,10 @@ from languages import lazyT
|
||||
import contrib.rss2 as rss2
|
||||
|
||||
try:
|
||||
import json as json_parser # try stdlib (Python 2.6)
|
||||
import simplejson as json_parser # try external module
|
||||
except ImportError:
|
||||
try:
|
||||
import simplejson as json_parser # try external module
|
||||
import json as json_parser # try stdlib (Python >= 2.6)
|
||||
except:
|
||||
import contrib.simplejson as json_parser # fallback to pure-Python module
|
||||
|
||||
@@ -127,7 +127,8 @@ def csv(value):
|
||||
return ''
|
||||
|
||||
|
||||
def ics(events, title=None, link=None, timeshift=0, **ignored):
|
||||
def ics(events, title=None, link=None, timeshift=0, calname=True,
|
||||
**ignored):
|
||||
import datetime
|
||||
title = title or '(unkown)'
|
||||
if link and not callable(link):
|
||||
@@ -135,7 +136,8 @@ def ics(events, title=None, link=None, timeshift=0, **ignored):
|
||||
'[id]', str(item['id']))
|
||||
s = 'BEGIN:VCALENDAR'
|
||||
s += '\nVERSION:2.0'
|
||||
s += '\nX-WR-CALNAME:%s' % title
|
||||
if not calname is False:
|
||||
s += '\nX-WR-CALNAME:%s' % (calname or title)
|
||||
s += '\nSUMMARY:%s' % title
|
||||
s += '\nPRODID:Generated by web2py'
|
||||
s += '\nCALSCALE:GREGORIAN'
|
||||
|
||||
+77
-37
@@ -21,7 +21,7 @@ import os
|
||||
from http import HTTP
|
||||
from html import XmlComponent
|
||||
from html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG, SCRIPT
|
||||
from html import FORM, INPUT, LABEL, OPTION, SELECT, BUTTON
|
||||
from html import FORM, INPUT, LABEL, OPTION, SELECT
|
||||
from html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE
|
||||
from html import URL, truncate_string, FIELDSET
|
||||
from dal import DAL, Field, Table, Row, CALLABLETYPES, smart_query, \
|
||||
@@ -36,7 +36,7 @@ import datetime
|
||||
import urllib
|
||||
import re
|
||||
import cStringIO
|
||||
from gluon import current, redirect, A, URL, DIV, H3, UL, LI, SPAN, INPUT
|
||||
from gluon import current, redirect
|
||||
import inspect
|
||||
import settings
|
||||
is_gae = settings.global_settings.web2py_runtime_gae
|
||||
@@ -1073,7 +1073,7 @@ class SQLFORM(FORM):
|
||||
cond = readonly or \
|
||||
(not ignore_rw and not field.writable and field.readable)
|
||||
|
||||
if default and not cond:
|
||||
if default is not None and not cond:
|
||||
default = field.formatter(default)
|
||||
dspval = default
|
||||
inpval = default
|
||||
@@ -1456,8 +1456,9 @@ class SQLFORM(FORM):
|
||||
if not f:
|
||||
continue
|
||||
else:
|
||||
f = os.path.join(current.request.folder,
|
||||
os.path.normpath(f))
|
||||
f = os.path.join(
|
||||
current.request.folder,
|
||||
os.path.normpath(f))
|
||||
source_file = open(f, 'rb')
|
||||
original_filename = os.path.split(f)[1]
|
||||
elif hasattr(f, 'file'):
|
||||
@@ -1466,6 +1467,10 @@ class SQLFORM(FORM):
|
||||
### do not know why this happens, it should not
|
||||
(source_file, original_filename) = \
|
||||
(cStringIO.StringIO(f), 'file.txt')
|
||||
else:
|
||||
# this should never happen, why does it happen?
|
||||
print 'f=',repr(f)
|
||||
continue
|
||||
newfilename = field.store(source_file, original_filename,
|
||||
field.uploadfolder)
|
||||
# this line was for backward compatibility but problematic
|
||||
@@ -1648,7 +1653,14 @@ class SQLFORM(FORM):
|
||||
selectfields = []
|
||||
for field in fields:
|
||||
name = str(field).replace('.', '-')
|
||||
options = search_options.get(field.type.split(' ')[0], None)
|
||||
# treat ftype 'decimal' as 'double'
|
||||
# (this fixes problems but needs refactoring!
|
||||
ftype = field.type.split(' ')[0]
|
||||
if ftype.startswith('decimal'): ftype = 'double'
|
||||
elif ftype=='bigint': ftype = 'integer'
|
||||
elif ftype.startswith('big-'): ftype = ftype[4:]
|
||||
# end
|
||||
options = search_options.get(ftype, None)
|
||||
if options:
|
||||
label = isinstance(
|
||||
field.label, str) and T(field.label) or field.label
|
||||
@@ -1773,7 +1785,9 @@ class SQLFORM(FORM):
|
||||
selectable_submit_button='Submit',
|
||||
buttons_placement = 'right',
|
||||
links_placement = 'right',
|
||||
noconfirm=False
|
||||
noconfirm=False,
|
||||
cache_count=None,
|
||||
client_side_delete=False,
|
||||
):
|
||||
|
||||
# jQuery UI ThemeRoller classes (empty if ui is disabled)
|
||||
@@ -1826,6 +1840,33 @@ class SQLFORM(FORM):
|
||||
create = wenabled and create
|
||||
editable = wenabled and editable
|
||||
deletable = wenabled and deletable
|
||||
rows = None
|
||||
|
||||
def fetch_count(dbset):
|
||||
##FIXME for google:datastore cache_count is ignored
|
||||
## if it's not an integer
|
||||
if cache_count is None or isinstance(cache_count, tuple):
|
||||
if groupby:
|
||||
c = 'count(*)'
|
||||
nrows = db.executesql(
|
||||
'select count(*) from (%s);' %
|
||||
dbset._select(c, left=left, cacheable=True,
|
||||
groupby=groupby, cache=cache_count)[:-1])[0][0]
|
||||
elif left:
|
||||
c = 'count(*)'
|
||||
nrows = dbset.select(c, left=left, cacheable=True, cache=cache_count).first()[c]
|
||||
elif dbset._db._adapter.dbengine=='google:datastore':
|
||||
#if we don't set a limit, this can timeout for a large table
|
||||
nrows = dbset.db._adapter.count(dbset.query, limit=1000)
|
||||
else:
|
||||
nrows = dbset.count(cache=cache_count)
|
||||
elif isinstance(cache_count, (int, long)):
|
||||
nrows = cache_count
|
||||
elif callable(cache_count):
|
||||
nrows = cache_count(dbset, request.vars)
|
||||
else:
|
||||
nrows = 0
|
||||
return nrows
|
||||
|
||||
def url(**b):
|
||||
b['args'] = args + b.get('args', [])
|
||||
@@ -1865,7 +1906,7 @@ class SQLFORM(FORM):
|
||||
delete=None, trap=True, noconfirm=None):
|
||||
if showbuttontext:
|
||||
return A(SPAN(_class=ui.get(buttonclass)),
|
||||
SPAN(T(buttontext), _title=buttontext,
|
||||
SPAN(T(buttontext), _title=T(buttontext),
|
||||
_class=ui.get('buttontext')),
|
||||
_href=buttonurl,
|
||||
callback=callback,
|
||||
@@ -1878,7 +1919,7 @@ class SQLFORM(FORM):
|
||||
callback=callback,
|
||||
delete=delete,
|
||||
noconfirm=noconfirm,
|
||||
_title=buttontext,
|
||||
_title=T(buttontext),
|
||||
_class=trap_class(ui.get('buttontext'), trap))
|
||||
|
||||
dbset = db(query)
|
||||
@@ -1957,6 +1998,7 @@ class SQLFORM(FORM):
|
||||
res.update_form = update_form
|
||||
res.view_form = view_form
|
||||
res.search_form = search_form
|
||||
res.rows = None
|
||||
return res
|
||||
|
||||
elif details and request.args(-3) == 'view':
|
||||
@@ -1973,15 +2015,17 @@ class SQLFORM(FORM):
|
||||
res.update_form = update_form
|
||||
res.view_form = view_form
|
||||
res.search_form = search_form
|
||||
res.rows = None
|
||||
return res
|
||||
elif editable and request.args(-3) == 'edit':
|
||||
table = db[request.args[-2]]
|
||||
record = table(request.args[-1]) or redirect(URL('error'))
|
||||
sqlformargs.update(editargs)
|
||||
deletable_ = deletable(record) if callable(deletable) else deletable
|
||||
update_form = SQLFORM(
|
||||
table,
|
||||
record, upload=upload, ignore_rw=ignore_rw,
|
||||
formstyle=formstyle, deletable=deletable,
|
||||
formstyle=formstyle, deletable=deletable_,
|
||||
_class='web2py_form',
|
||||
submit_button=T('Submit'),
|
||||
delete_label=T('Check to delete'),
|
||||
@@ -1998,13 +2042,21 @@ class SQLFORM(FORM):
|
||||
res.update_form = update_form
|
||||
res.view_form = view_form
|
||||
res.search_form = search_form
|
||||
res.rows = None
|
||||
return res
|
||||
elif deletable and request.args(-3) == 'delete':
|
||||
table = db[request.args[-2]]
|
||||
if ondelete:
|
||||
ondelete(table, request.args[-1])
|
||||
db(table[table._id.name] == request.args[-1]).delete()
|
||||
redirect(referrer)
|
||||
if not callable(deletable):
|
||||
if ondelete:
|
||||
ondelete(table, request.args[-1])
|
||||
db(table[table._id.name] == request.args[-1]).delete()
|
||||
else:
|
||||
record = table(request.args[-1]) or redirect(URL('error'))
|
||||
if deletable(record):
|
||||
if ondelete:
|
||||
ondelete(table, request.args[-1])
|
||||
record.delete_record()
|
||||
redirect(referrer, client_side=client_side_delete)
|
||||
|
||||
exportManager = dict(
|
||||
csv_with_hidden_cols=(ExporterCSV, 'CSV (hidden cols)'),
|
||||
@@ -2055,7 +2107,7 @@ class SQLFORM(FORM):
|
||||
else:
|
||||
rows = dbset.select(left=left, orderby=orderby,
|
||||
cacheable=True, *expcolumns)
|
||||
|
||||
|
||||
value = exportManager[export_type]
|
||||
clazz = value[0] if hasattr(value, '__getitem__') else value
|
||||
oExp = clazz(rows)
|
||||
@@ -2122,20 +2174,7 @@ class SQLFORM(FORM):
|
||||
if subquery:
|
||||
dbset = dbset(subquery)
|
||||
try:
|
||||
if groupby:
|
||||
c = 'count(*)'
|
||||
nrows = db.executesql(
|
||||
'select count(*) from (%s);' %
|
||||
dbset._select(c, left=left, cacheable=True,
|
||||
groupby=groupby)[:-1])[0][0]
|
||||
elif left:
|
||||
c = 'count(*)'
|
||||
nrows = dbset.select(c, left=left, cacheable=True).first()[c]
|
||||
elif dbset._db._adapter.dbengine=='google:datastore':
|
||||
#if we don't set a limit, this can timeout for a large table
|
||||
nrows = dbset.db._adapter.count(dbset.query, limit=1000)
|
||||
else:
|
||||
nrows = dbset.count()
|
||||
nrows = fetch_count(dbset)
|
||||
except:
|
||||
nrows = 0
|
||||
error = T('Unsupported query')
|
||||
@@ -2236,7 +2275,7 @@ class SQLFORM(FORM):
|
||||
message = T('at least %(nrows)s records found') % dict(nrows=nrows)
|
||||
else:
|
||||
message = T('%(nrows)s records found') % dict(nrows=nrows)
|
||||
console.append(DIV(message,_class='web2py_counter'))
|
||||
console.append(DIV(message or T('None'),_class='web2py_counter'))
|
||||
|
||||
paginator = UL()
|
||||
if paginate and dbset._db._adapter.dbengine=='google:datastore':
|
||||
@@ -2323,9 +2362,9 @@ class SQLFORM(FORM):
|
||||
if value:
|
||||
if callable(upload):
|
||||
value = A(
|
||||
current.T('file'), _href=upload(value))
|
||||
T('file'), _href=upload(value))
|
||||
elif upload:
|
||||
value = A(current.T('file'),
|
||||
value = A(T('file'),
|
||||
_href='%s/%s' % (upload, value))
|
||||
else:
|
||||
value = ''
|
||||
@@ -2397,7 +2436,7 @@ class SQLFORM(FORM):
|
||||
selectable(records)
|
||||
redirect(referrer)
|
||||
else:
|
||||
htmltable = DIV(current.T('No records found'))
|
||||
htmltable = DIV(T('No records found'))
|
||||
|
||||
if csv and nrows:
|
||||
export_links = []
|
||||
@@ -2427,6 +2466,7 @@ class SQLFORM(FORM):
|
||||
res.update_form = update_form
|
||||
res.view_form = view_form
|
||||
res.search_form = search_form
|
||||
res.rows = rows
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
@@ -2486,7 +2526,7 @@ class SQLFORM(FORM):
|
||||
name = None
|
||||
def format(table,row):
|
||||
if not row:
|
||||
return 'Unknown'
|
||||
return T('Unknown')
|
||||
elif isinstance(table._format,str):
|
||||
return table._format % row
|
||||
elif callable(table._format):
|
||||
@@ -2616,11 +2656,11 @@ class SQLFORM(FORM):
|
||||
SPAN(divider, _class='divider') if next else '',
|
||||
_class='active w2p_grid_breadcrumb_elem'))
|
||||
if grid.create_form:
|
||||
header = T('New %s' % table._singular)
|
||||
header = T('New %(entity)s') % dict(entity=table._singular)
|
||||
elif grid.update_form:
|
||||
header = T('Edit %s' % format(table,grid.update_form.record))
|
||||
header = T('Edit %(entity)s') % dict(entity=format(table,grid.update_form.record))
|
||||
elif grid.view_form:
|
||||
header = T('View %s' % format(table,grid.view_form.record))
|
||||
header = T('View %(entity)s') % dict(entity=format(table,grid.view_form.record))
|
||||
if next:
|
||||
breadcrumbs.append(LI(
|
||||
A(T(header), _class=trap_class(),_href=url()),
|
||||
|
||||
+5
-2
@@ -18,6 +18,7 @@ import portalocker
|
||||
__all__ = ['List', 'Storage', 'Settings', 'Messages',
|
||||
'StorageList', 'load_storage', 'save_storage']
|
||||
|
||||
DEFAULT = lambda:0
|
||||
|
||||
class Storage(dict):
|
||||
"""
|
||||
@@ -250,7 +251,7 @@ class List(list):
|
||||
instead of IndexOutOfBounds
|
||||
"""
|
||||
|
||||
def __call__(self, i, default=None, cast=None, otherwise=None):
|
||||
def __call__(self, i, default=DEFAULT, cast=None, otherwise=None):
|
||||
"""
|
||||
request.args(0,default=0,cast=int,otherwise='http://error_url')
|
||||
request.args(0,default=0,cast=int,otherwise=lambda:...)
|
||||
@@ -258,8 +259,10 @@ class List(list):
|
||||
n = len(self)
|
||||
if 0 <= i < n or -n <= i < 0:
|
||||
value = self[i]
|
||||
elif default is DEFAULT:
|
||||
value = None
|
||||
else:
|
||||
value = default
|
||||
value, cast = default, False
|
||||
if cast:
|
||||
try:
|
||||
value = cast(value)
|
||||
|
||||
+5
-4
@@ -562,8 +562,8 @@ class TemplateParser(object):
|
||||
if in_tag:
|
||||
line = i
|
||||
|
||||
# Get rid of '{{' and '}}'
|
||||
line = line[2:-2].strip()
|
||||
# Get rid of delimiters
|
||||
line = line[len(self.delimiters[0]):-len(self.delimiters[1])].strip()
|
||||
|
||||
# This is bad juju, but let's do it anyway
|
||||
if not line:
|
||||
@@ -832,7 +832,8 @@ def render(content="hello world",
|
||||
path=None,
|
||||
context={},
|
||||
lexers={},
|
||||
delimiters=('{{', '}}')
|
||||
delimiters=('{{', '}}'),
|
||||
writer='response.write'
|
||||
):
|
||||
"""
|
||||
>>> render()
|
||||
@@ -895,7 +896,7 @@ def render(content="hello world",
|
||||
|
||||
# Execute the template.
|
||||
code = str(TemplateParser(stream.read(
|
||||
), context=context, path=path, lexers=lexers, delimiters=delimiters))
|
||||
), context=context, path=path, lexers=lexers, delimiters=delimiters, writer=writer))
|
||||
try:
|
||||
exec(code) in context
|
||||
except Exception:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from test_http import *
|
||||
from test_cache import *
|
||||
from test_dal import *
|
||||
from test_html import *
|
||||
@@ -9,5 +10,8 @@ from test_storage import *
|
||||
from test_template import *
|
||||
from test_utils import *
|
||||
from test_contribs import *
|
||||
from test_markmin import *
|
||||
from test_web import *
|
||||
|
||||
import sys
|
||||
if sys.version[:3] == '2.7':
|
||||
from test_old_doctests import *
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
[run]
|
||||
branch = True
|
||||
parallel = True
|
||||
source = gluon
|
||||
## remove comment if you want applications coverage too
|
||||
#source = gluon, applications
|
||||
|
||||
[report]
|
||||
# Regexes for lines to exclude from consideration
|
||||
exclude_lines =
|
||||
# Have to re-enable the standard pragma
|
||||
pragma: no cover
|
||||
|
||||
# Don't complain about missing debug-only code:
|
||||
def __repr__
|
||||
if self\.debug
|
||||
|
||||
# Don't complain if tests don't hit defensive assertion code:
|
||||
raise AssertionError
|
||||
raise NotImplementedError
|
||||
|
||||
# Don't complain if non-runnable code isn't run:
|
||||
if 0:
|
||||
if __name__ == .__main__.:
|
||||
|
||||
ignore_errors = True
|
||||
omit = gluon/contrib/*
|
||||
|
||||
[html]
|
||||
directory = coverage_html_report
|
||||
@@ -236,9 +236,12 @@ class TestInsert(unittest.TestCase):
|
||||
self.assertEqual(db.tt.insert(aa='1'), 2)
|
||||
self.assertEqual(db.tt.insert(aa='1'), 3)
|
||||
self.assertEqual(db(db.tt.aa == '1').count(), 3)
|
||||
self.assertEqual(db(db.tt.aa == '2').isempty(), True)
|
||||
self.assertEqual(db(db.tt.aa == '1').update(aa='2'), 3)
|
||||
self.assertEqual(db(db.tt.aa == '2').count(), 3)
|
||||
self.assertEqual(db(db.tt.aa == '2').isempty(), False)
|
||||
self.assertEqual(db(db.tt.aa == '2').delete(), 3)
|
||||
self.assertEqual(db(db.tt.aa == '2').isempty(), True)
|
||||
db.tt.drop()
|
||||
|
||||
|
||||
@@ -272,6 +275,20 @@ class TestSelect(unittest.TestCase):
|
||||
self.assertEqual(db(~(db.tt.aa > '1') & (db.tt.aa > '2')).count(), 0)
|
||||
db.tt.drop()
|
||||
|
||||
class TestAddMethod(unittest.TestCase):
|
||||
|
||||
def testRun(self):
|
||||
db = DAL(DEFAULT_URI, check_reserved=['all'])
|
||||
db.define_table('tt', Field('aa'))
|
||||
@db.tt.add_method.all
|
||||
def select_all(table,orderby=None):
|
||||
return table._db(table).select(orderby=orderby)
|
||||
self.assertEqual(db.tt.insert(aa='1'), 1)
|
||||
self.assertEqual(db.tt.insert(aa='2'), 2)
|
||||
self.assertEqual(db.tt.insert(aa='3'), 3)
|
||||
self.assertEqual(len(db.tt.all()), 3)
|
||||
db.tt.drop()
|
||||
|
||||
|
||||
class TestBelongs(unittest.TestCase):
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Unit tests for http.py """
|
||||
|
||||
import sys
|
||||
import os
|
||||
import unittest
|
||||
if os.path.isdir('gluon'):
|
||||
sys.path.append(os.path.realpath('gluon'))
|
||||
else:
|
||||
sys.path.append(os.path.realpath('../'))
|
||||
|
||||
from http import HTTP, defined_status
|
||||
|
||||
|
||||
class TestHTTP(unittest.TestCase):
|
||||
""" Tests http.HTTP """
|
||||
|
||||
def test_status_message(self):
|
||||
""" Tests http status code message """
|
||||
|
||||
h = HTTP
|
||||
|
||||
def gen_status_str(code, message):
|
||||
return str(code) + ' ' + str(message)
|
||||
message = '1423 This is a custom message'
|
||||
code = 1423
|
||||
self.assertEqual(str(h(gen_status_str(code, message))),
|
||||
gen_status_str(code, message))
|
||||
|
||||
# test predefined codes
|
||||
for code in defined_status.keys():
|
||||
self.assertEqual(
|
||||
str(h(code)),
|
||||
gen_status_str(code, defined_status[code]))
|
||||
|
||||
# test correct use of status_message
|
||||
for code in defined_status.keys():
|
||||
self.assertEqual(str(h(gen_status_str(code, message))),
|
||||
gen_status_str(code, message))
|
||||
|
||||
# test wrong call detection
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Unit tests for running web2py
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
if os.path.isdir('gluon'):
|
||||
sys.path.append(os.path.realpath('gluon'))
|
||||
else:
|
||||
sys.path.append(os.path.realpath('../'))
|
||||
|
||||
import unittest
|
||||
from contrib.markmin.markmin2html import run_doctests
|
||||
|
||||
|
||||
class TestMarkmin(unittest.TestCase):
|
||||
def testMarkmin(self):
|
||||
run_doctests()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Unit tests for old doctests in validators.py, utf8.py, html.py,
|
||||
markmin2html.py.
|
||||
Don't abuse doctests, web2py > 2.4.5 will accept only unittests
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
if os.path.isdir('gluon'):
|
||||
sys.path.append(os.path.realpath('gluon'))
|
||||
else:
|
||||
sys.path.append(os.path.realpath('../'))
|
||||
|
||||
import unittest
|
||||
import doctest
|
||||
|
||||
def load_tests(loader, tests, ignore):
|
||||
|
||||
tests.addTests(
|
||||
doctest.DocTestSuite('validators',
|
||||
optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS,
|
||||
)
|
||||
)
|
||||
tests.addTests(
|
||||
doctest.DocTestSuite('html')
|
||||
)
|
||||
tests.addTests(
|
||||
doctest.DocTestSuite('utf8')
|
||||
)
|
||||
|
||||
tests.addTests(
|
||||
doctest.DocTestSuite('contrib.markmin.markmin2html',
|
||||
)
|
||||
)
|
||||
|
||||
return tests
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
+140
-60
@@ -248,6 +248,7 @@ class Mail(object):
|
||||
settings.sender = sender
|
||||
settings.login = login
|
||||
settings.tls = tls
|
||||
settings.hostname = None
|
||||
settings.ssl = False
|
||||
settings.cipher_type = None
|
||||
settings.gpg_home = None
|
||||
@@ -370,15 +371,16 @@ class Mail(object):
|
||||
if not isinstance(sender, str):
|
||||
raise Exception('Sender address not specified')
|
||||
|
||||
if not raw:
|
||||
if not raw and attachments:
|
||||
# Use multipart/mixed if there is attachments
|
||||
payload_in = MIMEMultipart.MIMEMultipart('mixed')
|
||||
else:
|
||||
elif raw:
|
||||
# no encoding configuration for raw messages
|
||||
if not isinstance(message, basestring):
|
||||
message = message.read()
|
||||
if isinstance(message, unicode):
|
||||
text = message.encode('utf-8')
|
||||
elif not encoding=='utf-8':
|
||||
elif not encoding == 'utf-8':
|
||||
text = message.decode(encoding).encode('utf-8')
|
||||
else:
|
||||
text = message
|
||||
@@ -410,21 +412,43 @@ class Mail(object):
|
||||
html = None
|
||||
|
||||
if (not text is None or not html is None) and (not raw):
|
||||
attachment = MIMEMultipart.MIMEMultipart('alternative')
|
||||
|
||||
if not text is None:
|
||||
if isinstance(text, basestring):
|
||||
if not isinstance(text, basestring):
|
||||
text = text.read()
|
||||
if isinstance(text, unicode):
|
||||
text = text.encode('utf-8')
|
||||
elif not encoding == 'utf-8':
|
||||
text = text.decode(encoding).encode('utf-8')
|
||||
else:
|
||||
text = text.read().decode(encoding).encode('utf-8')
|
||||
attachment.attach(MIMEText.MIMEText(text, _charset='utf-8'))
|
||||
if not html is None:
|
||||
if isinstance(html, basestring):
|
||||
if not isinstance(html, basestring):
|
||||
html = html.read()
|
||||
if isinstance(html, unicode):
|
||||
html = html.encode('utf-8')
|
||||
elif not encoding == 'utf-8':
|
||||
html = html.decode(encoding).encode('utf-8')
|
||||
else:
|
||||
html = html.read().decode(encoding).encode('utf-8')
|
||||
|
||||
# Construct mime part only if needed
|
||||
if text and html:
|
||||
# We have text and html we need multipart/alternative
|
||||
attachment = MIMEMultipart.MIMEMultipart('alternative')
|
||||
attachment.attach(MIMEText.MIMEText(text, _charset='utf-8'))
|
||||
attachment.attach(
|
||||
MIMEText.MIMEText(html, 'html', _charset='utf-8'))
|
||||
payload_in.attach(attachment)
|
||||
elif text:
|
||||
attachment = MIMEText.MIMEText(text, _charset='utf-8')
|
||||
elif html:
|
||||
attachment = \
|
||||
MIMEText.MIMEText(html, 'html', _charset='utf-8')
|
||||
|
||||
if attachments:
|
||||
# If there is attachments put text and html into
|
||||
# multipart/mixed
|
||||
payload_in.attach(attachment)
|
||||
else:
|
||||
# No attachments no multipart/mixed
|
||||
payload_in = attachment
|
||||
|
||||
if (attachments is None) or raw:
|
||||
pass
|
||||
elif isinstance(attachments, (list, tuple)):
|
||||
@@ -682,9 +706,9 @@ class Mail(object):
|
||||
else:
|
||||
server = smtplib.SMTP(*smtp_args)
|
||||
if self.settings.tls and not self.settings.ssl:
|
||||
server.ehlo()
|
||||
server.ehlo(self.settings.hostname)
|
||||
server.starttls()
|
||||
server.ehlo()
|
||||
server.ehlo(self.settings.hostname)
|
||||
if self.settings.login:
|
||||
server.login(*self.settings.login.split(':', 1))
|
||||
result = server.sendmail(
|
||||
@@ -867,6 +891,7 @@ class Auth(object):
|
||||
on_failed_authentication=lambda x: redirect(x),
|
||||
formstyle="table3cols",
|
||||
label_separator=": ",
|
||||
allow_delete_accounts=False,
|
||||
password_field='password',
|
||||
table_user_name='auth_user',
|
||||
table_group_name='auth_group',
|
||||
@@ -892,6 +917,8 @@ class Auth(object):
|
||||
username_case_sensitive=True,
|
||||
update_fields = ['email'],
|
||||
ondelete="CASCADE",
|
||||
client_side = True,
|
||||
wiki = Settings(),
|
||||
)
|
||||
# ## these are messages that can be customized
|
||||
default_messages = dict(
|
||||
@@ -899,7 +926,7 @@ class Auth(object):
|
||||
register_button='Register',
|
||||
password_reset_button='Request reset password',
|
||||
password_change_button='Change password',
|
||||
profile_save_button='Save profile',
|
||||
profile_save_button='Apply changes',
|
||||
submit_button='Submit',
|
||||
verify_password='Verify Password',
|
||||
delete_label='Check to delete',
|
||||
@@ -1182,6 +1209,14 @@ class Auth(object):
|
||||
# ## these are messages that can be customized
|
||||
messages = self.messages = Messages(current.T)
|
||||
messages.update(Auth.default_messages)
|
||||
messages.update(ajax_failed_authentication=DIV(H4('NOT AUTHORIZED'),
|
||||
'Please ',
|
||||
A('login',
|
||||
_href=self.settings.login_url +
|
||||
('?_next=' + urllib.quote(current.request.env.http_web2py_component_location))
|
||||
if current.request.env.http_web2py_component_location else ''),
|
||||
' to view this content.',
|
||||
_class='not-authorized alert alert-block'))
|
||||
messages.lock_keys = True
|
||||
|
||||
# for "remember me" option
|
||||
@@ -1738,7 +1773,7 @@ class Auth(object):
|
||||
if 'registration_id' in checks \
|
||||
and user \
|
||||
and user.registration_id \
|
||||
and user.registration_id != keys.get('registration_id', None):
|
||||
and ('registration_id' not in keys or user.registration_id != str(keys['registration_id'])):
|
||||
user = None # THINK MORE ABOUT THIS? DO WE TRUST OPENID PROVIDER?
|
||||
if user:
|
||||
update_keys = dict(registration_id=keys['registration_id'])
|
||||
@@ -2120,7 +2155,7 @@ class Auth(object):
|
||||
callback(onfail, None)
|
||||
redirect(
|
||||
self.url(args=request.args, vars=request.get_vars),
|
||||
client_side=True)
|
||||
client_side=self.settings.client_side)
|
||||
|
||||
else:
|
||||
# use a central authentication server
|
||||
@@ -2137,7 +2172,8 @@ class Auth(object):
|
||||
else:
|
||||
# we need to pass through login again before going on
|
||||
next = self.url(self.settings.function, args='login')
|
||||
redirect(cas.login_url(next), client_side=True)
|
||||
redirect(cas.login_url(next),
|
||||
client_side=self.settings.client_side)
|
||||
|
||||
# process authenticated users
|
||||
if user:
|
||||
@@ -2160,7 +2196,7 @@ class Auth(object):
|
||||
if next == session._auth_next:
|
||||
session._auth_next = None
|
||||
next = replace_id(next, form)
|
||||
redirect(next, client_side=True)
|
||||
redirect(next, client_side=self.settings.client_side)
|
||||
|
||||
table_user[username].requires = old_requires
|
||||
return form
|
||||
@@ -2169,7 +2205,7 @@ class Auth(object):
|
||||
|
||||
if next == session._auth_next:
|
||||
del session._auth_next
|
||||
redirect(next, client_side=True)
|
||||
redirect(next, client_side=self.settings.client_side)
|
||||
|
||||
def logout(self, next=DEFAULT, onlogout=DEFAULT, log=DEFAULT):
|
||||
"""
|
||||
@@ -2221,7 +2257,8 @@ class Auth(object):
|
||||
response = current.response
|
||||
session = current.session
|
||||
if self.is_logged_in():
|
||||
redirect(self.settings.logged_url, client_side=True)
|
||||
redirect(self.settings.logged_url,
|
||||
client_side=self.settings.client_side)
|
||||
if next is DEFAULT:
|
||||
next = self.next or self.settings.register_next
|
||||
if onvalidation is DEFAULT:
|
||||
@@ -2232,7 +2269,9 @@ class Auth(object):
|
||||
log = self.messages.register_log
|
||||
|
||||
table_user = self.table_user()
|
||||
if 'username' in table_user.fields:
|
||||
if self.settings.login_userfield:
|
||||
username = self.settings.login_userfield
|
||||
elif 'username' in table_user.fields:
|
||||
username = 'username'
|
||||
else:
|
||||
username = 'email'
|
||||
@@ -2337,7 +2376,7 @@ class Auth(object):
|
||||
next = self.url(args=request.args)
|
||||
else:
|
||||
next = replace_id(next, form)
|
||||
redirect(next, client_side=True)
|
||||
redirect(next, client_side=self.settings.client_side)
|
||||
return form
|
||||
|
||||
def is_logged_in(self):
|
||||
@@ -2585,7 +2624,7 @@ class Auth(object):
|
||||
raise Exception
|
||||
except Exception:
|
||||
session.flash = self.messages.invalid_reset_password
|
||||
redirect(next, client_side=True)
|
||||
redirect(next, client_side=self.settings.client_side)
|
||||
passfield = self.settings.password_field
|
||||
form = SQLFORM.factory(
|
||||
Field('new_password', 'password',
|
||||
@@ -2610,7 +2649,7 @@ class Auth(object):
|
||||
session.flash = self.messages.password_changed
|
||||
if self.settings.login_after_password_change:
|
||||
self.login_user(user)
|
||||
redirect(next, client_side=True)
|
||||
redirect(next, client_side=self.settings.client_side)
|
||||
return form
|
||||
|
||||
def request_reset_password(
|
||||
@@ -2668,10 +2707,12 @@ class Auth(object):
|
||||
user = table_user(email=form.vars.email)
|
||||
if not user:
|
||||
session.flash = self.messages.invalid_email
|
||||
redirect(self.url(args=request.args), client_side=True)
|
||||
redirect(self.url(args=request.args),
|
||||
client_side=self.settings.client_side)
|
||||
elif user.registration_key in ('pending', 'disabled', 'blocked'):
|
||||
session.flash = self.messages.registration_pending
|
||||
redirect(self.url(args=request.args), client_side=True)
|
||||
redirect(self.url(args=request.args),
|
||||
client_side=self.settings.client_side)
|
||||
if self.email_reset_password(user):
|
||||
session.flash = self.messages.email_sent
|
||||
else:
|
||||
@@ -2682,7 +2723,7 @@ class Auth(object):
|
||||
next = self.url(args=request.args)
|
||||
else:
|
||||
next = replace_id(next, form)
|
||||
redirect(next, client_side=True)
|
||||
redirect(next, client_side=self.settings.client_side)
|
||||
# old_requires = table_user.email.requires
|
||||
return form
|
||||
|
||||
@@ -2727,7 +2768,8 @@ class Auth(object):
|
||||
"""
|
||||
|
||||
if not self.is_logged_in():
|
||||
redirect(self.settings.login_url, client_side=True)
|
||||
redirect(self.settings.login_url,
|
||||
client_side=self.settings.client_side)
|
||||
db = self.db
|
||||
table_user = self.table_user()
|
||||
s = db(table_user.id == self.user.id)
|
||||
@@ -2777,7 +2819,7 @@ class Auth(object):
|
||||
next = self.url(args=request.args)
|
||||
else:
|
||||
next = replace_id(next, form)
|
||||
redirect(next, client_side=True)
|
||||
redirect(next, client_side=self.settings.client_side)
|
||||
return form
|
||||
|
||||
def profile(
|
||||
@@ -2797,7 +2839,8 @@ class Auth(object):
|
||||
|
||||
table_user = self.table_user()
|
||||
if not self.is_logged_in():
|
||||
redirect(self.settings.login_url, client_side=True)
|
||||
redirect(self.settings.login_url,
|
||||
client_side=self.settings.client_side)
|
||||
passfield = self.settings.password_field
|
||||
table_user[passfield].writable = False
|
||||
request = current.request
|
||||
@@ -2820,20 +2863,24 @@ class Auth(object):
|
||||
delete_label=self.messages.delete_label,
|
||||
upload=self.settings.download_url,
|
||||
formstyle=self.settings.formstyle,
|
||||
separator=self.settings.label_separator
|
||||
separator=self.settings.label_separator,
|
||||
deletable=self.settings.allow_delete_accounts,
|
||||
)
|
||||
if form.accepts(request, session,
|
||||
formname='profile',
|
||||
onvalidation=onvalidation, hideerror=self.settings.hideerror):
|
||||
onvalidation=onvalidation,
|
||||
hideerror=self.settings.hideerror):
|
||||
self.user.update(table_user._filter_fields(form.vars))
|
||||
session.flash = self.messages.profile_updated
|
||||
self.log_event(log, self.user)
|
||||
callback(onaccept, form)
|
||||
if form.deleted:
|
||||
return self.logout()
|
||||
if not next:
|
||||
next = self.url(args=request.args)
|
||||
else:
|
||||
next = replace_id(next, form)
|
||||
redirect(next, client_side=True)
|
||||
redirect(next, client_side=self.settings.client_side)
|
||||
return form
|
||||
|
||||
def is_impersonating(self):
|
||||
@@ -2947,7 +2994,7 @@ class Auth(object):
|
||||
if requires_login:
|
||||
if not user:
|
||||
if current.request.ajax:
|
||||
raise HTTP(401)
|
||||
raise HTTP(401, self.messages.ajax_failed_authentication)
|
||||
elif not otherwise is None:
|
||||
if callable(otherwise):
|
||||
return otherwise()
|
||||
@@ -2955,8 +3002,6 @@ class Auth(object):
|
||||
elif self.settings.allow_basic_login_only or \
|
||||
basic_accepted or current.request.is_restful:
|
||||
raise HTTP(403, "Not authorized")
|
||||
elif current.request.ajax:
|
||||
return A('login', _href=self.settings.login_url)
|
||||
else:
|
||||
next = self.here()
|
||||
current.session.flash = current.response.flash
|
||||
@@ -3368,7 +3413,12 @@ class Auth(object):
|
||||
resolve=True,
|
||||
extra=None,
|
||||
menu_groups=None,
|
||||
templates=None):
|
||||
templates=None,
|
||||
migrate=True,
|
||||
controller=None,
|
||||
function=None):
|
||||
|
||||
if controller and function: resolve = False
|
||||
|
||||
if not hasattr(self, '_wiki'):
|
||||
self._wiki = Wiki(self, render=render,
|
||||
@@ -3377,9 +3427,13 @@ class Auth(object):
|
||||
restrict_search=restrict_search,
|
||||
env=env, extra=extra or {},
|
||||
menu_groups=menu_groups,
|
||||
templates=templates)
|
||||
templates=templates,
|
||||
migrate=migrate,
|
||||
controller=controller,
|
||||
function=function)
|
||||
else:
|
||||
self._wiki.env.update(env or {})
|
||||
|
||||
# if resolve is set to True, process request as wiki call
|
||||
# resolve=False allows initial setup without wiki redirection
|
||||
wiki = None
|
||||
@@ -3396,6 +3450,13 @@ class Auth(object):
|
||||
wiki = XML(wiki)
|
||||
return wiki
|
||||
|
||||
def wikimenu(self):
|
||||
"""to be used in menu.py for app wide wiki menus"""
|
||||
if (hasattr(self, "_wiki") and
|
||||
self._wiki.settings.controller and
|
||||
self._wiki.settings.function):
|
||||
self._wiki.automenu()
|
||||
|
||||
|
||||
class Crud(object):
|
||||
|
||||
@@ -3448,7 +3509,7 @@ class Crud(object):
|
||||
|
||||
messages = self.messages = Messages(current.T)
|
||||
messages.submit_button = 'Submit'
|
||||
messages.delete_label = 'Check to delete:'
|
||||
messages.delete_label = 'Check to delete'
|
||||
messages.record_created = 'Record Created'
|
||||
messages.record_updated = 'Record Updated'
|
||||
messages.record_deleted = 'Record Deleted'
|
||||
@@ -4975,9 +5036,10 @@ class Wiki(object):
|
||||
def __init__(self, auth, env=None, render='markmin',
|
||||
manage_permissions=False, force_prefix='',
|
||||
restrict_search=False, extra=None,
|
||||
menu_groups=None, templates=None):
|
||||
menu_groups=None, templates=None, migrate=True,
|
||||
controller=None, function=None):
|
||||
|
||||
settings = self.settings = Settings()
|
||||
settings = self.settings = auth.settings.wiki
|
||||
|
||||
# render: "markmin", "html", ..., <function>
|
||||
settings.render = render
|
||||
@@ -4988,11 +5050,14 @@ class Wiki(object):
|
||||
settings.extra = extra or {}
|
||||
settings.menu_groups = menu_groups
|
||||
settings.templates = templates
|
||||
settings.controller = controller
|
||||
settings.function = function
|
||||
|
||||
db = auth.db
|
||||
self.env = env or {}
|
||||
self.env['component'] = Wiki.component
|
||||
self.auth = auth
|
||||
self.wiki_menu_items = None
|
||||
|
||||
if self.auth.user:
|
||||
self.settings.force_prefix = force_prefix % self.auth.user
|
||||
@@ -5023,20 +5088,20 @@ class Wiki(object):
|
||||
compute=self.get_render(),
|
||||
readable=False, writable=False),
|
||||
auth.signature],
|
||||
'vars':{'format':'%(title)s'}}),
|
||||
'vars':{'format':'%(title)s', 'migrate':migrate}}),
|
||||
('wiki_tag', {
|
||||
'args':[
|
||||
Field('name'),
|
||||
Field('wiki_page', 'reference wiki_page'),
|
||||
auth.signature],
|
||||
'vars':{'format':'%(name)s'}}),
|
||||
'vars':{'format':'%(title)s', 'migrate':migrate}}),
|
||||
('wiki_media', {
|
||||
'args':[
|
||||
Field('wiki_page', 'reference wiki_page'),
|
||||
Field('title', required=True),
|
||||
Field('filename', 'upload', required=True),
|
||||
auth.signature],
|
||||
'vars':{'format':'%(title)s'}})
|
||||
'vars':{'format':'%(title)s', 'migrate':migrate}}),
|
||||
]
|
||||
|
||||
# define only non-existent tables
|
||||
@@ -5131,10 +5196,21 @@ class Wiki(object):
|
||||
|
||||
### END POLICY
|
||||
|
||||
def automenu(self):
|
||||
"""adds the menu if not present"""
|
||||
request = current.request
|
||||
if not self.wiki_menu_items and self.settings.controller and self.settings.function:
|
||||
self.wiki_menu_items = self.menu(self.settings.controller,
|
||||
self.settings.function)
|
||||
current.response.menu += self.wiki_menu_items
|
||||
|
||||
def __call__(self):
|
||||
request = current.request
|
||||
automenu = self.menu(request.controller, request.function)
|
||||
current.response.menu += automenu
|
||||
settings = self.settings
|
||||
settings.controller = settings.controller or request.controller
|
||||
settings.function = settings.function or request.function
|
||||
self.automenu()
|
||||
|
||||
zero = request.args(0) or 'index'
|
||||
if zero and zero.isdigit():
|
||||
return self.media(int(zero))
|
||||
@@ -5193,6 +5269,7 @@ class Wiki(object):
|
||||
else:
|
||||
return dict(title=page.title,
|
||||
slug=page.slug,
|
||||
page=page,
|
||||
content=XML(self.fix_hostname(page.html)),
|
||||
tags=page.tags,
|
||||
created_on=page.created_on,
|
||||
@@ -5205,6 +5282,7 @@ class Wiki(object):
|
||||
else:
|
||||
return dict(title=page.title,
|
||||
slug=page.slug,
|
||||
page=page,
|
||||
content=page.body,
|
||||
tags=page.tags,
|
||||
created_on=page.created_on,
|
||||
@@ -5255,25 +5333,27 @@ class Wiki(object):
|
||||
current.session.flash = 'page created'
|
||||
redirect(URL(args=slug))
|
||||
script = """
|
||||
$(function() {
|
||||
jQuery(function() {
|
||||
if (!jQuery('#wiki_page_body').length) return;
|
||||
var pagecontent = jQuery('#wiki_page_body');
|
||||
pagecontent.css('font-family',
|
||||
'Monaco,Menlo,Consolas,"Courier New",monospace');
|
||||
var prevbutton = jQuery('<button class="btn nopreview">Preview</button>');
|
||||
var mediabutton = jQuery('<button class="btn nopreview">Media</button>');
|
||||
var preview = jQuery('<div id="preview"></div>').hide();
|
||||
var previewmedia = jQuery('<div id="previewmedia"></div>');
|
||||
var form = pagecontent.closest('form');
|
||||
preview.insertBefore(form);
|
||||
prevbutton.insertBefore(form);
|
||||
mediabutton.insertBefore(form);
|
||||
previewmedia.insertBefore(form);
|
||||
mediabutton.toggle(function() {
|
||||
web2py_component('%(urlmedia)s', 'previewmedia');
|
||||
}, function() {
|
||||
previewmedia.empty();
|
||||
});
|
||||
if(%(link_media)s) {
|
||||
var mediabutton = jQuery('<button class="btn nopreview">Media</button>');
|
||||
mediabutton.insertBefore(form);
|
||||
previewmedia.insertBefore(form);
|
||||
mediabutton.toggle(function() {
|
||||
web2py_component('%(urlmedia)s', 'previewmedia');
|
||||
}, function() {
|
||||
previewmedia.empty();
|
||||
});
|
||||
}
|
||||
prevbutton.click(function(e) {
|
||||
e.preventDefault();
|
||||
if (prevbutton.hasClass('nopreview')) {
|
||||
@@ -5288,7 +5368,7 @@ class Wiki(object):
|
||||
}
|
||||
})
|
||||
})
|
||||
""" % dict(url=URL(args=('_preview', slug)),
|
||||
""" % dict(url=URL(args=('_preview', slug)),link_media=('true' if page else 'false'),
|
||||
urlmedia=URL(extension='load',
|
||||
args=('_editmedia',slug),
|
||||
vars=dict(embedded=1)))
|
||||
@@ -5348,7 +5428,7 @@ class Wiki(object):
|
||||
'%(slug)s')),
|
||||
comment=current.T(
|
||||
"Choose Template or empty for new Page")))
|
||||
form = SQLFORM.factory(*fields, **dict(_class="well span6"))
|
||||
form = SQLFORM.factory(*fields, **dict(_class="well"))
|
||||
form.element("[type=submit]").attributes["_value"] = \
|
||||
current.T("Create Page from Slug")
|
||||
|
||||
@@ -5413,8 +5493,8 @@ class Wiki(object):
|
||||
items = link[2:].split('/')
|
||||
if len(items) > 3:
|
||||
title_page = items[3]
|
||||
link = URL(a=items[0] or None, c=items[1] or None,
|
||||
f=items[2] or None, args=items[3:])
|
||||
link = URL(a=items[0] or None, c=items[1] or controller,
|
||||
f=items[2] or function, args=items[3:])
|
||||
parent = tree.get(base[1:], tree[''])
|
||||
subtree = []
|
||||
tree[base] = subtree
|
||||
|
||||
+26
-9
@@ -295,14 +295,31 @@ def is_valid_ip_address(address):
|
||||
return True
|
||||
|
||||
|
||||
def is_loopback_ip_address(ip):
|
||||
"""Determines whether the IP address appears to be a loopback address.
|
||||
|
||||
This assumes that the IP is valid. The IPv6 check is limited to '::1'.
|
||||
|
||||
def is_loopback_ip_address(ip=None, addrinfo=None):
|
||||
"""
|
||||
if not ip:
|
||||
Determines whether the address appears to be a loopback address.
|
||||
This assumes that the IP is valid.
|
||||
"""
|
||||
if addrinfo: # see socket.getaddrinfo() for layout of addrinfo tuple
|
||||
if addrinfo[0] == socket.AF_INET or addrinfo[0] == socket.AF_INET6:
|
||||
ip = addrinfo[4]
|
||||
if not isinstance(ip, basestring):
|
||||
return False
|
||||
if ip.count('.') == 3: # IPv4
|
||||
return ip.startswith('127') or ip.startswith('::ffff:127')
|
||||
return ip == '::1' # IPv6
|
||||
# IPv4 or IPv6-embedded IPv4 or IPv4-compatible IPv6
|
||||
if ip.count('.') == 3:
|
||||
return ip.lower().startswith(('127', '::127', '0:0:0:0:0:0:127',
|
||||
'::ffff:127', '0:0:0:0:0:ffff:127'))
|
||||
return ip == '::1' or ip == '0:0:0:0:0:0:0:1' # IPv6 loopback
|
||||
|
||||
|
||||
def getipaddrinfo(host):
|
||||
"""
|
||||
Filter out non-IP and bad IP addresses from getaddrinfo
|
||||
"""
|
||||
try:
|
||||
return [addrinfo for addrinfo in socket.getaddrinfo(host, None)
|
||||
if (addrinfo[0] == socket.AF_INET or
|
||||
addrinfo[0] == socket.AF_INET6)
|
||||
and isinstance(addrinfo[4][0], basestring)]
|
||||
except socket.error:
|
||||
return []
|
||||
|
||||
+228
-13
@@ -51,6 +51,7 @@ __all__ = [
|
||||
'IS_INT_IN_RANGE',
|
||||
'IS_IPV4',
|
||||
'IS_IPV6',
|
||||
'IS_IPADDRESS',
|
||||
'IS_LENGTH',
|
||||
'IS_LIST_OF',
|
||||
'IS_LOWER',
|
||||
@@ -538,7 +539,7 @@ class IS_IN_DB(Validator):
|
||||
records = self.dbset(table).select(table.ALL, **dd)
|
||||
self.theset = [str(r[self.kfield]) for r in records]
|
||||
if isinstance(self.label, str):
|
||||
self.labels = [self.label % dict(r) for r in records]
|
||||
self.labels = [self.label % r for r in records]
|
||||
else:
|
||||
self.labels = [self.label(r) for r in records]
|
||||
|
||||
@@ -648,11 +649,11 @@ class IS_NOT_IN_DB(Validator):
|
||||
id = self.record_id
|
||||
if isinstance(id, dict):
|
||||
fields = [table[f] for f in id]
|
||||
row = subset.select(*fields, **dict(limitby=(0, 1))).first()
|
||||
row = subset.select(*fields, **dict(limitby=(0, 1), orderby_on_limitby=False)).first()
|
||||
if row and any(str(row[f]) != str(id[f]) for f in id):
|
||||
return (value, translate(self.error_message))
|
||||
else:
|
||||
row = subset.select(table._id, field, limitby=(0, 1)).first()
|
||||
row = subset.select(table._id, field, limitby=(0, 1), orderby_on_limitby=False).first()
|
||||
if row and str(row.id) != str(id):
|
||||
return (value, translate(self.error_message))
|
||||
return (value, None)
|
||||
@@ -706,17 +707,20 @@ class IS_INT_IN_RANGE(Validator):
|
||||
self.minimum = self.maximum = None
|
||||
if minimum is None:
|
||||
if maximum is None:
|
||||
self.error_message = error_message or 'enter an integer'
|
||||
self.error_message = translate(
|
||||
error_message or 'enter an integer')
|
||||
else:
|
||||
self.maximum = int(maximum)
|
||||
if error_message is None:
|
||||
error_message = 'enter an integer less than or equal to %(max)g'
|
||||
error_message = \
|
||||
'enter an integer less than or equal to %(max)g'
|
||||
self.error_message = translate(
|
||||
error_message) % dict(max=self.maximum - 1)
|
||||
elif maximum is None:
|
||||
self.minimum = int(minimum)
|
||||
if error_message is None:
|
||||
error_message = 'enter an integer greater than or equal to %(min)g'
|
||||
error_message = \
|
||||
'enter an integer greater than or equal to %(min)g'
|
||||
self.error_message = translate(
|
||||
error_message) % dict(min=self.minimum)
|
||||
else:
|
||||
@@ -2510,17 +2514,18 @@ class IS_UPPER(Validator):
|
||||
return (value.decode('utf8').upper().encode('utf8'), None)
|
||||
|
||||
|
||||
def urlify(value, maxlen=80, keep_underscores=False):
|
||||
def urlify(s, maxlen=80, keep_underscores=False):
|
||||
"""
|
||||
Convert incoming string to a simplified ASCII subset.
|
||||
if (keep_underscores): underscores are retained in the string
|
||||
else: underscores are translated to hyphens (default)
|
||||
"""
|
||||
s = value.lower() # to lowercase
|
||||
s = s.decode('utf-8') # to utf-8
|
||||
if isinstance(s, str):
|
||||
s = s.decode('utf-8') # to unicode
|
||||
s = s.lower() # to lowercase
|
||||
s = unicodedata.normalize('NFKD', s) # normalize eg è => e, ñ => n
|
||||
s = s.encode('ASCII', 'ignore') # encode as ASCII
|
||||
s = re.sub('&\w+;', '', s) # strip html entities
|
||||
s = s.encode('ascii', 'ignore') # encode as ASCII
|
||||
s = re.sub('&\w+?;', '', s) # strip html entities
|
||||
if keep_underscores:
|
||||
s = re.sub('\s+', '-', s) # whitespace to hyphens
|
||||
s = re.sub('[^\w\-]', '', s)
|
||||
@@ -2654,8 +2659,8 @@ class IS_EMPTY_OR(Validator):
|
||||
if hasattr(other, 'options'):
|
||||
self.options = self._options
|
||||
|
||||
def _options(self):
|
||||
options = self.other.options()
|
||||
def _options(self, zero=False):
|
||||
options = self.other.options(zero=zero)
|
||||
if (not options or options[0][0] != '') and not self.multiple:
|
||||
options.insert(0, ('', ''))
|
||||
return options
|
||||
@@ -3562,6 +3567,216 @@ class IS_IPV6(Validator):
|
||||
return (value, translate(self.error_message))
|
||||
|
||||
|
||||
class IS_IPADDRESS(Validator):
|
||||
"""
|
||||
Checks if field's value is an IP Address (v4 or v6). Can be set to force
|
||||
addresses from within a specific range. Checks are done with the correct
|
||||
IS_IPV4 and IS_IPV6 validators.
|
||||
|
||||
Uses ipaddress library if found, falls back to PEP-3144 ipaddr.py from
|
||||
Google (in contrib).
|
||||
|
||||
Universal arguments:
|
||||
|
||||
minip: lowest allowed address; accepts:
|
||||
str, eg. 192.168.0.1
|
||||
list or tuple of octets, eg. [192, 168, 0, 1]
|
||||
maxip: highest allowed address; same as above
|
||||
invert: True to allow addresses only from outside of given range; note
|
||||
that range boundaries are not matched this way
|
||||
|
||||
IPv4 specific arguments:
|
||||
|
||||
is_localhost: localhost address treatment:
|
||||
None (default): indifferent
|
||||
True (enforce): query address must match localhost address
|
||||
(127.0.0.1)
|
||||
False (forbid): query address must not match localhost
|
||||
address
|
||||
is_private: same as above, except that query address is checked against
|
||||
two address ranges: 172.16.0.0 - 172.31.255.255 and
|
||||
192.168.0.0 - 192.168.255.255
|
||||
is_automatic: same as above, except that query address is checked against
|
||||
one address range: 169.254.0.0 - 169.254.255.255
|
||||
is_ipv4: None (default): indifferent
|
||||
True (enforce): must be an IPv4 address
|
||||
False (forbid): must NOT be an IPv4 address
|
||||
|
||||
IPv6 specific arguments:
|
||||
|
||||
is_link_local: Same as above but uses fe80::/10 range
|
||||
is_reserved: Same as above but uses IETF reserved range
|
||||
is_mulicast: Same as above but uses ff00::/8 range
|
||||
is_routeable: Similar to above but enforces not private, link_local,
|
||||
reserved or multicast
|
||||
is_6to4: Same as above but uses 2002::/16 range
|
||||
is_teredo: Same as above but uses 2001::/32 range
|
||||
subnets: value must be a member of at least one from list of subnets
|
||||
is_ipv6: None (default): indifferent
|
||||
True (enforce): must be an IPv6 address
|
||||
False (forbid): must NOT be an IPv6 address
|
||||
|
||||
Minip and maxip may also be lists or tuples of addresses in all above
|
||||
forms (str, int, list / tuple), allowing setup of multiple address ranges:
|
||||
|
||||
minip = (minip1, minip2, ... minipN)
|
||||
| | |
|
||||
| | |
|
||||
maxip = (maxip1, maxip2, ... maxipN)
|
||||
|
||||
Longer iterable will be truncated to match length of shorter one.
|
||||
|
||||
>>> IS_IPADDRESS()('192.168.1.5')
|
||||
('192.168.1.5', None)
|
||||
>>> IS_IPADDRESS(is_ipv6=False)('192.168.1.5')
|
||||
('192.168.1.5', None)
|
||||
>>> IS_IPADDRESS()('255.255.255.255')
|
||||
('255.255.255.255', None)
|
||||
>>> IS_IPADDRESS()('192.168.1.5 ')
|
||||
('192.168.1.5 ', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS()('192.168.1.1.5')
|
||||
('192.168.1.1.5', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS()('123.123')
|
||||
('123.123', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS()('1111.2.3.4')
|
||||
('1111.2.3.4', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS()('0111.2.3.4')
|
||||
('0111.2.3.4', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS()('256.2.3.4')
|
||||
('256.2.3.4', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS()('300.2.3.4')
|
||||
('300.2.3.4', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS(minip='192.168.1.0', maxip='192.168.1.255')('192.168.1.100')
|
||||
('192.168.1.100', None)
|
||||
>>> IS_IPADDRESS(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4')
|
||||
('1.2.3.4', 'bad ip')
|
||||
>>> IS_IPADDRESS(maxip='1.2.3.4', invert=True)('127.0.0.1')
|
||||
('127.0.0.1', None)
|
||||
>>> IS_IPADDRESS(maxip='192.168.1.4', invert=True)('192.168.1.4')
|
||||
('192.168.1.4', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS(is_localhost=True)('127.0.0.1')
|
||||
('127.0.0.1', None)
|
||||
>>> IS_IPADDRESS(is_localhost=True)('192.168.1.10')
|
||||
('192.168.1.10', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS(is_localhost=False)('127.0.0.1')
|
||||
('127.0.0.1', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS(maxip='100.0.0.0', is_localhost=True)('127.0.0.1')
|
||||
('127.0.0.1', 'enter valid IP address')
|
||||
|
||||
>>> IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af')
|
||||
('fe80::126c:8ffa:fe22:b3af', None)
|
||||
>>> IS_IPADDRESS(is_ipv4=False)('fe80::126c:8ffa:fe22:b3af')
|
||||
('fe80::126c:8ffa:fe22:b3af', None)
|
||||
>>> IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af ')
|
||||
('fe80::126c:8ffa:fe22:b3af ', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS(is_ipv4=True)('fe80::126c:8ffa:fe22:b3af')
|
||||
('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS(is_ipv6=True)('192.168.1.1')
|
||||
('192.168.1.1', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS(is_ipv6=True, error_message='bad ip')('192.168.1.1')
|
||||
('192.168.1.1', 'bad ip')
|
||||
>>> IS_IPADDRESS(is_link_local=True)('fe80::126c:8ffa:fe22:b3af')
|
||||
('fe80::126c:8ffa:fe22:b3af', None)
|
||||
>>> IS_IPADDRESS(is_link_local=False)('fe80::126c:8ffa:fe22:b3af')
|
||||
('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS(is_link_local=True)('2001::126c:8ffa:fe22:b3af')
|
||||
('2001::126c:8ffa:fe22:b3af', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS(is_multicast=True)('2001::126c:8ffa:fe22:b3af')
|
||||
('2001::126c:8ffa:fe22:b3af', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS(is_multicast=True)('ff00::126c:8ffa:fe22:b3af')
|
||||
('ff00::126c:8ffa:fe22:b3af', None)
|
||||
>>> IS_IPADDRESS(is_routeable=True)('2001::126c:8ffa:fe22:b3af')
|
||||
('2001::126c:8ffa:fe22:b3af', None)
|
||||
>>> IS_IPADDRESS(is_routeable=True)('ff00::126c:8ffa:fe22:b3af')
|
||||
('ff00::126c:8ffa:fe22:b3af', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS(subnets='2001::/32')('2001::8ffa:fe22:b3af')
|
||||
('2001::8ffa:fe22:b3af', None)
|
||||
>>> IS_IPADDRESS(subnets='fb00::/8')('2001::8ffa:fe22:b3af')
|
||||
('2001::8ffa:fe22:b3af', 'enter valid IP address')
|
||||
>>> IS_IPADDRESS(subnets=['fc00::/8','2001::/32'])('2001::8ffa:fe22:b3af')
|
||||
('2001::8ffa:fe22:b3af', None)
|
||||
>>> IS_IPADDRESS(subnets='invalidsubnet')('2001::8ffa:fe22:b3af')
|
||||
('2001::8ffa:fe22:b3af', 'invalid subnet provided')
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
minip='0.0.0.0',
|
||||
maxip='255.255.255.255',
|
||||
invert=False,
|
||||
is_localhost=None,
|
||||
is_private=None,
|
||||
is_automatic=None,
|
||||
is_ipv4=None,
|
||||
is_link_local=None,
|
||||
is_reserved=None,
|
||||
is_multicast=None,
|
||||
is_routeable=None,
|
||||
is_6to4=None,
|
||||
is_teredo=None,
|
||||
subnets=None,
|
||||
is_ipv6=None,
|
||||
error_message='enter valid IP address'):
|
||||
self.minip = minip,
|
||||
self.maxip = maxip,
|
||||
self.invert = invert
|
||||
self.is_localhost = is_localhost
|
||||
self.is_private = is_private
|
||||
self.is_automatic = is_automatic
|
||||
self.is_ipv4 = is_ipv4
|
||||
self.is_private = is_private
|
||||
self.is_link_local = is_link_local
|
||||
self.is_reserved = is_reserved
|
||||
self.is_multicast = is_multicast
|
||||
self.is_routeable = is_routeable
|
||||
self.is_6to4 = is_6to4
|
||||
self.is_teredo = is_teredo
|
||||
self.subnets = subnets
|
||||
self.is_ipv6 = is_ipv6
|
||||
self.error_message = error_message
|
||||
|
||||
def __call__(self, value):
|
||||
try:
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
from contrib import ipaddr as ipaddress
|
||||
|
||||
try:
|
||||
ip = ipaddress.ip_address(value)
|
||||
except ValueError, e:
|
||||
return (value, translate(self.error_message))
|
||||
|
||||
if self.is_ipv4 and isinstance(ip, ipaddress.IPv6Address):
|
||||
retval = (value, translate(self.error_message))
|
||||
elif self.is_ipv6 and isinstance(ip, ipaddress.IPv4Address):
|
||||
retval = (value, translate(self.error_message))
|
||||
elif self.is_ipv4 or isinstance(ip, ipaddress.IPv4Address):
|
||||
retval = IS_IPV4(
|
||||
minip=self.minip,
|
||||
maxip=self.maxip,
|
||||
invert=self.invert,
|
||||
is_localhost=self.is_localhost,
|
||||
is_private=self.is_private,
|
||||
is_automatic=self.is_automatic,
|
||||
error_message=self.error_message
|
||||
)(value)
|
||||
elif self.is_ipv6 or isinstance(ip, ipaddress.IPv6Address):
|
||||
retval = IS_IPV6(
|
||||
is_private=self.is_private,
|
||||
is_link_local=self.is_link_local,
|
||||
is_reserved=self.is_reserved,
|
||||
is_multicast=self.is_multicast,
|
||||
is_routeable=self.is_routeable,
|
||||
is_6to4=self.is_6to4,
|
||||
is_teredo=self.is_teredo,
|
||||
subnets=self.subnets,
|
||||
error_message=self.error_message
|
||||
)(value)
|
||||
else:
|
||||
retval = (value, translate(self.error_message))
|
||||
|
||||
return retval
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod(
|
||||
|
||||
+40
-17
@@ -15,7 +15,6 @@ import cStringIO
|
||||
import time
|
||||
import thread
|
||||
import threading
|
||||
import re
|
||||
import os
|
||||
import socket
|
||||
import signal
|
||||
@@ -25,10 +24,10 @@ import newcron
|
||||
import getpass
|
||||
import main
|
||||
|
||||
from fileutils import w2p_pack, read_file, write_file, create_welcome_w2p
|
||||
from fileutils import read_file, write_file, create_welcome_w2p
|
||||
from settings import global_settings
|
||||
from shell import run, test
|
||||
from utils import is_valid_ip_address, is_loopback_ip_address
|
||||
from utils import is_valid_ip_address, is_loopback_ip_address, getipaddrinfo
|
||||
|
||||
try:
|
||||
import Tkinter
|
||||
@@ -62,22 +61,36 @@ if not sys.version[:3] in ['2.4', '2.5', '2.6', '2.7']:
|
||||
logger = logging.getLogger("web2py")
|
||||
|
||||
|
||||
def run_system_tests():
|
||||
def run_system_tests(options):
|
||||
"""
|
||||
Runs unittests for gluon.tests
|
||||
"""
|
||||
import subprocess
|
||||
major_version = sys.version_info[0]
|
||||
minor_version = sys.version_info[1]
|
||||
print "minor_version = %r" % minor_version
|
||||
if major_version == 2:
|
||||
if minor_version in (5, 6):
|
||||
print "Python 2.5 or 2.6"
|
||||
ret = os.system("PYTHONPATH=. unit2 -v gluon.tests")
|
||||
sys.stderr.write("Python 2.5 or 2.6\n")
|
||||
ret = subprocess.call(['unit2', '-v', 'gluon.tests'])
|
||||
elif minor_version in (7,):
|
||||
print "Python 2.7"
|
||||
ret = os.system("PYTHONPATH=. python -m unittest -v gluon.tests")
|
||||
call_args = [sys.executable, '-m', 'unittest', '-v', 'gluon.tests']
|
||||
if options.with_coverage:
|
||||
try:
|
||||
import coverage
|
||||
coverage_config = os.environ.get("COVERAGE_PROCESS_START",
|
||||
os.path.join('gluon', 'tests', 'coverage.ini')
|
||||
)
|
||||
call_args = ['coverage', 'run', '--rcfile=%s' % coverage_config,
|
||||
'-m', 'unittest', '-v', 'gluon.tests']
|
||||
except:
|
||||
sys.stderr.write('Coverage was not installed, skipping\n')
|
||||
sys.stderr.write("Python 2.7\n")
|
||||
ret = subprocess.call(call_args)
|
||||
else:
|
||||
print "unknown python 2.x version"
|
||||
sys.stderr.write("unknown python 2.x version\n")
|
||||
ret = 256
|
||||
else:
|
||||
print "Only Python 2.x supported."
|
||||
sys.stderr.write("Only Python 2.x supported.\n")
|
||||
ret = 256
|
||||
sys.exit(ret and 1)
|
||||
|
||||
@@ -324,7 +337,6 @@ class web2pyDialog(object):
|
||||
self.tb = None
|
||||
|
||||
def update_schedulers(self, start=False):
|
||||
x = 0
|
||||
apps = []
|
||||
available_apps = [arq for arq in os.listdir('applications/')]
|
||||
available_apps = [arq for arq in available_apps
|
||||
@@ -902,7 +914,18 @@ def console():
|
||||
dest='run_system_tests',
|
||||
default=False,
|
||||
help=msg)
|
||||
|
||||
|
||||
msg = ('adds coverage reporting (needs --run_system_tests), '
|
||||
'python 2.7 and the coverage module installed. '
|
||||
'You can alter the default path setting the environmental '
|
||||
'var "COVERAGE_PROCESS_START". '
|
||||
'By default it takes gluon/tests/coverage.ini')
|
||||
parser.add_option('--with_coverage',
|
||||
action='store_true',
|
||||
dest='with_coverage',
|
||||
default=False,
|
||||
help=msg)
|
||||
|
||||
if '-A' in sys.argv:
|
||||
k = sys.argv.index('-A')
|
||||
elif '--args' in sys.argv:
|
||||
@@ -916,14 +939,14 @@ def console():
|
||||
global_settings.cmd_args = args
|
||||
|
||||
try:
|
||||
options.ips = list(set([
|
||||
ip[4][0] for ip in socket.getaddrinfo(socket.getfqdn(), 0)
|
||||
if not is_loopback_ip_address(ip[4][0])]))
|
||||
options.ips = list(set( # no duplicates
|
||||
[addrinfo[4][0] for addrinfo in getipaddrinfo(socket.getfqdn())
|
||||
if not is_loopback_ip_address(addrinfo=addrinfo)]))
|
||||
except socket.gaierror:
|
||||
options.ips = []
|
||||
|
||||
if options.run_system_tests:
|
||||
run_system_tests()
|
||||
run_system_tests(options)
|
||||
|
||||
if options.quiet:
|
||||
capture = cStringIO.StringIO()
|
||||
|
||||
@@ -136,6 +136,8 @@ class Web2pyService(Service):
|
||||
path=options.folder
|
||||
)
|
||||
try:
|
||||
from rewrite import load
|
||||
load()
|
||||
self.server.start()
|
||||
except:
|
||||
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/bin/bash
|
||||
# Autor: Nilton OS -- www.linuxpro.com.br
|
||||
echo 'setup-web2py-nginx-uwsgi-centos64.sh'
|
||||
echo 'Support CentOS 6.4'
|
||||
echo 'Installs Nginx 1.4 + uWSGI + Web2py'
|
||||
|
||||
|
||||
# Get Web2py Admin Password
|
||||
echo -e "Web2py Admin Password: \c "
|
||||
read PW
|
||||
|
||||
echo -e "Set Server Name Ex: web2py.domain.com : \c "
|
||||
read SERVER_FQDN
|
||||
|
||||
echo -e "Set Server IP: \c "
|
||||
read SERVER_IP
|
||||
|
||||
|
||||
echo "" >>/etc/hosts
|
||||
echo "$SERVER_IP $SERVER_FQDN" >>/etc/hosts
|
||||
|
||||
yum update -y
|
||||
|
||||
yum install -y http://mirror-fpt-telecom.fpt.net/fedora/epel/6/i386/epel-release-6-8.noarch.rpm
|
||||
yum clean all
|
||||
yum install -y gcc libxml2-devel python-devel python-pip PyXML unzip make sudo
|
||||
|
||||
## 64Bits System
|
||||
## yum install -y http://nginx.org/packages/rhel/6/x86_64/RPMS/nginx-1.4.0-1.el6.ngx.x86_64.rpm
|
||||
yum install -y http://nginx.org/packages/rhel/6/i386/RPMS/nginx-1.4.0-1.el6.ngx.i386.rpm
|
||||
|
||||
|
||||
pip-python install --upgrade pip
|
||||
PIPPATH=`which pip`
|
||||
$PIPPATH install --upgrade uwsgi
|
||||
|
||||
|
||||
# Prepare folders for uwsgi
|
||||
mkdir /etc/uwsgi
|
||||
mkdir /var/log/uwsgi
|
||||
mkdir -p /var/www/
|
||||
|
||||
#usermod -a -G apache nginx
|
||||
mkdir -p /etc/nginx/ssl/
|
||||
|
||||
|
||||
cd /etc/nginx/ssl
|
||||
openssl genrsa 1024 > web2py.key && chmod 400 web2py.key
|
||||
openssl req -new -x509 -nodes -sha1 -days 1780 -key web2py.key > web2py.crt
|
||||
openssl x509 -noout -fingerprint -text < web2py.crt > web2py.info
|
||||
|
||||
|
||||
echo 'server {
|
||||
listen YOUR_SERVER_IP:80;
|
||||
server_name YOUR_SERVER_FQDN;
|
||||
#to enable correct use of response.static_version
|
||||
#location ~* /(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$ {
|
||||
# alias /var/www/web2py/applications/$1/static/$2;
|
||||
# expires max;
|
||||
#}
|
||||
location ~* /(\w+)/static/ {
|
||||
root /var/www/web2py/applications/;
|
||||
#remove next comment on production
|
||||
#expires max;
|
||||
}
|
||||
location / {
|
||||
#uwsgi_pass 127.0.0.1:9001;
|
||||
uwsgi_pass unix:///var/www/web2py/logs/web2py.socket;
|
||||
include /etc/nginx/uwsgi_params;
|
||||
uwsgi_param UWSGI_SCHEME $scheme;
|
||||
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen YOUR_SERVER_IP:443 default_server ssl;
|
||||
server_name YOUR_SERVER_FQDN;
|
||||
ssl_certificate /etc/nginx/ssl/web2py.crt;
|
||||
ssl_certificate_key /etc/nginx/ssl/web2py.key;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_ciphers ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA;
|
||||
ssl_protocols SSLv3 TLSv1;
|
||||
keepalive_timeout 70;
|
||||
location / {
|
||||
#uwsgi_pass 127.0.0.1:9001;
|
||||
uwsgi_pass unix:///var/www/web2py/logs/web2py.socket;
|
||||
include /etc/nginx/uwsgi_params;
|
||||
uwsgi_param UWSGI_SCHEME $scheme;
|
||||
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||
}
|
||||
|
||||
}' >/etc/nginx/conf.d/web2py.conf
|
||||
|
||||
sed -i "s/YOUR_SERVER_IP/$SERVER_IP/" /etc/nginx/conf.d/web2py.conf
|
||||
sed -i "s/YOUR_SERVER_FQDN/$SERVER_FQDN/" /etc/nginx/conf.d/web2py.conf
|
||||
|
||||
|
||||
# Create configuration file /etc/uwsgi/web2py.xml
|
||||
echo '<uwsgi>
|
||||
<socket>/var/www/web2py/logs/web2py.socket</socket>
|
||||
<pythonpath>/var/www/web2py/</pythonpath>
|
||||
<mount>/=wsgihandler:application</mount>
|
||||
<master/>
|
||||
<processes>4</processes>
|
||||
<harakiri>60</harakiri>
|
||||
<reload-mercy>8</reload-mercy>
|
||||
<cpu-affinity>1</cpu-affinity>
|
||||
<stats>/var/www/web2py/logs/stats.socket</stats>
|
||||
<max-requests>2000</max-requests>
|
||||
<limit-as>512</limit-as>
|
||||
<reload-on-as>256</reload-on-as>
|
||||
<reload-on-rss>192</reload-on-rss>
|
||||
<uid>nginx</uid>
|
||||
<gid>nginx</gid>
|
||||
<cron>0 0 -1 -1 -1 python /var/www/web2py/web2py.py -Q -S welcome -M -R scripts/sessions2trash.py -A -o</cron>
|
||||
<no-orphans/>
|
||||
</uwsgi>' >/etc/uwsgi/web2py.xml
|
||||
|
||||
|
||||
cd /var/www/
|
||||
curl --progress -O http://web2py.com/examples/static/web2py_src.zip
|
||||
unzip web2py_src.zip && rm -rf web2py_src.zip
|
||||
# Download latest version of sessions2trash.py
|
||||
curl --output /var/www/web2py/scripts/sessions2trash.py http://web2py.googlecode.com/hg/scripts/sessions2trash.py
|
||||
chown -R nginx:nginx web2py
|
||||
cd /var/www/web2py
|
||||
sudo -u nginx python -c "from gluon.main import save_password; save_password('$PW',443)"
|
||||
|
||||
|
||||
|
||||
## Daemons /start/stop
|
||||
|
||||
echo '#!/bin/sh
|
||||
# Autor: Nilton OS -- www.linuxpro.com.br
|
||||
#
|
||||
#
|
||||
### BEGIN INIT INFO
|
||||
# Provides: uwsgi
|
||||
# Required-Start: $syslog $remote_fs
|
||||
# Should-Start: $time ypbind smtp
|
||||
# Required-Stop: $syslog $remote_fs
|
||||
# Should-Stop: ypbind smtp
|
||||
# Default-Start: 3 5
|
||||
# Default-Stop: 0 1 2 6
|
||||
### END INIT INFO
|
||||
|
||||
# Source function library.
|
||||
. /etc/rc.d/init.d/functions
|
||||
|
||||
# Check for missing binaries (stale symlinks should not happen)
|
||||
UWSGI_BIN=`which uwsgi`
|
||||
test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed";
|
||||
if [ "$1" = "stop" ]; then exit 0;
|
||||
else exit 5; fi; }
|
||||
|
||||
UWSGI_EMPEROR_MODE=true
|
||||
UWSGI_VASSALS="/etc/uwsgi/"
|
||||
UWSGI_OPTIONS="--enable-threads --logto /var/log/uwsgi/uwsgi.log"
|
||||
lockfile=/var/lock/subsys/uwsgi
|
||||
|
||||
UWSGI_OPTIONS="$UWSGI_OPTIONS --autoload"
|
||||
|
||||
if [ "$UWSGI_EMPEROR_MODE" = "true" ] ; then
|
||||
UWSGI_OPTIONS="$UWSGI_OPTIONS --emperor $UWSGI_VASSALS"
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
echo -n "Starting uWSGI "
|
||||
daemon $UWSGI_BIN $UWSGI_OPTIONS &
|
||||
;;
|
||||
stop)
|
||||
echo -n "Shutting down uWSGI "
|
||||
killproc $UWSGI_BIN
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
$0 start
|
||||
;;
|
||||
status)
|
||||
echo -n "Checking for service uWSGI "
|
||||
status $UWSGI_BIN
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|status|restart}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
exit 0 '> /etc/init.d/uwsgi
|
||||
|
||||
chmod +x /etc/init.d/uwsgi
|
||||
|
||||
/etc/init.d/uwsgi start
|
||||
/etc/init.d/nginx start
|
||||
|
||||
/etc/init.d/iptables stop
|
||||
chkconfig --del iptables
|
||||
|
||||
chkconfig --levels 235 uwsgi on
|
||||
chkconfig --levels 235 nginx on
|
||||
|
||||
## you can reload uwsgi with
|
||||
#/etc/init.d/uwsgi restart
|
||||
## to reload web2py only (without restarting uwsgi)
|
||||
# touch /etc/uwsgi/web2py.xml
|
||||
@@ -0,0 +1,226 @@
|
||||
#!/bin/bash
|
||||
echo 'setup-web2py-nginx-uwsgi-opensuse.sh'
|
||||
echo 'Requires OpenSUSE 12.3 32Bits and installs Nginx + uWSGI + Web2py'
|
||||
|
||||
|
||||
# Get Web2py Admin Password
|
||||
echo -e "Web2py Admin Password: \c "
|
||||
read PW
|
||||
|
||||
zypper clean && zypper refresh && zypper up
|
||||
zypper in -y nginx python-xml python-pip unzip sudo
|
||||
zypper in -y gcc python-devel libxml2-devel python-pip unzip
|
||||
pip install --upgrade pip
|
||||
PIPPATH=`which pip`
|
||||
$PIPPATH install --upgrade uwsgi
|
||||
|
||||
|
||||
# Prepare folders for uwsgi
|
||||
mkdir /etc/uwsgi
|
||||
mkdir /var/log/uwsgi
|
||||
|
||||
usermod -G www nginx
|
||||
|
||||
mkdir -p /etc/nginx/vhosts.d/
|
||||
mkdir -p /etc/nginx/ssl/
|
||||
|
||||
|
||||
cd /etc/nginx/ssl
|
||||
openssl genrsa 1024 > web2py.key
|
||||
chmod 400 web2py.key
|
||||
openssl req -new -x509 -nodes -sha1 -days 1780 -key web2py.key > web2py.crt
|
||||
openssl x509 -noout -fingerprint -text < web2py.crt > web2py.info
|
||||
|
||||
|
||||
echo 'server {
|
||||
listen 80;
|
||||
server_name $hostname;
|
||||
#to enable correct use of response.static_version
|
||||
#location ~* /(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$ {
|
||||
# alias /srv/www/web2py/applications/$1/static/$2;
|
||||
# expires max;
|
||||
#}
|
||||
location ~* /(\w+)/static/ {
|
||||
root /srv/www/web2py/applications/;
|
||||
#remove next comment on production
|
||||
#expires max;
|
||||
}
|
||||
location / {
|
||||
#uwsgi_pass 127.0.0.1:9001;
|
||||
uwsgi_pass unix:///tmp/web2py.socket;
|
||||
include /etc/nginx/uwsgi_params;
|
||||
uwsgi_param UWSGI_SCHEME $scheme;
|
||||
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 443 default_server ssl;
|
||||
server_name $hostname;
|
||||
ssl_certificate /etc/nginx/ssl/web2py.crt;
|
||||
ssl_certificate_key /etc/nginx/ssl/web2py.key;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_ciphers ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA;
|
||||
ssl_protocols SSLv3 TLSv1;
|
||||
keepalive_timeout 70;
|
||||
location / {
|
||||
#uwsgi_pass 127.0.0.1:9001;
|
||||
uwsgi_pass unix:///tmp/web2py.socket;
|
||||
include /etc/nginx/uwsgi_params;
|
||||
uwsgi_param UWSGI_SCHEME $scheme;
|
||||
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||
}
|
||||
|
||||
}' >/etc/nginx/vhosts.d/web2py.conf
|
||||
|
||||
|
||||
|
||||
# Create configuration file /etc/uwsgi/web2py.xml
|
||||
echo '<uwsgi>
|
||||
<socket>/tmp/web2py.socket</socket>
|
||||
<pythonpath>/srv/www/web2py/</pythonpath>
|
||||
<mount>/=wsgihandler:application</mount>
|
||||
<master/>
|
||||
<processes>4</processes>
|
||||
<harakiri>60</harakiri>
|
||||
<reload-mercy>8</reload-mercy>
|
||||
<cpu-affinity>1</cpu-affinity>
|
||||
<stats>/tmp/stats.socket</stats>
|
||||
<max-requests>2000</max-requests>
|
||||
<limit-as>512</limit-as>
|
||||
<reload-on-as>256</reload-on-as>
|
||||
<reload-on-rss>192</reload-on-rss>
|
||||
<uid>nginx</uid>
|
||||
<gid>www</gid>
|
||||
<cron>0 0 -1 -1 -1 python /srv/www/web2py/web2py.py -Q -S welcome -M -R scripts/sessions2trash.py -A -o</cron>
|
||||
<no-orphans/>
|
||||
</uwsgi>' >/etc/uwsgi/web2py.xml
|
||||
|
||||
|
||||
cd /srv/www/
|
||||
wget http://web2py.com/examples/static/web2py_src.zip
|
||||
unzip web2py_src.zip
|
||||
rm web2py_src.zip
|
||||
# Download latest version of sessions2trash.py
|
||||
wget http://web2py.googlecode.com/hg/scripts/sessions2trash.py -O /srv/www/web2py/scripts/sessions2trash.py
|
||||
chown -R nginx:www web2py
|
||||
cd /srv/www/web2py
|
||||
sudo -u nginx python -c "from gluon.main import save_password; save_password('$PW',443)"
|
||||
|
||||
|
||||
|
||||
## Daemons /start/stop
|
||||
|
||||
echo '#!/bin/sh
|
||||
# Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany.
|
||||
#
|
||||
# Author: James Oakley
|
||||
#
|
||||
# /etc/init.d/uwsgi
|
||||
# and its symbolic link
|
||||
# /(usr/)sbin/rcuwsgi
|
||||
#
|
||||
# LSB compatible service control script; see http://www.linuxbase.org/spec/
|
||||
#
|
||||
### BEGIN INIT INFO
|
||||
# Provides: uwsgi
|
||||
# Required-Start: $syslog $remote_fs
|
||||
# Should-Start: $time ypbind smtp
|
||||
# Required-Stop: $syslog $remote_fs
|
||||
# Should-Stop: ypbind smtp
|
||||
# Default-Start: 3 5
|
||||
# Default-Stop: 0 1 2 6
|
||||
# Short-Description: Application Container Server for Networked/Clustered Web Applications
|
||||
# Description: Application Container Server for Networked/Clustered Web Applications
|
||||
### END INIT INFO
|
||||
|
||||
# Check for missing binaries (stale symlinks should not happen)
|
||||
UWSGI_BIN=/usr/bin/uwsgi
|
||||
test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed";
|
||||
if [ "$1" = "stop" ]; then exit 0;
|
||||
else exit 5; fi; }
|
||||
|
||||
UWSGI_EMPEROR_MODE=true
|
||||
UWSGI_VASSALS="/etc/uwsgi/"
|
||||
UWSGI_OPTIONS="--logto /var/log/uwsgi/uwsgi.log"
|
||||
|
||||
|
||||
UWSGI_OPTIONS="$UWSGI_OPTIONS --autoload"
|
||||
|
||||
if [ "$UWSGI_EMPEROR_MODE" = "true" ] ; then
|
||||
UWSGI_OPTIONS="$UWSGI_OPTIONS --emperor $UWSGI_VASSALS"
|
||||
fi
|
||||
|
||||
. /etc/rc.status
|
||||
|
||||
rc_reset
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
echo -n "Starting uWSGI "
|
||||
/sbin/startproc $UWSGI_BIN $UWSGI_OPTIONS
|
||||
rc_status -v
|
||||
;;
|
||||
stop)
|
||||
echo -n "Shutting down uWSGI "
|
||||
/sbin/killproc $UWSGI_BIN
|
||||
rc_status -v
|
||||
;;
|
||||
try-restart|condrestart)
|
||||
if test "$1" = "condrestart"; then
|
||||
echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}"
|
||||
fi
|
||||
$0 status
|
||||
if test $? = 0; then
|
||||
$0 restart
|
||||
else
|
||||
rc_reset
|
||||
fi
|
||||
rc_status
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
$0 start
|
||||
rc_status
|
||||
;;
|
||||
force-reload)
|
||||
echo -n "Reload service uWSGI "
|
||||
/sbin/killproc -HUP $UWSGI_BIN
|
||||
rc_status -v
|
||||
;;
|
||||
reload)
|
||||
echo -n "Reload service uWSGI "
|
||||
/sbin/killproc -HUP $UWSGI_BIN
|
||||
rc_status -v
|
||||
;;
|
||||
status)
|
||||
echo -n "Checking for service uWSGI "
|
||||
/sbin/checkproc $UWSGI_BIN
|
||||
rc_status -v
|
||||
;;
|
||||
probe)
|
||||
echo -n "uWSGI does not support probe "
|
||||
rc_failed 3
|
||||
rc_status -v
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
rc_exit '> /etc/init.d/uwsgi
|
||||
|
||||
|
||||
chmod +x /etc/init.d/uwsgi
|
||||
/etc/init.d/uwsgi start
|
||||
/etc/init.d/nginx restart
|
||||
|
||||
|
||||
chkconfig --add uwsgi
|
||||
chkconfig --add nginx
|
||||
|
||||
## you can reload uwsgi with
|
||||
#/etc/init.d/uwsgi restart
|
||||
## to reload web2py only (without restarting uwsgi)
|
||||
# touch /etc/uwsgi/web2py.xml
|
||||
Reference in New Issue
Block a user