Compare commits

...

49 Commits

Author SHA1 Message Date
mdipierro 8dcb4326c6 R-2.18.2 2019-02-25 22:08:20 -08:00
mdipierro d5d2cf55de removed teritem which breaks py3 2019-02-25 21:44:53 -08:00
mdipierro 818494d0f3 fixed problems with byte escaping in py3 2019-02-25 21:30:04 -08:00
mdipierro c439355c1a removed duplicate function local_html_escape 2019-02-24 02:12:22 -08:00
mdipierro b58b7da18f validatos no longer depend on T 2019-02-24 01:51:18 -08:00
mdipierro afaf57f6da R-2.18.1 2019-02-23 21:46:01 -08:00
mdipierro 7a8b5695bc syncing dal 2019-02-23 19:07:28 -08:00
mdipierro 2d62c6ce88 updated pydal 2019-02-23 17:56:01 -08:00
mdipierro 263b1d0156 removed gluon/tests/test_template because moved to yatl 2019-02-23 16:38:31 -08:00
mdipierro e199d0b495 added yatl to submodules 2019-02-23 16:28:29 -08:00
mdipierro 632946ac79 Merge branch 'master' of github.com:web2py/web2py 2019-02-23 16:25:39 -08:00
mdipierro 0663088d7f added yatl 2019-02-23 16:24:33 -08:00
mdipierro 943e3b4de8 Merge pull request #2101 from jvanbraekel/patch-1
Fix redirect url when login failed (invalid login)
2019-02-23 15:55:54 -08:00
jvanbraekel b21cfb53f3 Fix redirect url when login failed (invalid login)
Hello,

This is broken since 1457e12f70 . 'self.url' does'nt have any get_vars,post_vars, only one parameter called vars. I suggest to pass all variables instead  with request.vars  that will encompass both get and post vars... Another possibility would be to change the self.url signature but I don't see the purpose of making the distinction between post and get vars in this case.
2019-02-21 15:17:40 +01:00
mdipierro f06c60b963 space cleanup 2019-02-17 17:20:38 -08:00
mdipierro bf23e7bb0e better support of virtual fields in grid by accessing new sqlrows.colnames_fields, thanks Paolo 2019-02-17 17:02:24 -08:00
mdipierro 001b77b5f3 better regex escaping 2019-02-17 16:45:45 -08:00
mdipierro 23bcfe4239 Merge pull request #2094 from sethkinast/is-strong
gluon.validators: update IS_STRONG to behave as it did in web2py < 2.15
2019-02-17 16:19:26 -08:00
Seth Kinast 0077f29d71 gluon.validators: update IS_STRONG to behave as it did in web2py < 2.15
We used to compare `type(self.upper) == int` but the check was changed
to `isinstance(self.upper, int)`. Because bool is an instance of int but is not
the type int, the meaning of the checks changed to treat False as 0.

This patch specifically differentiates between False and 0.
  - False means "no requirements for this character class".
  - 0 means "exactly 0 of this character class".

Fixes #2093
2019-02-13 21:14:05 -08:00
mdipierro ddc99b3552 validators are back in web2py for now 2019-02-10 22:15:12 -08:00
mdipierro cf278d3741 validators now tested in pydal and code moved there 2019-02-10 21:32:31 -08:00
mdipierro 21b0bac625 Merge branch 'master' of github.com:web2py/web2py 2019-02-10 21:10:38 -08:00
mdipierro e1cbaf1cb3 made template independent of everything else (ready to bedome its own package) and other minor fixes 2019-02-10 21:10:25 -08:00
mdipierro b3175d092e Merge pull request #2088 from vinyldarkscratch/python-3
Introduce better Python 3 compatibility across all files
2019-02-10 20:09:27 -08:00
mdipierro d8bdc47542 Merge pull request #2084 from vinyldarkscratch/fix-2083
Fix login form clearing after failed login
2019-02-10 20:09:14 -08:00
mdipierro d16b90c04f pydal 19.02 2019-02-10 20:08:32 -08:00
Vinyl Darkscratch f4ffac58ae Convert to print function 2019-02-09 10:03:49 -08:00
Vinyl Darkscratch c856c2b0e2 Fix tabs + indents mix 2019-02-09 10:03:41 -08:00
Vinyl Darkscratch 405527672c Update Python version in setup scripts 2019-02-09 09:41:54 -08:00
Vinyl Darkscratch f434ebec8a Update Python 3 compatibility 2019-02-09 00:01:11 -08:00
Vinyl Darkscratch 8852df7a7a Delete password rather than simply clearing it 2019-02-04 09:56:58 -08:00
mdipierro fcea326855 using _compat.py from pydal 2019-02-03 21:36:26 -08:00
mdipierro 5e1527a8e2 many improvements to grid, thanks Paolo 2019-02-03 21:24:52 -08:00
mdipierro 7c515f48f6 Merge pull request #2090 from vinyldarkscratch/appveyor/add-python-3.7
Add Python 3.7 to AppVeyor
2019-02-03 21:17:20 -08:00
mdipierro 0bf55f18d6 Merge pull request #2086 from vinyldarkscratch/patch-5
Fix tools.py _get_login_settings()
2019-02-03 21:15:01 -08:00
mdipierro 2fb3a78d75 Merge pull request #2076 from ilvalle/ci_updates
updated travis env
2019-02-03 21:13:01 -08:00
mdipierro 3f56a0ad70 Merge pull request #2075 from benlawraus/benlawraus
unicode() is not a standard python 3 function so using str() if pytho…
2019-02-03 21:10:43 -08:00
mdipierro b501d77903 Merge pull request #2072 from vinyldarkscratch/admin-debug-interact-patch
Fix translation strings to use Markmin
2019-02-03 21:10:13 -08:00
mdipierro 252835d8e7 Merge pull request #2071 from vinyldarkscratch/translations/de
Update German translations
2019-02-03 21:09:48 -08:00
Vinyl Darkscratch 4529ee5ead Add Python 3.7 to AppVeyor 2019-02-02 06:59:36 -08:00
Vinyl Darkscratch 3269e1a3f5 Fix #2044 2019-01-29 20:39:16 -08:00
Vinyl Darkscratch 1457e12f70 Don't remember password on failed login 2019-01-29 17:30:10 -08:00
Vinyl Darkscratch 603f54fade Fix login form clearing after failed login 2019-01-29 02:11:18 -08:00
ilvalle 04626f83cf updated travis env 2019-01-17 20:10:27 +01:00
Ben Lawrence b23131b3d6 unicode() is not a standard python 3 function so using str() if python 3 test is true 2019-01-17 09:58:56 -08:00
Vinyl Darkscratch fc3e9d95b8 Fix translation strings to use Markmin 2019-01-05 05:42:15 -08:00
Vinyl Darkscratch b1e397c223 Update German translations for admin (2nd revision) 2019-01-05 03:48:38 -08:00
Vinyl Darkscratch 41fd73e26c Update German translations for welcome 2019-01-04 15:24:35 -08:00
Vinyl Darkscratch 975949ab18 Update German translations for admin 2019-01-04 15:23:15 -08:00
74 changed files with 741 additions and 2202 deletions
+3
View File
@@ -1,3 +1,6 @@
[submodule "gluon/packages/dal"] [submodule "gluon/packages/dal"]
path = gluon/packages/dal path = gluon/packages/dal
url = https://github.com/web2py/pydal.git url = https://github.com/web2py/pydal.git
[submodule "gluon/packages/yatl"]
path = gluon/packages/yatl
url = https://github.com/web2py/yatl
+8 -14
View File
@@ -4,20 +4,22 @@ sudo: required
cache: pip cache: pip
dist: "trusty" dist: "xenial"
services:
- mysql
python: python:
- '2.7' - '2.7'
- '3.5' - '3.5'
- '3.6' - '3.6'
- '3.6-dev' - '3.6-dev'
- '3.7'
- '3.7-dev' - '3.7-dev'
- 'pypy-5.3.1' - 'pypy3.5'
- 'pypy3.5-5.7.1-beta'
matrix: matrix:
allow_failures: allow_failures:
- python: 'pypy3.5-5.7.1-beta'
- python: '3.6-dev' - python: '3.6-dev'
- python: '3.7-dev' - python: '3.7-dev'
@@ -27,11 +29,9 @@ install:
before_script: before_script:
- pip install coverage - pip install coverage
- pip install codecov - pip install codecov
- mysql -e 'create database pydal;'
- psql -c 'create database pydal;' -U postgres
- psql -c 'create extension postgis;' -U postgres -d pydal;
- psql -c 'SHOW SERVER_VERSION' -U postgres
before_install:
- mysql -e 'create database pydal;'
script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage
@@ -41,9 +41,3 @@ after_success:
notifications: notifications:
email: true email: true
addons:
postgresql: "9.4"
apt:
packages:
- postgresql-9.4-postgis-2.3
+6
View File
@@ -1,3 +1,9 @@
## 2.18.1-2.18.2
- pydal 19.02
- made template its own module (Yet Another Template Language)
- improved python 3.4-3.7 support
- bug fixes
## 2.17.1-2 ## 2.17.1-2
- pydal 18.08 - pydal 18.08
- many small bug fixes - many small bug fixes
+1 -1
View File
@@ -44,7 +44,7 @@ rmfiles:
rm -rf applications/examples/uploads/* rm -rf applications/examples/uploads/*
src: src:
### Use semantic versioning ### Use semantic versioning
echo 'Version 2.17.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION echo 'Version 2.18.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files ### rm -f all junk files
#make clean #make clean
# make rmfiles # make rmfiles
+1 -1
View File
@@ -1 +1 @@
Version 2.17.2-stable+timestamp.2018.10.06.11.34.06 Version 2.18.2-stable+timestamp.2019.02.25.21.46.30
+3 -3
View File
@@ -185,9 +185,9 @@ def select():
is_imap = db._uri.startswith("imap://") is_imap = db._uri.startswith("imap://")
except (KeyError, AttributeError, TypeError): except (KeyError, AttributeError, TypeError):
is_imap = False is_imap = False
regex = re.compile('(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)') regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)')
if len(request.args) > 1 and hasattr(db[request.args[1]], '_primarykey'): if len(request.args) > 1 and hasattr(db[request.args[1]], '_primarykey'):
regex = re.compile('(?P<table>\w+)\.(?P<field>\w+)=(?P<value>.+)') regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>.+)')
if request.vars.query: if request.vars.query:
match = regex.match(request.vars.query) match = regex.match(request.vars.query)
if match: if match:
@@ -237,7 +237,7 @@ def select():
tb = None tb = None
if form.accepts(request.vars, formname=None): if form.accepts(request.vars, formname=None):
regex = re.compile(request.args[0] + '\.(?P<table>\w+)\..+') regex = re.compile(request.args[0] + r'\.(?P<table>\w+)\..+')
match = regex.match(form.vars.query.strip()) match = regex.match(form.vars.query.strip())
if match: if match:
table = match.group('table') table = match.group('table')
+267 -267
View File
@@ -2,92 +2,92 @@
{ {
'!langcode!': 'de', '!langcode!': 'de',
'!langname!': 'Deutsch', '!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', '"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',
'"User Exception" debug mode. ': '"User Exception" debug mode. ', '"User Exception" debug mode. ': '"Benutzerausnahme" Debug-Modus.',
'%s': '%s', '%s': '%s',
'%s %%{row} deleted': '%s %%{row} Zeilen gelöscht', '%s %%{row} deleted': '%s %%{Reihe} gelöscht',
'%s %%{row} updated': '%s %%{row} Zeilen aktualisiert', '%s %%{row} updated': '%s %%{Reihe} aktualisiert',
'%s selected': '%s selected', '%s selected': '%s ausgewählt',
'%s students registered': '%s students registered', '%s students registered': '%s Studenten registriert',
'%Y-%m-%d': '%d.%m.%Y', '%Y-%m-%d': '%d.%m.%Y',
'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S', '%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S',
'(requires internet access)': '(Benötigt Internetzugang)', '(requires internet access)': '(Erfordert Internetzugang)',
'(requires internet access, experimental)': '(Benötigt Internetzugang)', '(requires internet access, experimental)': '(Erfordert Internetzugang, experimentell)',
'(something like "it-it")': '(so etwas wie "it-it")', '(something like "it-it")': '(so etwas wie "it-it")',
'(version %s)': '(version %s)', '(version %s)': '(version %s)',
'?': '?', '?': '?',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(Datei **gluon/contrib/plural_rules/%s.py** wurde nicht gefunden)', '@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(Datei **gluon/contrib/plural_rules/%s.py** wurde nicht gefunden)',
'@markmin\x01An error occured, please [[reload %s]] the page': 'Ein Fehler ist aufgetreten, bitte [[reload %s]] sie die Seite erneut', '@markmin\x01An error occured, please [[reload %s]] the page': 'Ein Fehler ist aufgetreten, bitte [[laden %s]] Sie die Seite neu',
'@markmin\x01Searching: **%s** %%{file}': '@markmin\x01Suche: **%s** Dateien', '@markmin\x01Searching: **%s** %%{file}': 'Suche: **% s ** %% {Datei}',
'A new version of web2py is available': 'Eine neue Version von web2py ist verfügbar', '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', 'A new version of web2py is available: %s': 'Eine neue Version von web2py ist verfügbar: %s',
'Abort': 'Abbrechen', 'Abort': 'Verwerfen',
'About': 'Über', 'About': 'Über',
'About application': 'Über die Anwendung', 'About application': 'Über die Anwendung',
'Accept Terms': 'Accept Terms', 'Accept Terms': 'Nutzerbedingungen akzeptieren',
'Add breakpoint': 'Add breakpoint', 'Add breakpoint': 'Breakpoint hinzufügen',
'Additional code for your application': 'Zusätzlicher Code für Ihre Anwendung', 'Additional code for your application': 'Zusätzlicher Code für Ihre Anwendung',
'Admin design page': 'Admin design page', 'Admin design page': 'Admin-Design-Seite',
'admin disabled because no admin password': 'admin ist deaktiviert, weil kein Admin-Passwort gesetzt ist', 'admin disabled because no admin password': 'Admin ist deaktiviert, da kein Admin-Passwort gesetzt ist',
'admin disabled because not supported on google app engine': 'admin disabled because not supported on google app engine', 'admin disabled because not supported on google app engine': 'Admin deaktiviert, da auf Google App Engine nicht unterstützt',
'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 not supported on google apps engine': 'Admin deaktiviert, da es auf Google Apps Engine nicht unterstützt wird.',
'admin disabled because too many invalid login attempts': 'admin disabled because too many invalid login attempts', 'admin disabled because too many invalid login attempts': 'Admin deaktiviert, weil zu viele ungültige Anmeldeversuche',
'admin disabled because unable to access password file': 'admin ist deaktiviert, weil kein Zugriff auf die Passwortdatei besteht', 'admin disabled because unable to access password file': 'Admin deaktiviert, da der Zugriff auf die Kennwortdatei nicht möglich ist',
'Admin is disabled because insecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals', '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 is disabled because unsecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals',
'Admin language': 'Admin-Sprache', 'Admin language': 'Admin-Sprache',
'Admin versioning page': 'Admin versioning page', 'Admin versioning page': 'Admin-Versionierungsseite',
'administrative interface': 'Administrative Schnittstelle', 'administrative interface': 'Administratives Interface',
'Administrator Password:': 'Administrator Passwort:', 'Administrator Password:': 'Administrator Passwort:',
'An error occured, please %s the page': 'Ein Fehler ist aufgetereten, bitte %s die Seite', '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 (required):': 'und benenne sie se um (erforderlich):',
'and rename it:': ' und benenne sie um:', 'and rename it:': ' und benenne sie es um:',
'App does not exist or you are not authorized': 'App does not exist or you are not authorized', 'App does not exist or you are not authorized': 'App existiert nicht oder Sie sind nicht autorisiert',
'appadmin': 'appadmin', 'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals', 'appadmin is disabled because insecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals',
'Application': 'Anwendung', 'Application': 'Anwendung',
'application "%s" uninstalled': 'Anwendung "%s" deinstalliert', 'application "%s" uninstalled': 'Anwendung "%s" deinstalliert',
'Application cannot be generated in demo mode': 'Application cannot be generated in demo mode', 'Application cannot be generated in demo mode': 'Anwendung kann nicht im Demo-Modus erstellt werden',
'application compiled': 'Anwendung kompiliert', 'application compiled': 'Anwendung kompiliert',
'Application exists already': 'Application exists already', 'Application exists already': 'Anwendung existiert bereits',
'application is compiled and cannot be designed': 'Die Anwendung ist kompiliert und kann deswegen nicht mehr geändert werden', 'application is compiled and cannot be designed': 'Die Anwendung ist kompiliert und kann deswegen nicht mehr geändert werden',
'Application name:': 'Name der Anwendung:', 'Application name:': 'Anwendungsname:',
'Application updated via git pull': 'Application updated via git pull', 'Application updated via git pull': 'Die Anwendung wurde über Git Pull updated',
'are not used': 'werden nicht verwendet', 'are not used': 'werden nicht verwendet',
'are not used yet': 'werden bisher nicht verwendet', 'are not used yet': 'werden bisher nicht verwendet',
'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 file "%s"?': 'Sind Sie sich sicher, dass Sie die Datei "%s" löschen wollen?',
'Are you sure you want to delete plugin "%s"?': 'Are you sure you want to delete plugin "%s"?', 'Are you sure you want to delete plugin "%s"?': 'Möchten Sie das Plugin "%s" wirklich löschen?',
'Are you sure you want to delete this object?': 'Sind Sie sich sicher, dass Sie dieses Objekt löschen wollen?', 'Are you sure you want to delete this object?': 'Sind Sie sich sicher, dass Sie dieses Objekt löschen wollen?',
'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 die Anwendung "%s" deinstallieren wollen',
'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 die Anwendung "%s" deinstallieren wollen?',
'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?',
'Are you sure?': 'Are you sure?', 'Are you sure?': 'Bist du sicher?',
'arguments': 'arguments', 'arguments': 'Argumente',
'at char %s': 'bei Zeichen %s', 'at char %s': 'bei Zeichen %s',
'at line %s': 'in Linie %s', 'at line %s': 'in Linie %s',
'ATTENTION:': 'ATTENTION:', 'ATTENTION:': 'BEACHTUNG:',
'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: 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: 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: 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!', 'ATTENTION: you cannot edit the running application!': 'ACHTUNG: Eine laufende Anwendung kann nicht editiert werden!',
'Authentication': 'Authentifizierung', 'Authentication': 'Authentifizierung',
'Autocomplete Python Code': 'Autocomplete Python Code', 'Autocomplete Python Code': 'Python-Code automatisch vervollständigen',
'Available databases and tables': 'Verfügbare Datenbanken und Tabellen', 'Available databases and tables': 'Verfügbare Datenbanken und Tabellen',
'Available Databases and Tables': 'Available Databases and Tables', 'Available Databases and Tables': 'Verfügbare Datenbanken und Tabellen',
'back': 'Zurück', 'back': 'Zurück',
'Back to the plugins list': 'Back to the plugins list', 'Back to the plugins list': 'Zurück zur Plugin-Liste',
'Back to wizard': 'Back to wizard', 'Back to wizard': 'Zurück zum Wizard',
'Basics': 'Basics', 'Basics': 'Basics',
'beautify': 'Verschönern', 'beautify': 'Verschönern',
'Begin': 'Begin', 'Begin': 'Start',
'breakpoint': 'breakpoint', 'breakpoint': 'breakpoint',
'Breakpoints': 'Breakpoints', 'Breakpoints': 'Breakpoints',
'breakpoints': 'breakpoints', 'breakpoints': 'breakpoints',
'Bulk Register': 'Bulk Register', 'Bulk Register': 'Masse-registrierung',
'Bulk Student Registration': 'Bulk Student Registration', 'Bulk Student Registration': 'Massen-Studenten registrierung',
'Cache': 'Cache', 'Cache': 'Zwischenspeicher',
'cache': 'Zwischenspeicher', 'cache': 'Zwischenspeicher',
'Cache Cleared': 'Cache Cleared', 'Cache Cleared': 'Cache geleert',
'Cache Keys': 'Cache Keys', 'Cache Keys': 'Cache Keys',
'cache, errors and sessions cleaned': 'Zwischenspeicher (cache), Fehler und Sitzungen (sessions) gelöscht', 'cache, errors and sessions cleaned': 'Zwischenspeicher (cache), Fehler und Sitzungen (sessions) gelöscht',
'call': 'Aufruf', 'call': 'Aufruf',
@@ -95,10 +95,10 @@
'Cancel': 'Abbrechen', 'Cancel': 'Abbrechen',
'Cannot be empty': 'Darf nicht leer 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 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 compile: there are errors in your app:': 'Cannot compile: there are errors in your app:', 'Cannot compile: there are errors in your app:': 'Kompilieren nicht möglich: Es gibt Fehler in Ihrer App:',
'cannot create file': 'Kann Datei nicht erstellen', 'cannot create file': 'Kann Datei nicht erstellen',
'cannot upload file "%(filename)s"': 'Kann Datei nicht Hochladen "%(filename)s"', 'cannot upload file "%(filename)s"': 'Kann Datei nicht Hochladen "%(filename)s"',
'Change Admin Password': 'Change Admin Password', 'Change Admin Password': 'Admin-Passwort ändern',
'Change admin password': 'Administrator-Passwort ändern', 'Change admin password': 'Administrator-Passwort ändern',
'change editor settings': 'Editoreinstellungen ändern', 'change editor settings': 'Editoreinstellungen ändern',
'Change Password': 'Passwort ändern', 'Change Password': 'Passwort ändern',
@@ -109,30 +109,30 @@
'Check to delete': 'Markiere zum löschen', 'Check to delete': 'Markiere zum löschen',
'Checking for upgrades...': 'Überprüfe auf Updates...', 'Checking for upgrades...': 'Überprüfe auf Updates...',
'Clean': 'Leeren', 'Clean': 'Leeren',
'Clear': 'Clear', 'Clear': 'Leeren',
'Clear CACHE?': 'Clear CACHE?', 'Clear CACHE?': 'CACHE leeren?',
'Clear DISK': 'Clear DISK', 'Clear DISK': 'DISK leeren',
'Clear RAM': 'Clear RAM', 'Clear RAM': 'RAM leeren',
'click here for online examples': 'hier klicken für online Beispiele', '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 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 row to expand traceback': 'Klicke auf die Zeile für Fehlerverfolgung',
'Click row to view a ticket': 'Click row to view a ticket', 'Click row to view a ticket': 'Klicke auf eine Zeile, um ein Ticket anzusehen',
'click to check for upgrades': 'hier klicken um nach Upgrades zu suchen', 'click to check for upgrades': 'hier klicken um nach Upgrades zu suchen',
'Client IP': 'Client IP', 'Client IP': 'Client IP',
'code': 'code', 'code': 'Code',
'Code listing': 'Code listing', 'Code listing': 'Codeauflistung',
'collapse/expand all': 'alles zu- bzw. aufklappen', 'collapse/expand all': 'alles zu- bzw. aufklappen',
'Command': 'Command', 'Command': 'Befehl',
'Comment:': 'Comment:', 'Comment:': 'Kommentar:',
'Commit': 'Commit', 'Commit': 'Commit',
'Commit form': 'Commit form', 'Commit form': 'Formular ausfüllen',
'Committed files': 'Committed files', 'Committed files': 'Festgelegte Dateien',
'Compile': 'kompilieren', 'Compile': 'kompilieren',
'Compile (all or nothing)': 'Compile (all or nothing)', 'Compile (all or nothing)': 'Kompilieren (alles oder nichts)',
'Compile (skip failed views)': 'Compile (skip failed views)', 'Compile (skip failed views)': 'Kompilieren (Fehlgeschlagene Ansichten überspringen)',
'compiled application removed': 'kompilierte Anwendung gelöscht', 'compiled application removed': 'kompilierte Anwendung gelöscht',
'Condition': 'Condition', 'Condition': 'Bedingung',
'continue': 'continue', 'continue': 'fortsetzen',
'Controller': 'Controller', 'Controller': 'Controller',
'Controllers': 'Controllers', 'Controllers': 'Controllers',
'controllers': 'controllers', 'controllers': 'controllers',
@@ -142,11 +142,11 @@
'create file with filename:': 'Erzeuge Datei mit Dateinamen:', 'create file with filename:': 'Erzeuge Datei mit Dateinamen:',
'create new application:': 'Erzeuge neue Anwendung:', 'create new application:': 'Erzeuge neue Anwendung:',
'Create new simple application': 'Erzeuge neue Anwendung', 'Create new simple application': 'Erzeuge neue Anwendung',
'Create/Upload': 'Create/Upload', 'Create/Upload': 'Erstellen/Uploaden',
'created by': 'Erstellt von', 'created by': 'Erstellt von',
'Created by:': 'Created by:', 'Created by:': 'Erstellt von:',
'Created On': 'Created On', 'Created On': 'Erstellt am',
'Created on:': 'Created on:', 'Created on:': 'Erstellt am:',
'crontab': 'crontab', 'crontab': 'crontab',
'Current request': 'Aktuelle Anfrage (request)', 'Current request': 'Aktuelle Anfrage (request)',
'Current response': 'Aktuelle Antwort (response)', 'Current response': 'Aktuelle Antwort (response)',
@@ -157,7 +157,7 @@
'data uploaded': 'Daten hochgeladen', 'data uploaded': 'Daten hochgeladen',
'Database': 'Datenbank', 'Database': 'Datenbank',
'database': 'Datenbank', 'database': 'Datenbank',
'Database %s select': 'Database %s select', 'Database %s select': 'Database %s ausgewählt',
'database %s select': 'Datenbank %s ausgewählt', 'database %s select': 'Datenbank %s ausgewählt',
'Database administration': 'Database administration', 'Database administration': 'Database administration',
'database administration': 'Datenbankadministration', 'database administration': 'Datenbankadministration',
@@ -173,49 +173,49 @@
'delete plugin': 'Plugin löschen', 'delete plugin': 'Plugin löschen',
'Delete this file (you will be asked to confirm deletion)': 'Diese Datei löschen (mit Bestätigungsdialog)', 'Delete this file (you will be asked to confirm deletion)': 'Diese Datei löschen (mit Bestätigungsdialog)',
'Delete:': 'Löschen:', 'Delete:': 'Löschen:',
'deleted after first hit': 'deleted after first hit', 'deleted after first hit': 'nach dem ersten Treffer gelöscht',
'Demo': 'Demo', 'Demo': 'Demo',
'Deploy': 'Installieren', 'Deploy': 'Installieren',
'Deploy on Google App Engine': 'Auf Google App Engine installieren', 'Deploy on Google App Engine': 'Auf Google App Engine installieren',
'Deploy to OpenShift': 'Auf OpenShift installieren', 'Deploy to OpenShift': 'Auf OpenShift installieren',
'Deploy to pythonanywhere': 'Deploy to pythonanywhere', 'Deploy to pythonanywhere': 'Bereitstellen für pythonanywhere',
'Deploy to PythonAnywhere': 'Deploy to PythonAnywhere', 'Deploy to PythonAnywhere': 'Bereitstellen für PythonAnywhere',
'Deployment form': 'Deployment form', 'Deployment form': 'Deployment form',
'Deployment Interface': 'Deployment Interface', 'Deployment Interface': 'Deployment Interface',
'Description': 'Beschreibung', 'Description': 'Beschreibung',
'Description:': 'Description:', 'Description:': 'Beschreibung:',
'design': 'design', 'design': 'design',
'DESIGN': 'DESIGN', 'DESIGN': 'DESIGN',
'Design for': 'Design für', 'Design for': 'Design für',
'Detailed traceback description': 'Detaillierte traceback Beschreibung', 'Detailed traceback description': 'Detaillierte traceback Beschreibung',
'details': 'details', 'details': 'Details',
'direction: ltr': 'direction: ltr', 'direction: ltr': 'Richtung: ltr',
'directory not found': 'directory not found', 'directory not found': 'Verzeichnis nicht gefunden',
'Disable': 'Deaktivieren', 'Disable': 'Deaktivieren',
'Disabled': 'Disabled', 'Disabled': 'Deaktiviert',
'disabled in demo mode': 'disabled in demo mode', 'disabled in demo mode': 'Im Demo Modus deaktiviert',
'disabled in GAE mode': 'disabled in GAE mode', 'disabled in GAE mode': 'Im GAE Modus deaktiviert',
'disabled in multi user mode': 'disabled in multi user mode', 'disabled in multi user mode': 'Im Multi-User-Modus deaktiviert',
'DISK': 'DISK', 'DISK': 'DISK',
'Disk Cache Keys': 'Disk Cache Keys', 'Disk Cache Keys': 'Disk Cache Keys',
'Disk Cleared': 'Disk Cleared', 'Disk Cleared': 'Disk geleert',
'Display line numbers': 'Display line numbers', 'Display line numbers': 'Zeilennummern anzeigen',
'DO NOT use the "Pack compiled" feature.': 'DO NOT use the "Pack compiled" feature.', 'DO NOT use the "Pack compiled" feature.': 'Verwenden Sie NICHT die Funktion "Pack kompiliert".',
'docs': 'docs', 'docs': 'docs',
'Docs': 'Docs', 'Docs': 'Docs',
'documentation': 'Dokumentation', 'documentation': 'Dokumentation',
'done!': 'fertig!', 'done!': 'fertig!',
'Downgrade': 'Downgrade', 'Downgrade': 'Downgrade',
'Download .w2p': 'Download .w2p', 'Download .w2p': 'Download .w2p',
'Download as .exe': 'Download as .exe', 'Download as .exe': 'Downloade als .exe',
'download layouts': 'Layouts herunterladen', 'download layouts': 'Layouts herunterladen',
'Download layouts from repository': 'Download layouts from repository', 'Download layouts from repository': 'Layouts aus dem Repository herunterladen',
'download plugins': 'Plugins herunterladen', 'download plugins': 'Plugins herunterladen',
'Download plugins from repository': 'Download plugins from repository', 'Download plugins from repository': 'Plugins aus dem Repository herunterladen',
'E-mail': 'E-mail', 'E-mail': 'E-mail',
'EDIT': 'BEARBEITEN', 'EDIT': 'BEARBEITEN',
'Edit': 'Bearbeiten', 'Edit': 'Bearbeiten',
'edit all': 'edit all', 'edit all': 'Alles bearbeiten',
'Edit application': 'Bearbeite Anwendung', 'Edit application': 'Bearbeite Anwendung',
'edit controller': 'Bearbeite Controller', 'edit controller': 'Bearbeite Controller',
'edit controller:': 'bearbeite Controller:', 'edit controller:': 'bearbeite Controller:',
@@ -228,12 +228,12 @@
'Editing file': 'Bearbeite Datei', 'Editing file': 'Bearbeite Datei',
'Editing file "%s"': 'Bearbeite Datei "%s"', 'Editing file "%s"': 'Bearbeite Datei "%s"',
'Editing Language file': 'Sprachdatei bearbeiten', 'Editing Language file': 'Sprachdatei bearbeiten',
'Editing Plural Forms File': 'Editing Plural Forms File', 'Editing Plural Forms File': 'Bearbeiten von Plural formen Datei',
'Editor': 'Editor', 'Editor': 'Editor',
'Email Address': 'Email Address', 'Email Address': 'Email Address',
'Enable': 'Aktivieren', 'Enable': 'Aktivieren',
'Enable Close-Tag': 'Enable Close-Tag', 'Enable Close-Tag': 'Aktiviere Close-Tag',
'Enable Code Folding': 'Enable Code Folding', 'Enable Code Folding': 'Aktiviere Code Folding',
'Enterprise Web Framework': 'Enterprise Web Framework', 'Enterprise Web Framework': 'Enterprise Web Framework',
'Error': 'Fehler', 'Error': 'Fehler',
'Error logs for "%(app)s"': 'Fehlerprotokoll für "%(app)s"', 'Error logs for "%(app)s"': 'Fehlerprotokoll für "%(app)s"',
@@ -241,8 +241,8 @@
'Error ticket': 'Error ticket', 'Error ticket': 'Error ticket',
'Errors': 'Fehlermeldungen', 'Errors': 'Fehlermeldungen',
'escape': 'escape', 'escape': 'escape',
'Exception %(extype)s: %(exvalue)s': 'Exception %(extype)s: %(exvalue)s', 'Exception %(extype)s: %(exvalue)s': 'Ausnahme %(extype)s: %(exvalue)s',
'Exception %s': 'Exception %s', 'Exception %s': 'Ausnahme %s',
'Exception instance attributes': 'Attribute der Ausnahmeinstanz', 'Exception instance attributes': 'Attribute der Ausnahmeinstanz',
'Exit Fullscreen': 'Vollbild beenden', 'Exit Fullscreen': 'Vollbild beenden',
'Expand Abbreviation': 'Kürzel erweitern', 'Expand Abbreviation': 'Kürzel erweitern',
@@ -254,7 +254,7 @@
'extends': 'erweitert', 'extends': 'erweitert',
'failed to compile file because:': 'Datei konnte nicht kompiliert werden, da:', 'failed to compile file because:': 'Datei konnte nicht kompiliert werden, da:',
'failed to reload module': 'neu laden des Moduls fehlgeschlagen', 'failed to reload module': 'neu laden des Moduls fehlgeschlagen',
'failed to reload module because:': 'failed to reload module because:', 'failed to reload module because:': 'Modul konnte nicht geladen werden, weil:',
'File': 'Datei', 'File': 'Datei',
'file "%(filename)s" created': 'Datei "%(filename)s" erstellt', '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',
@@ -263,166 +263,166 @@
'file "%s" of %s restored': 'Datei "%s" von %s wiederhergestellt', '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 does not exist': 'Datei existiert nicht',
'file not found': 'file not found', 'file not found': 'Datei nicht gefunden',
'file saved on %(time)s': 'Datei gespeichert am %(time)s', 'file saved on %(time)s': 'Datei gespeichert am %(time)s',
'file saved on %s': 'Datei gespeichert auf %s', 'file saved on %s': 'Datei gespeichert auf %s',
'filename': 'filename', 'filename': 'dateiname',
'Filename': 'Filename', 'Filename': 'Dateiname',
'Files added': 'Files added', 'Files added': 'Dateien hinzugefügt',
'filter': 'Filter', 'filter': 'Filter',
'Find Next': 'Nächster', 'Find Next': 'Nächster',
'Find Previous': 'Vorheriger', 'Find Previous': 'Vorheriger',
'First name': 'Vorname', 'First name': 'Vorname',
'Form has errors': 'Form has errors', 'Form has errors': 'Das Formular hat Fehler',
'Frames': 'Frames', 'Frames': 'Frames',
'Functions with no doctests will result in [passed] tests.': 'Funktionen ohne doctests erzeugen [passed] in Tests', 'Functions with no doctests will result in [passed] tests.': 'Funktionen ohne doctests erzeugen [passed] in Tests',
'GAE Email': 'GAE Email', 'GAE Email': 'GAE Email',
'GAE Output': 'GAE Output', 'GAE Output': 'GAE Output',
'GAE Password': 'GAE Password', 'GAE Password': 'GAE Passwort',
'Generate': 'Generate', 'Generate': 'Generieren',
'Get from URL:': 'Get from URL:', 'Get from URL:': 'Von URL abrufen:',
'Git Pull': 'Git Pull', 'Git Pull': 'Git Pull',
'Git Push': 'Git Push', 'Git Push': 'Git Push',
'Globals##debug': 'Globals##debug', 'Globals##debug': 'Globales##debug',
'Go to Matching Pair': 'Gehe zum übereinstimmenden Paar', 'Go to Matching Pair': 'Gehe zum übereinstimmenden Paar',
'go!': 'go!', 'go!': 'go!',
'Google App Engine Deployment Interface': 'Google App Engine Deployment Interface', 'Google App Engine Deployment Interface': 'Google App Engine Deployment Interface',
'Google Application Id': 'Google Application Id', 'Google Application Id': 'Google-Anwendungs-ID',
'Goto': 'Goto', 'Goto': 'Gehe zu',
'graph model': 'graph model', 'graph model': 'graph model',
'Graph Model': 'Graph Model', 'Graph Model': 'Graph Model',
'Group ID': 'Gruppen ID', 'Group ID': 'Gruppen ID',
'Hello World': 'Hallo Welt', 'Hello World': 'Hallo Welt',
'Help': 'Hilfe', 'Help': 'Hilfe',
'here': 'here', 'here': 'Hier',
'Hide/Show Translated strings': 'Zeige/Verstecke übersetzte Strings', 'Hide/Show Translated strings': 'Zeige/Verstecke übersetzte Strings',
'Highlight current line': 'Highlight current line', 'Highlight current line': 'Aktuelle Zeile markieren',
'Hits': 'Hits', 'Hits': 'Treffer',
'Home': 'Startseite', 'Home': 'Startseite',
'honored only if the expression evaluates to true': 'honored only if the expression evaluates to true', 'honored only if the expression evaluates to true': 'Wird nur berücksichtigt, wenn der Ausdruck als true ausgewertet wird',
'htmledit': 'htmledit', 'htmledit': 'htmledit',
'If start the downgrade, be patient, it may take a while to rollback': 'If start the downgrade, be patient, it may take a while to rollback', 'If start the downgrade, be patient, it may take a while to rollback': 'Wenn Sie das Downgrade starten, haben Sie etwas Geduld. Das Rollback kann einige Zeit dauern',
'If start the upgrade, be patient, it may take a while to download': 'If start the upgrade, be patient, it may take a while to download', 'If start the upgrade, be patient, it may take a while to download': 'Wenn Sie das Upgrade starten, haben Sie etwas Geduld. Das Herunterladen kann einige Zeit dauern',
'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.\n\t\tA green title indicates that all tests (if defined) passed. In this case test results are not shown.': '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.\n\t\tA green title indicates that all tests (if defined) passed. In this case test results are not shown.', '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.\n\t\tA green title indicates that all tests (if defined) passed. In this case test results are not shown.': '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.\r\n\t\tA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
'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': 'Wenn Sie mit "Ja" antworten, seien Sie bitte geduldig. Der Download könnte eine Weile dauern.', 'If you answer "yes", be patient, it may take a while to download': 'Wenn Sie mit "Ja" antworten, seien Sie bitte geduldig. Der Download könnte eine Weile dauern.',
'If you answer yes, be patient, it may take a while to download': 'Wenn Sie mit Ja antworten, seien Sie bitte geduldig. Der Download könnte eine Weile dauern.', 'If you answer yes, be patient, it may take a while to download': 'Wenn Sie mit Ja antworten, seien Sie bitte geduldig. Der Download könnte eine Weile dauern.',
'if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.': 'if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.', 'if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.': 'Wenn Ihre Anwendung eine andere Datenbank als sqlite verwendet, müssen Sie ihre DAL in pythonanywhere konfigurieren.',
'import': 'import', 'import': 'Import',
'Import/Export': 'Importieren/Exportieren', 'Import/Export': 'Importieren/Exportieren',
'In development, use the default Rocket webserver that is currently supported by this debugger.': 'In development, use the default Rocket webserver that is currently supported by this debugger.', 'In development, use the default Rocket webserver that is currently supported by this debugger.': 'Verwenden Sie in der Entwicklung den Standard-Rocket-Webserver, der derzeit von diesem Debugger unterstützt wird.',
'includes': 'Einfügen', 'includes': 'Einfügen',
'Indent with tabs': 'Indent with tabs', 'Indent with tabs': 'Einrücken mit Tabulatoren',
'Index': 'Index', 'Index': 'Index',
'index': 'index', 'index': 'index',
'insert new': 'neu Einfügen', 'insert new': 'neu Einfügen',
'insert new %s': 'neu Einfügen %s', 'insert new %s': 'neu Einfügen %s',
'inspect attributes': 'Attribute inspizieren', 'inspect attributes': 'Attribute inspizieren',
'Install': 'Installieren', 'Install': 'Installieren',
'Installation of %(plugin)s for %(app)s': 'Installation of %(plugin)s for %(app)s', 'Installation of %(plugin)s for %(app)s': 'Installation von %(Plugin)s für %(App)s',
'Installed applications': 'Installierte Anwendungen', 'Installed applications': 'Installierte Anwendungen',
'Interaction at %s line %s': 'Interaction at %s line %s', 'Interaction at %s line %s': 'Interaktion bei %s Zeile %s',
'Interactive console': 'Interactive console', 'Interactive console': 'Interaktive Konsole',
'internal error': 'Interner Fehler', 'internal error': 'Interner Fehler',
'internal error: %s': 'internal error: %s', 'internal error: %s': 'interner Error: %s',
'Internal State': 'Interner Status', 'Internal State': 'Interner Status',
'Invalid action': 'Ungültige Aktion', 'Invalid action': 'Ungültige Aktion',
'Invalid application name': 'Invalid application name', 'Invalid application name': 'Ungültiger Anwendungsname',
'invalid circular reference': 'invalid circular reference', 'invalid circular reference': 'ungültiger Zirkelverweis',
'Invalid email': 'Ungültige Email', 'Invalid email': 'Ungültige Email',
'Invalid git repository specified.': 'Invalid git repository specified.', 'Invalid git repository specified.': 'Ungültiges git-Repository angegeben.',
'invalid password': 'Ungültiges Passwort', 'invalid password': 'Ungültiges Passwort',
'invalid password.': 'invalid password.', 'invalid password.': 'Ungültiges Passwort.',
'Invalid Query': 'Ungültige Abfrage', 'Invalid Query': 'Ungültige Abfrage',
'invalid request': 'Ungültige Anfrage', 'invalid request': 'Ungültige Anfrage',
'Invalid request': 'Invalid request', 'Invalid request': 'Ungültige Anfrage',
'invalid table names (auth_* tables already defined)': 'invalid table names (auth_* tables already defined)', 'invalid table names (auth_* tables already defined)': 'Ungültige Tabellennamen (auth_* Tabellen bereits definiert)',
'invalid ticket': 'Ungültiges Ticket', 'invalid ticket': 'Ungültiges Ticket',
'Key': 'Key', 'Key': 'Key',
'Key bindings': 'Tastenbelegungen', 'Key bindings': 'Tastenbelegungen',
'Key bindings for ZenCoding Plugin': 'Key bindings for ZenCoding Plugin', 'Key bindings for ZenCoding Plugin': 'Tastenbelegungen für das ZenCoding Plugin',
'Key bindings for ZenConding Plugin': 'Tastenbelegungen für das ZenConding Plugin', 'Key bindings for ZenConding Plugin': 'Tastenbelegungen für das ZenConding Plugin',
'Keyboard shortcuts': 'Tastenkombination', 'Keyboard shortcuts': 'Tastenkombination',
'kill process': 'kill process', 'kill process': 'Prozess zu töten',
'language file "%(filename)s" created/updated': 'Sprachdatei "%(filename)s" erstellt/aktualisiert', 'language file "%(filename)s" created/updated': 'Sprachdatei "%(filename)s" erstellt/aktualisiert',
'Language files (static strings) updated': 'Sprachdatei (statisch Strings) aktualisiert', 'Language files (static strings) updated': 'Sprachdatei (statisch Strings) aktualisiert',
'languages': 'Sprachen', 'languages': 'Sprachen',
'Languages': 'Sprachen', 'Languages': 'Sprachen',
'languages updated': 'Sprachen aktualisiert', 'languages updated': 'Sprachen aktualisiert',
'Last name': 'Nachname', 'Last name': 'Nachname',
'Last Revision': 'Last Revision', 'Last Revision': 'Letzete Revision',
'Last saved on:': 'Zuletzt gespeichert am:', 'Last saved on:': 'Zuletzt gespeichert am:',
'Layout': 'Layout', 'Layout': 'Layout',
'License for': 'Lizenz für', 'License for': 'Lizenz für',
'License:': 'License:', 'License:': 'Lizenz:',
'Line Nr': 'Line Nr', 'Line Nr': 'Zeilen Nr',
'Line number': 'Line number', 'Line number': 'Zeilennummer',
'lists by exception': 'lists by exception', 'lists by exception': 'Listen nach Ausnahme',
'lists by ticket': 'nach Ticket aufgelistet', 'lists by ticket': 'nach Ticket aufgelistet',
'Loading...': 'Loading...', 'Loading...': 'Laden...',
'loading...': 'lade...', 'loading...': 'lade...',
'Local Apps': 'Local Apps', 'Local Apps': 'Lokale Apps',
'locals': 'locals', 'locals': 'lokales',
'Locals##debug': 'Locals##debug', 'Locals##debug': 'Lokales##debug',
'located in the file': 'befindet sich in der Datei', 'located in the file': 'befindet sich in der Datei',
'login': 'anmelden', 'login': 'anmelden',
'Login': 'Anmelden', 'Login': 'Anmelden',
'Login successful': 'Login successful', 'Login successful': 'Login erfolgreich',
'Login to the Administrative Interface': 'An das Administrations-Interface anmelden', 'Login to the Administrative Interface': 'An das Administrations-Interface anmelden',
'Login/Register': 'Login/Register', 'Login/Register': 'Login/Registrieren',
'Logout': 'Abmelden', 'Logout': 'Abmelden',
'lost password': 'lost password', 'lost password': 'Passwort vergessen',
'Lost Password': 'Passwort vergessen', 'Lost Password': 'Passwort vergessen',
'lost password?': 'Passwort vergessen?', 'lost password?': 'Passwort vergessen?',
'Main Menu': 'Hauptmenü', 'Main Menu': 'Hauptmenü',
'Manage': 'Verwalten', 'Manage': 'Verwalten',
'Manage %(action)s': 'Manage %(action)s', 'Manage %(action)s': '%(action)s verwalten',
'Manage Access Control': 'Manage Access Control', 'Manage Access Control': 'Zugriffskontrolle verwalten',
'Manage Admin Users/Students': 'Manage Admin Users/Students', 'Manage Admin Users/Students': 'Admin-User/Studenten verwalten',
'Manage Cache': 'Manage Cache', 'Manage Cache': 'Cache verwalten',
'Manage Students': 'Manage Students', 'Manage Students': 'Studenten verwalten',
'Match Pair': 'Paare finden', 'Match Pair': 'Paare finden',
'Memberships': 'Memberships', 'Memberships': 'Mitgliedschaften',
'Menu Model': 'Menü Modell', 'Menu Model': 'Menü Modell',
'merge': 'verbinden', 'merge': 'verbinden',
'Merge Lines': 'Zeilen zusammenfügen', 'Merge Lines': 'Zeilen zusammenfügen',
'Models': 'Modelle', 'Models': 'Modelle',
'models': 'Modelle', 'models': 'Modelle',
'Modified On': 'Modified On', 'Modified On': 'Geändert am',
'Modules': 'Module', 'Modules': 'Module',
'modules': 'Module', 'modules': 'Module',
'Multi User Mode': 'Multi User Mode', 'Multi User Mode': 'Multi-User-Modus',
'Name': 'Name', 'Name': 'Name',
'new application "%s" created': 'neue Anwendung "%s" erzeugt', 'new application "%s" created': 'neue Anwendung "%s" erzeugt',
'new application "%s" imported': 'new application "%s" imported', 'new application "%s" imported': 'Neue Anwendung "%s" importiert',
'New Application Wizard': 'New Application Wizard', 'New Application Wizard': 'Neuer Anwendungs Assistent',
'New application wizard': 'Neue Anwendung per Assistent', 'New application wizard': 'Neue Anwendung per Assistent',
'new plugin installed': 'Neues Plugin wurde installiert', 'new plugin installed': 'Neues Plugin wurde installiert',
'New plugin installed: %s': 'New plugin installed: %s', 'New plugin installed: %s': 'Neues Plugin installiert: %s',
'New Record': 'Neuer Datensatz', 'New Record': 'Neuer Datensatz',
'new record inserted': 'Neuer wurde Datensatz eingefügt', 'new record inserted': 'Neuer wurde Datensatz eingefügt',
'New simple application': 'Neue einfache Anwendung', 'New simple application': 'Neue einfache Anwendung',
'next': 'next', 'next': 'Nächste',
'next %s rows': 'next %s rows', 'next %s rows': 'nächste %s Zeilen',
'next 100 rows': 'nächsten 100 Zeilen', 'next 100 rows': 'nächsten 100 Zeilen',
'Next Edit Point': 'nächster Bearbeitungsschritt', 'Next Edit Point': 'nächster Bearbeitungsschritt',
'NO': 'NEIN', 'NO': 'NEIN',
'no changes': 'no changes', 'no changes': 'keine Änderungen',
'No databases in this application': 'Keine Datenbank in dieser Anwendung', 'No databases in this application': 'Keine Datenbank in dieser Anwendung',
'No Interaction yet': 'No Interaction yet', 'No Interaction yet': 'Noch keine Interaktion',
'no match': 'no match', 'no match': 'keine Übereinstimmung',
'no package selected': 'Kein Paket ausgewählt', 'no package selected': 'Kein Paket ausgewählt',
'no permission to uninstall "%s"': 'no permission to uninstall "%s"', 'no permission to uninstall "%s"': 'Keine Berechtigung zum Deinstallieren von "%s"',
'No ticket_storage.txt found under /private folder': 'Kein ticket_storage.txt unter /private folder gefunden', 'No ticket_storage.txt found under /private folder': 'Kein ticket_storage.txt unter /private folder gefunden',
'Node:': 'Node:', 'Node:': 'Hinweis:',
'Not Authorized': 'Not Authorized', 'Not Authorized': 'Nicht berechtigt',
'Not supported': 'Not supported', 'Not supported': 'Nicht unterstützt',
'Note: If you receive an error with github status code of 128, ensure the system and account you are deploying from has a cooresponding ssh key configured in the openshift account.': 'Note: If you receive an error with github status code of 128, ensure the system and account you are deploying from has a cooresponding ssh key configured in the openshift account.', 'Note: If you receive an error with github status code of 128, ensure the system and account you are deploying from has a cooresponding ssh key configured in the openshift account.': 'Hinweis: Wenn Sie einen Fehler mit dem Github-Statuscode 128 erhalten, stellen Sie sicher, dass das System und der Account, von dem Sie das Deployment durchführen, über einen entsprechenden ssh-Schlüssel im Account "openshift" verfügen.',
"On production, you'll have to configure your webserver to use one process and multiple threads to use this debugger.": "On production, you'll have to configure your webserver to use one process and multiple threads to use this debugger.", "On production, you'll have to configure your webserver to use one process and multiple threads to use this debugger.": 'In der Produktion müssen Sie Ihren Webserver so konfigurieren, dass er einen Prozess und mehrere Threads verwendet, um diesen Debugger zu verwenden.',
'online designer': 'Online Designer', 'online designer': 'Online Designer',
'Open new app in new window': 'Open new app in new window', 'Open new app in new window': 'Neue App in neuem Fenster öffnen',
'OpenShift Deployment Interface': 'OpenShift Deployment Interface', 'OpenShift Deployment Interface': 'OpenShift Deployment Interface',
'OpenShift Output': 'OpenShift Output', 'OpenShift Output': 'OpenShift Ausgabe',
'or alternatively': 'oder Alternativ', 'or alternatively': 'oder Alternativ',
'Or Get from URL:': 'oder von folgender URL herunterladen:', 'Or Get from URL:': 'oder von folgender URL herunterladen:',
'or import from csv file': 'oder importieren von cvs Datei', 'or import from csv file': 'oder importieren von cvs Datei',
@@ -430,52 +430,52 @@
'or provide application url:': 'oder geben Sie eine Anwendungs-URL an:', 'or provide application url:': 'oder geben Sie eine Anwendungs-URL an:',
'Origin': 'Herkunft', 'Origin': 'Herkunft',
'Original/Translation': 'Original/übersetzung', 'Original/Translation': 'Original/übersetzung',
'Overview': 'Overview', 'Overview': 'Übersicht',
'Overwrite installed app': 'Installierte Anwendungen überschreiben', 'Overwrite installed app': 'Installierte Anwendungen überschreiben',
'Pack all': 'Verpacke alles', 'Pack all': 'Verpacke alles',
'Pack compiled': 'Verpacke kompiliert', 'Pack compiled': 'Verpacke kompiliert',
'Pack custom': 'Verpacke individuell', 'Pack custom': 'Verpacke individuell',
'pack plugin': 'Plugin verpacken', 'pack plugin': 'Plugin verpacken',
'Password': 'Passwort', 'Password': 'Passwort',
'password changed': 'password changed', 'password changed': 'passwort geändert',
'Past revisions': 'Past revisions', 'Past revisions': 'Vergangene Revisionen',
'Path to appcfg.py': 'Path to appcfg.py', 'Path to appcfg.py': 'Pfad zu appcfg.py',
'Path to local openshift repo root.': 'Path to local openshift repo root.', 'Path to local openshift repo root.': 'Pfad zum lokalen OpenShift-Repo-root.',
'Peeking at file': 'Dateiansicht', 'Peeking at file': 'Dateiansicht',
'Permission': 'Permission', 'Permission': 'Berechtigung',
'Permissions': 'Permissions', 'Permissions': 'Berechtigungen',
'Please': 'Please', 'Please': 'Bitte',
'please wait!': 'Bitte warten!', 'please wait!': 'Bitte warten!',
'Please wait, giving pythonanywhere a moment...': 'Please wait, giving pythonanywhere a moment...', 'Please wait, giving pythonanywhere a moment...': 'Bitte warten Sie, und geben Sie pythonanywhere einen Moment Zeit...',
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" deleted', 'plugin "%(plugin)s" deleted': 'Plugin "%(plugin) s" wurde gelöscht',
'Plugin "%s" in application': 'Plugin "%s" in Anwendung', 'Plugin "%s" in application': 'Plugin "%s" in Anwendung',
'plugin not specified': 'plugin not specified', 'plugin not specified': 'Plugin nicht angegeben',
'Plugin page': 'Plugin page', 'Plugin page': 'Plugin seite',
'plugins': 'plugins', 'plugins': 'plugins',
'Plugins': 'Plugins', 'Plugins': 'Plugins',
'Plural Form #%s': 'Plural Form #%s', 'Plural Form #%s': 'Plural Formen #%s',
'Plural-Forms:': 'Plural-Forms:', 'Plural-Forms:': 'Plural Formen:',
'Powered by': 'Unterstützt von', 'Powered by': 'Unterstützt von',
'Preferences saved correctly': 'Preferences saved correctly', 'Preferences saved correctly': 'Einstellungen korrekt gespeichert',
'Preferences saved on session only': 'Preferences saved on session only', 'Preferences saved on session only': 'Einstellungen nur in der Sitzung gespeichert',
'previous %s rows': 'previous %s rows', 'previous %s rows': 'vorherige %s Zeilen',
'previous 100 rows': 'vorherige 100 zeilen', 'previous 100 rows': 'vorherige 100 zeilen',
'Previous Edit Point': 'vorheriger Bearbeitungsschritt', 'Previous Edit Point': 'vorheriger Bearbeitungsschritt',
'Private files': 'Private files', 'Private files': 'Private Dateien',
'private files': 'private files', 'private files': 'private Dateien',
'Project Progress': 'Projekt Fortschritt', 'Project Progress': 'Projekt Fortschritt',
'Pull': 'Pull', 'Pull': 'Pull',
'Pull failed, certain files could not be checked out. Check logs for details.': 'Pull failed, certain files could not be checked out. Check logs for details.', 'Pull failed, certain files could not be checked out. Check logs for details.': 'Pull fehlgeschlagen, bestimmte Dateien konnten nicht ausgecheckt werden. Überprüfen Sie die Protokolle für Details.',
'Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.': 'Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.', 'Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.': 'Pull ist nicht möglich, weil Sie nicht gespeicherte Dateien haben. Reparieren Sie sie im Arbeitsbaum und versuchen Sie es erneut.',
'Push': 'Push', 'Push': 'Push',
'Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.': 'Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.', 'Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.': 'Push fehlgeschlagen, es sind nicht fusionierte Einträge im Cache. Beheben Sie fusionierungsprobleme manuell und versuchen Sie es erneut.',
'pygraphviz library not found': 'pygraphviz library not found', 'pygraphviz library not found': 'pygraphviz Bibliothek nicht gefunden',
'PythonAnywhere Apps': 'PythonAnywhere Apps', 'PythonAnywhere Apps': 'PythonAnywhere Apps',
'PythonAnywhere Password': 'PythonAnywhere Password', 'PythonAnywhere Password': 'PythonAnywhere Passwort',
'Query:': 'Abfrage:', 'Query:': 'Abfrage:',
'RAM': 'RAM', 'RAM': 'RAM',
'RAM Cache Keys': 'RAM Cache Keys', 'RAM Cache Keys': 'RAM Cache Keys',
'Ram Cleared': 'Ram Cleared', 'Ram Cleared': 'Ram geleert',
'Rapid Search': 'Schnelle Suche', 'Rapid Search': 'Schnelle Suche',
'Record': 'Record', 'Record': 'Record',
'record': 'Datensatz', 'record': 'Datensatz',
@@ -483,91 +483,91 @@
'record id': 'Datensatz id', 'record id': 'Datensatz id',
'Record ID': 'Datensatz ID', 'Record ID': 'Datensatz ID',
'Record id': 'Record id', 'Record id': 'Record id',
'refresh': 'refresh', 'refresh': 'Aktualisieren',
'Register': 'Registrieren', 'Register': 'Registrieren',
'register': 'Registrierung', 'register': 'Registrierung',
'Registration key': 'Registrierungsschlüssel', 'Registration key': 'Registrierungsschlüssel',
'reload': 'Neu laden', 'reload': 'Neu laden',
'Reload routes': 'Routen neu laden', 'Reload routes': 'Routen neu laden',
'Remove compiled': 'Bytecode löschen', 'Remove compiled': 'Bytecode löschen',
'Removed Breakpoint on %s at line %s': 'Removed Breakpoint on %s at line %s', 'Removed Breakpoint on %s at line %s': 'Breakpoint entfernt an %s auf Zeile %s',
'Replace': 'Ersetzen', 'Replace': 'Ersetzen',
'Replace All': 'Alle Ersetzen', 'Replace All': 'Alle Ersetzen',
'Repository (%s)': 'Repository (%s)', 'Repository (%s)': 'Speiche-Archiv (%s)',
'request': 'Anfrage', 'request': 'Anfrage',
'requires distutils, but not installed': 'requires distutils, but not installed', 'requires distutils, but not installed': 'erfordert distutils, aber nicht installiert',
'requires python-git, but not installed': 'requires python-git, but not installed', 'requires python-git, but not installed': 'rfordert Python-Git, aber nicht installiert',
'Reset Password key': 'Passwortschlüssel zurücksetzen', 'Reset Password key': 'Passwortschlüssel zurücksetzen',
'Resolve Conflict file': 'bereinige Konflikt-Datei', 'Resolve Conflict file': 'bereinige Konflikt-Datei',
'response': 'Antwort', 'response': 'Antwort',
'restart': 'restart', 'restart': 'Neustart',
'restore': 'wiederherstellen', 'restore': 'wiederherstellen',
'return': 'return', 'return': 'Zurück',
'Revert': 'Revert', 'Revert': 'Zurückkehren',
'revert': 'zurückkehren', 'revert': 'zurückkehren',
'reverted to revision %s': 'reverted to revision %s', 'reverted to revision %s': 'Zurück zur revision %s',
'Revision %s': 'Revision %s', 'Revision %s': 'Revision %s',
'Revision:': 'Revision:', 'Revision:': 'Revision:',
'Role': 'Rolle', 'Role': 'Rolle',
'Roles': 'Roles', 'Roles': 'Rollen',
'Rows in table': 'Zeilen in Tabelle', 'Rows in table': 'Zeilen in Tabelle',
'Rows in Table': 'Rows in Table', 'Rows in Table': 'Zeilen in der Tabelle',
'Rows selected': 'Zeilen ausgewählt', 'Rows selected': 'Zeilen ausgewählt',
'rules are not defined': 'Regeln sind nicht definiert', 'rules are not defined': 'Regeln sind nicht definiert',
'Run tests': 'Run tests', 'Run tests': 'Tests ausführen',
'Run tests in this file': 'Run tests in this file', 'Run tests in this file': 'Führen Sie Tests in dieser Datei aus',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Tests in dieser Datei ausführen (um alle Dateien auszuführen, kann auch der Button 'test' genutzt werden)", "Run tests in this file (to run all files, you may also use the button labelled 'test')": "Tests in dieser Datei ausführen (um alle Dateien auszuführen, kann auch der Button 'test' genutzt werden)",
'Running on %s': 'läuft auf %s', 'Running on %s': 'läuft auf %s',
'Save': 'Speichern', 'Save': 'Speichern',
'save': 'Speichern', 'save': 'Speichern',
'Save file:': 'Speichere Datei:', 'Save file:': 'Speichere Datei:',
'Save file: %s': 'Speichere Datei: %s', 'Save file: %s': 'Speichere Datei: %s',
'Save model as...': 'Save model as...', 'Save model as...': 'Modell speichern als...',
'Save via Ajax': 'via Ajax speichern', 'Save via Ajax': 'via Ajax speichern',
'Saved file hash:': 'Gespeicherter Datei-Hash:', 'Saved file hash:': 'Gespeicherter Datei-Hash:',
'Screenshot %s': 'Screenshot %s', 'Screenshot %s': 'Bildschirmfoto %s',
'Search': 'Search', 'Search': 'Suche',
'Select Files to Package': 'Dateien zum Paketieren wählen', 'Select Files to Package': 'Dateien zum Paketieren wählen',
'selected': 'ausgewählt(e)', 'selected': 'ausgewählt(e)',
'session': 'Sitzung', 'session': 'Sitzung',
'session expired': 'Sitzung abgelaufen', 'session expired': 'Sitzung abgelaufen',
'Session saved correctly': 'Session saved correctly', 'Session saved correctly': 'Sitzung korrekt gespeichert',
'Session saved on session only': 'Session saved on session only', 'Session saved on session only': 'Sitzung gespeichert auf Nur Sitzung',
'Set Breakpoint on %s at line %s: %s': 'Set Breakpoint on %s at line %s: %s', 'Set Breakpoint on %s at line %s: %s': 'Setze Breakpoint auf %s an Linie %s: %s',
'shell': 'Shell', 'shell': 'Shell',
'Showing %s to %s of %s %s found': 'Showing %s to %s of %s %s found', 'Showing %s to %s of %s %s found': 'Showing %s to %s of %s %s found',
'Singular Form': 'Singular Form', 'Singular Form': 'Einzahl',
'Site': 'Seite', 'Site': 'Seite',
'Size of cache:': 'Size of cache:', 'Size of cache:': 'Größe des Caches:',
'skip to generate': 'skip to generate', 'skip to generate': 'überspringen, um zu generieren',
'some files could not be removed': 'einige Dateien konnten nicht gelöscht werden', 'some files could not be removed': 'einige Dateien konnten nicht gelöscht werden',
'Something went wrong please wait a few minutes before retrying': 'Something went wrong please wait a few minutes before retrying', 'Something went wrong please wait a few minutes before retrying': 'Es ist ein Fehler aufgetreten. Bitte warten Sie einige Minuten, bevor Sie es erneut versuchen',
'Sorry, could not find mercurial installed': 'Sorry, could not find mercurial installed', 'Sorry, could not find mercurial installed': 'Leider konnte mercurial nicht installiert werden',
'source : db': 'source : db', 'source : db': 'Quelle : db',
'source : filesystem': 'Quelle : Dateisystem', 'source : filesystem': 'Quelle : Dateisystem',
'Start a new app': 'Start a new app', 'Start a new app': 'Starten Sie eine neue App',
'Start searching': 'Suche beginnen', 'Start searching': 'Suche beginnen',
'Start wizard': 'Assistent starten', 'Start wizard': 'Assistent starten',
'state': 'Status', 'state': 'Status',
'Static': 'Statisch', 'Static': 'Statisch',
'static': 'statische Dateien', 'static': 'statische Dateien',
'Static files': 'statische Dateien', 'Static files': 'statische Dateien',
'Statistics': 'Statistics', 'Statistics': 'Statistiken',
'Step': 'Step', 'Step': 'Schritt',
'step': 'step', 'step': 'schritt',
'stop': 'stop', 'stop': 'Halt',
'Stylesheet': 'Stylesheet', 'Stylesheet': 'Stylesheet',
'submit': 'Absenden', 'submit': 'Absenden',
'Submit': 'Submit', 'Submit': 'Einreichen',
'successful': 'successful', 'successful': 'erfolgreich',
'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?',
'switch to : db': 'wechsel zu : db', 'switch to : db': 'wechsel zu : db',
'switch to : filesystem': 'switch to : filesystem', 'switch to : filesystem': 'wechseln zu : filesystem',
'Tab width (# characters)': 'Tab width (# characters)', 'Tab width (# characters)': 'Tabulatorbreite (# characters)',
'table': 'Tabelle', 'table': 'Tabelle',
'Table': 'Table', 'Table': 'Tabelle',
'Table name': 'Tabellen Name', 'Table name': 'Tabellen Name',
'Temporary': 'Temporary', 'Temporary': 'Temporär',
'test': 'Test', 'test': 'Test',
'test_def': 'test_def', 'test_def': 'test_def',
'test_for': 'test_for', 'test_for': 'test_for',
@@ -576,8 +576,8 @@
'Testing application': 'Teste die Anwendung', 'Testing application': 'Teste die Anwendung',
'Testing controller': 'Teste Controller', '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 "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 app exists, was created by wizard, continue to overwrite!': 'The app exists, was created by wizard, continue to overwrite!', 'The app exists, was created by wizard, continue to overwrite!': 'Die App existiert, wurde von Wizard erstellt, weiter überschreiben!',
'The app exists, was NOT created by wizard, continue to overwrite!': 'The app exists, was NOT created by wizard, continue to overwrite!', 'The app exists, was NOT created by wizard, continue to overwrite!': 'Die App existiert, wurde NICHT vom Assistenten erstellt, weiter überschreiben!',
'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': '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': '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': 'Die Logik der Anwendung, jeder URL-Pfad wird auf eine Funktion abgebildet die der Controller zur Verfügung stellt',
'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',
@@ -585,39 +585,39 @@
'The output of the file is a dictionary that was rendered by the view': 'Die Ausgabe der Datei ist ein "dictionary" und wurde vom "view" gerendert', 'The output of the file is a dictionary that was rendered by the view': 'Die Ausgabe der Datei ist ein "dictionary" und wurde vom "view" gerendert',
'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',
'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',
'Theme': 'Theme', 'Theme': 'Thema',
'There are no controllers': 'Keine Controller vorhanden', 'There are no controllers': 'Keine Controller vorhanden',
'There are no models': 'Keine Modelle vorhanden', 'There are no models': 'Keine Modelle vorhanden',
'There are no modules': 'Keine Module vorhanden', 'There are no modules': 'Keine Module vorhanden',
'There are no plugins': 'Keine Plugins vorhanden', 'There are no plugins': 'Keine Plugins vorhanden',
'There are no private files': 'Keine privaten Dateien vorhanden', 'There are no private files': 'Keine privaten Dateien vorhanden',
'There are no static files': 'Keine statischen Dateien vorhanden', 'There are no static files': 'Keine statischen Dateien vorhanden',
'There are no translators': 'There are no translators', 'There are no translators': 'Es gibt keine Übersetzer',
'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', 'There are no views': 'Keine Views vorhanden',
'These files are not served, they are only available from within your app': 'Diese Dateien werden nicht ausgeliefert, sie sind nur innerhalb Ihrer App verfügbar', 'These files are not served, they are only available from within your app': 'Diese Dateien werden nicht ausgeliefert, sie sind nur innerhalb Ihrer App verfügbar',
'These files are served without processing, your images go here': 'Diese Dateien werden ohne Verarbeitung ausgeliefert. Beispielsweise Bilder kommen hier hin.', 'These files are served without processing, your images go here': 'Diese Dateien werden ohne Verarbeitung ausgeliefert. Beispielsweise Bilder kommen hier hin.',
'these files are served without processing, your images go here': 'Diese Dateien werden ohne Verarbeitung ausgeliefert. Beispielsweise Bilder kommen hier hin.', 'these files are served without processing, your images go here': 'Diese Dateien werden ohne Verarbeitung ausgeliefert. Beispielsweise Bilder kommen hier hin.',
"This debugger may not work properly if you don't have a threaded webserver or you're using multiple daemon processes.": "This debugger may not work properly if you don't have a threaded webserver or you're using multiple daemon processes.", "This debugger may not work properly if you don't have a threaded webserver or you're using multiple daemon processes.": 'Dieser Debugger funktioniert möglicherweise nicht ordnungsgemäß, wenn Sie keinen Thread-Webserver haben oder mehrere Daemon-Prozesse verwenden.',
'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 an experimental feature and it needs more testing. If you decide to downgrade you do it at your own risk': 'This is an experimental feature and it needs more testing. If you decide to downgrade you do it at your own risk', 'This is an experimental feature and it needs more testing. If you decide to downgrade you do it at your own risk': 'Dies ist eine experimentelle Funktion und muss noch getestet werden. Wenn Sie sich für ein Downgrade entscheiden, tun Sie dies auf eigene Gefahr',
'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk', 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'Dies ist eine experimentelle Funktion und muss noch getestet werden. Wenn Sie sich für ein Upgrade entscheiden, tun Sie dies auf eigene Gefahr',
'This is the %(filename)s template': 'Dies ist das Template %(filename)s', 'This is the %(filename)s template': 'Dies ist das Template %(filename)s',
"This page can commit your changes to an openshift app repo and push them to your cloud instance. This assumes that you've already created the application instance using the web2py skeleton and have that repo somewhere on a filesystem that this web2py instance can access. This functionality requires GitPython installed and on the python path of the runtime that web2py is operating in.": "This page can commit your changes to an openshift app repo and push them to your cloud instance. This assumes that you've already created the application instance using the web2py skeleton and have that repo somewhere on a filesystem that this web2py instance can access. This functionality requires GitPython installed and on the python path of the runtime that web2py is operating in.", "This page can commit your changes to an openshift app repo and push them to your cloud instance. This assumes that you've already created the application instance using the web2py skeleton and have that repo somewhere on a filesystem that this web2py instance can access. This functionality requires GitPython installed and on the python path of the runtime that web2py is operating in.": 'Auf dieser Seite können Sie Ihre Änderungen in ein OpenShift-App-Repo übernehmen und in Ihre Cloud-Instanz verschieben. Dies setzt voraus, dass Sie die Anwendungsinstanz bereits mit dem web2py-Skelett erstellt haben und dieses Repo auf einem Dateisystem haben, auf das diese web2py-Instanz zugreifen kann. Für diese Funktionalität muss GitPython installiert sein und sich auf dem Python-Pfad der Runtime befinden, in der web2py ausgeführt wird.',
'This page can upload your application to the Google App Engine computing cloud. Mind that you must first create indexes locally and this is done by installing the Google appserver and running the app locally with it once, or there will be errors when selecting records. Attention: deployment may take long time, depending on the network speed. Attention: it will overwrite your app.yaml. DO NOT SUBMIT TWICE.': 'This page can upload your application to the Google App Engine computing cloud. Mind that you must first create indexes locally and this is done by installing the Google appserver and running the app locally with it once, or there will be errors when selecting records. Attention: deployment may take long time, depending on the network speed. Attention: it will overwrite your app.yaml. DO NOT SUBMIT TWICE.', 'This page can upload your application to the Google App Engine computing cloud. Mind that you must first create indexes locally and this is done by installing the Google appserver and running the app locally with it once, or there will be errors when selecting records. Attention: deployment may take long time, depending on the network speed. Attention: it will overwrite your app.yaml. DO NOT SUBMIT TWICE.': 'Diese Seite kann Ihre Anwendung in die Cloud der Google App Engine Computing hochladen. Beachten Sie, dass Sie zunächst Indexe lokal erstellen müssen. Dies geschieht, indem Sie den Google Appserver installieren und die App einmalig lokal ausführen. Andernfalls treten Fehler bei der Auswahl von Datensätzen auf. Achtung: Die Bereitstellung kann abhängig von der Netzwerkgeschwindigkeit sehr lange dauern. Achtung: Ihre app.yaml wird überschrieben. SENDEN SIE NICHT ZWEIMAL EIN.',
'this page to see if a breakpoint was hit and debug interaction is required.': 'this page to see if a breakpoint was hit and debug interaction is required.', 'this page to see if a breakpoint was hit and debug interaction is required.': 'Auf dieser Seite können Sie sehen, ob ein breakpoint getroffen wurde und eine Debug-Interaktion erforderlich ist.',
'This will pull changes from the remote repo for application "%s"?': 'This will pull changes from the remote repo for application "%s"?', 'This will pull changes from the remote repo for application "%s"?': 'Dadurch werden Änderungen vom Remote-Repo für die Anwendung "% s" übernommen?',
'This will push changes to the remote repo for application "%s".': 'This will push changes to the remote repo for application "%s".', 'This will push changes to the remote repo for application "%s".': 'Dadurch werden Änderungen an das Remote-Repo für die Anwendung "% s" übertragen.',
'Ticket': 'Ticket', 'Ticket': 'Ticket',
'Ticket ID': 'Ticket ID', 'Ticket ID': 'Ticket ID',
'Ticket Missing': 'Ticket Missing', 'Ticket Missing': 'Ticket fehlt',
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)', 'Time in Cache (h:m:s)': 'Zeit im Cache (h:m:s)',
'Timestamp': 'Zeitstempel', 'Timestamp': 'Zeitstempel',
'TM': 'TM', '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]', 'To create a plugin, name a file/folder plugin_[name]': 'Um ein Plugin zu erstellen benennen Sie eine(n) Datei/Ordner plugin_[Name]',
'To emulate a breakpoint programatically, write:': 'To emulate a breakpoint programatically, write:', 'To emulate a breakpoint programatically, write:': 'Um einen breapoint programmatisch zu emulieren, schreiben Sie:',
'to use the debugger!': 'to use the debugger!', 'to use the debugger!': 'um den Debugger zuverwenden!',
'toggle breakpoint': 'Breakpoint aktivieren/deaktivieren', 'toggle breakpoint': 'Breakpoint aktivieren/deaktivieren',
'Toggle comment': ' Kommentar ein-/ausblenden', 'Toggle comment': ' Kommentar ein-/ausblenden',
'Toggle Fullscreen': 'Vollbild ein-/ausschalten', 'Toggle Fullscreen': 'Vollbild ein-/ausschalten',
@@ -628,31 +628,31 @@
'try something like': 'Versuchen Sie so etwas wie', 'try something like': 'Versuchen Sie so etwas wie',
'Try the mobile interface': 'Testen Sie das Interface für Handys', 'Try the mobile interface': 'Testen Sie das Interface für Handys',
'try view': 'Versuche view', 'try view': 'Versuche view',
'Type PDB debugger command in here and hit Return (Enter) to execute it.': 'Type PDB debugger command in here and hit Return (Enter) to execute it.', 'Type PDB debugger command in here and hit Return (Enter) to execute it.': 'Geben Sie den PDB-Debuggerbefehl hier ein und drücken Sie die Eingabetaste, um ihn auszuführen.',
'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.', 'Type some Python code in here and hit Return (Enter) to execute it.': 'Geben Sie hier einen Python-Code ein und drücken Sie die Eingabetaste, um ihn auszuführen.',
'Unable to check for upgrades': 'Überprüfen von Upgrades nicht möglich', '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 create application "%s"': 'Erzeugen von Anwendung "%s" nicht möglich',
'unable to delete file "%(filename)s"': 'Löschen von Dateien "%(filename)s" nicht möglich', 'unable to delete file "%(filename)s"': 'Löschen von Dateien "%(filename)s" nicht möglich',
'unable to delete file plugin "%(plugin)s"': 'unable to delete file plugin "%(plugin)s"', 'unable to delete file plugin "%(plugin)s"': 'Datei-Plugin "%(plugin)s" kann nicht gelöscht werden',
'Unable to determine the line number!': 'Unable to determine the line number!', 'Unable to determine the line number!': 'Die Zeilennummer kann nicht ermittelt werden!',
'Unable to download': 'Herunterladen nicht möglich', 'Unable to download': 'Herunterladen nicht möglich',
'Unable to download app': 'Herunterladen der Anwendung nicht möglich', 'Unable to download app': 'Herunterladen der Anwendung nicht möglich',
'Unable to download app because:': 'Unable to download app because:', 'Unable to download app because:': 'App kann nicht heruntergeladen werden, weil:',
'unable to download layout': 'unable to download layout', 'unable to download layout': 'Layout kann nicht heruntergeladen werden',
'unable to download plugin: %s': 'unable to download plugin: %s', 'unable to download plugin: %s': 'Folgendes Plugin konnte nicht heruntergeladen werden: %s',
'Unable to download the list of plugins': 'Unable to download the list of plugins', 'Unable to download the list of plugins': 'Die Liste der Plugins kann nicht heruntergeladen werden',
'unable to install plugin "%s"': 'unable to install plugin "%s"', 'unable to install plugin "%s"': 'Plugin "%s" kann nicht installiert werden',
'unable to parse csv file': 'Analysieren der cvs Datei 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', 'unable to uninstall "%s"': 'Deinstallieren von "%s" nicht möglich',
'unable to upgrade because "%s"': 'unable to upgrade because "%s"', 'unable to upgrade because "%s"': 'Upgrade wegen "%s" nicht möglich',
'uncheck all': 'Auswahl entfernen', 'uncheck all': 'Auswahl entfernen',
'Uninstall': 'Deinstallieren', 'Uninstall': 'Deinstallieren',
'Unsupported webserver working mode: %s': 'Unsupported webserver working mode: %s', 'Unsupported webserver working mode: %s': 'Nicht unterstützte Webserver-Arbeitsmodell: %s',
'update': 'Aktualisieren', 'update': 'Aktualisieren',
'update all languages': 'Aktualisiere alle Sprachen', 'update all languages': 'Aktualisiere alle Sprachen',
'Update:': 'Aktualisiere:', 'Update:': 'Aktualisiere:',
'Upgrade': 'Upgrade', 'Upgrade': 'Upgrade',
'upgrade now to %s': 'upgrade now to %s', 'upgrade now to %s': 'Upgrade jetzt zu %s',
'upgrade web2py now': 'web2py jetzt upgraden', 'upgrade web2py now': 'web2py jetzt upgraden',
'upload': 'upload', 'upload': 'upload',
'Upload': 'Upload', 'Upload': 'Upload',
@@ -670,7 +670,7 @@
'User ID': 'Benutzer ID', 'User ID': 'Benutzer ID',
'Username': 'Username', 'Username': 'Username',
'Users': 'Users', 'Users': 'Users',
'Using the shell may lock the database to other users of this app.': 'Using the shell may lock the database to other users of this app.', 'Using the shell may lock the database to other users of this app.': 'Durch die Verwendung des Shell kann die Datenbank für andere Benutzer dieser App gesperrt werden.',
'variables': 'Variablen', 'variables': 'Variablen',
'Version': 'Version', 'Version': 'Version',
'Version %s.%s.%s (%s) %s': 'Version %s.%s.%s (%s) %s', 'Version %s.%s.%s (%s) %s': 'Version %s.%s.%s (%s) %s',
@@ -680,35 +680,35 @@
'view': 'Ansicht', 'view': 'Ansicht',
'Views': 'Ansichten', 'Views': 'Ansichten',
'views': 'Ansichten', 'views': 'Ansichten',
'Warning!': 'Warning!', 'Warning!': 'Warnung!',
'WARNING:': 'WARNING:', 'WARNING:': 'WARNUNG:',
'WARNING: The following views could not be compiled:': 'WARNING: The following views could not be compiled:', 'WARNING: The following views could not be compiled:': 'WARNUNG: Die folgenden Ansichten konnten nicht kompiliert werden:',
'Web Framework': 'Web Framework', 'Web Framework': 'Web Framework',
'web2py Admin Password': 'web2py Admin Password', 'web2py Admin Password': 'Administratorkennwort für web2py',
'web2py apps to deploy': 'web2py apps to deploy', 'web2py apps to deploy': 'Web2py-Apps zur Bereitstellung',
'web2py Debugger': 'web2py Debugger', 'web2py Debugger': 'web2py Debugger',
'web2py downgrade': 'web2py downgrade', 'web2py downgrade': 'web2py Downgrade',
'web2py is up to date': 'web2py ist aktuell', 'web2py is up to date': 'web2py ist aktuell',
'web2py online debugger': 'web2py online debugger', 'web2py online debugger': 'web2py Online-Debugger',
'web2py Recent Tweets': 'Neuste Tweets von web2py', 'web2py Recent Tweets': 'Neuste Tweets von web2py',
'web2py upgrade': 'web2py upgrade', 'web2py upgrade': 'web2py-Upgrade',
'web2py upgraded; please restart it': 'web2py upgraded; please restart it', 'web2py upgraded; please restart it': 'web2py aktualisiert bitte neu starten',
'Welcome %s': 'Willkommen %s', 'Welcome %s': 'Willkommen %s',
'Welcome to web2py': 'Willkommen zu web2py', 'Welcome to web2py': 'Willkommen zu web2py',
'Which called the function': 'welche die Funktion aufrief', 'Which called the function': 'welche die Funktion aufrief',
'Working...': 'Working...', 'Working...': 'Arbeitet...',
'Wrap with Abbreviation': 'mit Kürzel einhüllen', 'Wrap with Abbreviation': 'mit Kürzel einhüllen',
'WSGI reference name': 'WSGI reference name', 'WSGI reference name': 'WSGI Referenzname',
'xml': 'xml', 'xml': 'xml',
'YES': 'JA', 'YES': 'JA',
'Yes': 'Yes', 'Yes': 'JA',
'You are successfully running web2py': 'web2by wird erfolgreich ausgeführt', 'You are successfully running web2py': 'web2by wird erfolgreich ausgeführt',
'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button', 'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'Sie können Breakpoint auch im Bearbeitungsfenster festlegen und entfernen, indem Sie die Schaltfläche Breakpoint umschalten verwenden',
'You can inspect variables using the console below': 'You can inspect variables using the console below', 'You can inspect variables using the console below': 'Sie können Variablen mit der folgenden Konsole überprüfen',
'You can modify this application and adapt it to your needs': 'Sie können diese Anwendung verändern und Ihren Bedürfnissen anpassen', 'You can modify this application and adapt it to your needs': 'Sie können diese Anwendung verändern und Ihren Bedürfnissen anpassen',
'You have one more login attempt before you are locked out': 'You have one more login attempt before you are locked out', 'You have one more login attempt before you are locked out': 'Sie haben einen weiteren Anmeldeversuch, bevor Sie gesperrt werden',
'You need to set up and reach a': 'You need to set up and reach a', 'You need to set up and reach a': 'You need to set up and reach a',
'You only need these if you have already registered': 'You only need these if you have already registered', 'You only need these if you have already registered': 'Sie benötigen diese nur, wenn Sie sich bereits registriert haben.',
'You visited the url': 'Sie besuchten die URL', 'You visited the url': 'Sie besuchten die URL',
'Your application will be blocked until you click an action button (next, step, continue, etc.)': 'Your application will be blocked until you click an action button (next, step, continue, etc.)', 'Your application will be blocked until you click an action button (next, step, continue, etc.)': 'Ihre Bewerbung wird blockiert, bis Sie auf eine Aktionsschaltfläche klicken (next, step, continue usw.).',
} }
+2 -2
View File
@@ -63,12 +63,12 @@
<h3 class="not_paused">{{=T("No Interaction yet")}}</h3> <h3 class="not_paused">{{=T("No Interaction yet")}}</h3>
<div class="help span7 alert alert-block alert-info"> <div class="help span7 alert alert-block alert-info">
<ul class="unstyled"> <ul class="unstyled">
<li>{{=T("You need to set up and reach a")}} {{=A(T("breakpoint"), _href=URL('breakpoints'))}} {{=T('to use the debugger!')}}</li> <li>{{=T.M("You need to set up and reach a [[breakpoint %s]] to use the debugger!') %URL('breakpoints')}}</li>
<li>{{=T('To emulate a breakpoint programatically, write:')}} <li>{{=T('To emulate a breakpoint programatically, write:')}}
{{=CODE("from gluon.debug import dbg\n" {{=CODE("from gluon.debug import dbg\n"
"dbg.set_trace() # stop here!\n", "dbg.set_trace() # stop here!\n",
counter=None)}}</li> counter=None)}}</li>
<li>{{=T('Please')}} {{=A(T("refresh"), _href=URL('interact'))}} {{=T('this page to see if a breakpoint was hit and debug interaction is required.')}}</li> <li>{{=T.M('Please [[refresh %s]] this page to see if a breakpoint was hit and debug interaction is required.') %URL('interact')}}</li>
</ul> </ul>
</div> </div>
{{pass}} {{pass}}
@@ -185,9 +185,9 @@ def select():
is_imap = db._uri.startswith("imap://") is_imap = db._uri.startswith("imap://")
except (KeyError, AttributeError, TypeError): except (KeyError, AttributeError, TypeError):
is_imap = False is_imap = False
regex = re.compile('(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)') regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)')
if len(request.args) > 1 and hasattr(db[request.args[1]], '_primarykey'): if len(request.args) > 1 and hasattr(db[request.args[1]], '_primarykey'):
regex = re.compile('(?P<table>\w+)\.(?P<field>\w+)=(?P<value>.+)') regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>.+)')
if request.vars.query: if request.vars.query:
match = regex.match(request.vars.query) match = regex.match(request.vars.query)
if match: if match:
@@ -237,7 +237,7 @@ def select():
tb = None tb = None
if form.accepts(request.vars, formname=None): if form.accepts(request.vars, formname=None):
regex = re.compile(request.args[0] + '\.(?P<table>\w+)\..+') regex = re.compile(request.args[0] + r'\.(?P<table>\w+)\..+')
match = regex.match(form.vars.query.strip()) match = regex.match(form.vars.query.strip())
if match: if match:
table = match.group('table') table = match.group('table')
+3 -3
View File
@@ -185,9 +185,9 @@ def select():
is_imap = db._uri.startswith("imap://") is_imap = db._uri.startswith("imap://")
except (KeyError, AttributeError, TypeError): except (KeyError, AttributeError, TypeError):
is_imap = False is_imap = False
regex = re.compile('(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)') regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)')
if len(request.args) > 1 and hasattr(db[request.args[1]], '_primarykey'): if len(request.args) > 1 and hasattr(db[request.args[1]], '_primarykey'):
regex = re.compile('(?P<table>\w+)\.(?P<field>\w+)=(?P<value>.+)') regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>.+)')
if request.vars.query: if request.vars.query:
match = regex.match(request.vars.query) match = regex.match(request.vars.query)
if match: if match:
@@ -237,7 +237,7 @@ def select():
tb = None tb = None
if form.accepts(request.vars, formname=None): if form.accepts(request.vars, formname=None):
regex = re.compile(request.args[0] + '\.(?P<table>\w+)\..+') regex = re.compile(request.args[0] + r'\.(?P<table>\w+)\..+')
match = regex.match(form.vars.query.strip()) match = regex.match(form.vars.query.strip())
if match: if match:
table = match.group('table') table = match.group('table')
@@ -1 +0,0 @@
#crontab
+8 -8
View File
@@ -5,17 +5,17 @@
'"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', '"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 %%(shop)': '%s %%(shop)', '%s %%(shop)': '%s %%(shop)',
'%s %%(shop[0])': '%s %%(shop[0])', '%s %%(shop[0])': '%s %%(shop[0])',
'%s %%{quark[0]}': '%s %%{quark[0]}', '%s %%{quark[0]}': '%s %%{Quark[0]}',
'%s %%{row} deleted': '%s %%{row} gelöscht', '%s %%{row} deleted': '%s %%{row} gelöscht',
'%s %%{row} updated': '%s %%{row} aktualisiert', '%s %%{row} updated': '%s %%{row} aktualisiert',
'%s %%{shop[0]}': '%s %%{shop[0]}', '%s %%{shop[0]}': '%s %%{Shop[0]}',
'%s %%{shop}': '%s %%{shop}', '%s %%{shop}': '%s %%{Shop}',
'%s selected': '%s ausgewählt', '%s selected': '%s ausgewählt',
'%Y-%m-%d': '%Y-%m-%d', '%Y-%m-%d': '%Y-%m-%d',
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
'(**%.0d MB**)': '(**%.0d MB**)', '(**%.0d MB**)': '(**%.0d MB**)',
'**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}', '**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** %%{Item(Items)}, **%(bytes)s** %%{Byte(Bytes)}',
'**%(items)s** items, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** items, **%(bytes)s** %%{byte(bytes)}', '**%(items)s** items, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** Items, **%(bytes)s** %%{byte(bytes)}',
'**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '**nicht verfügbar** (benötigt die Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] Bibliothek)', '**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '**nicht verfügbar** (benötigt die Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] Bibliothek)',
'?': '?', '?': '?',
'@markmin\x01**Hello World**': '**Hallo Welt**', '@markmin\x01**Hello World**': '**Hallo Welt**',
@@ -94,7 +94,7 @@
'Hello World': 'Hallo Welt', 'Hello World': 'Hallo Welt',
'Hello World ## Kommentar': 'Hallo Welt ', 'Hello World ## Kommentar': 'Hallo Welt ',
'Hello World## Kommentar': 'Hallo Welt', 'Hello World## Kommentar': 'Hallo Welt',
'Helping web2py': 'Helping web2py', 'Helping web2py': 'web2py Helfen',
'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})': 'Trefferquote: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} und **%(misses)s** %%{miss(misses)})', 'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})': 'Trefferquote: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} und **%(misses)s** %%{miss(misses)})',
'Home': 'Startseite', 'Home': 'Startseite',
'How did you get here?': 'Wie sind Sie hier her gelangt?', 'How did you get here?': 'Wie sind Sie hier her gelangt?',
@@ -164,7 +164,7 @@
'record does not exist': 'Eintrag existiert nicht', 'record does not exist': 'Eintrag existiert nicht',
'Record ID': 'ID des Eintrags', 'Record ID': 'ID des Eintrags',
'Record id': 'id des Eintrags', 'Record id': 'id des Eintrags',
'Register': 'Register', 'Register': 'Registrieren',
'Registration identifier': 'Registrierungsbezeichnung', 'Registration identifier': 'Registrierungsbezeichnung',
'Registration key': 'Registierungsschlüssel', 'Registration key': 'Registierungsschlüssel',
'Registration successful': 'Registrierung erfolgreich', 'Registration successful': 'Registrierung erfolgreich',
@@ -211,7 +211,7 @@
'Welcome': 'Willkommen', 'Welcome': 'Willkommen',
'Welcome to web2py!': 'Willkommen bei web2py!', 'Welcome to web2py!': 'Willkommen bei web2py!',
'Which called the function %s located in the file %s': 'Welche die Funktion %s in der Datei %s aufrief', 'Which called the function %s located in the file %s': 'Welche die Funktion %s in der Datei %s aufrief',
'Working...': 'Arbeite...', 'Working...': 'Arbeitet...',
'You are successfully running web2py': 'web2py wird erfolgreich ausgeführt', 'You are successfully running web2py': 'web2py 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 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 %s': 'Sie haben die URL %s besucht', 'You visited the url %s': 'Sie haben die URL %s besucht',
+1
View File
@@ -102,6 +102,7 @@
'enter a number between %(min)g and %(max)g': 'enter a number between %(min)g and %(max)g', 'enter a number between %(min)g and %(max)g': 'enter a number between %(min)g and %(max)g',
'Enter an integer between %(min)g and %(max)g': 'Enter an integer between %(min)g and %(max)g', 'Enter an integer between %(min)g and %(max)g': 'Enter an integer between %(min)g and %(max)g',
'enter an integer between %(min)g and %(max)g': 'inserisci un intero tra %(min)g e %(max)g', 'enter an integer between %(min)g and %(max)g': 'inserisci un intero tra %(min)g e %(max)g',
'Enter an integer greater than or equal to %(min)g': 'Enter an integer greater than or equal to %(min)g',
'Errors': 'Errori', 'Errors': 'Errori',
'Errors in form, please check it out.': 'Errori nel form, ricontrollalo', 'Errors in form, please check it out.': 'Errori nel form, ricontrollalo',
'export as csv file': 'esporta come file CSV', 'export as csv file': 'esporta come file CSV',
@@ -1,4 +1,3 @@
label, th { label, th {
font-weigth: bold; font-weigth: bold;
white-space: nowrap; white-space: nowrap;
@@ -20,6 +19,9 @@ div.w2p_flash {
margin: 0 0 20px; margin: 0 0 20px;
padding: 15px 35px 15px 15px; padding: 15px 35px 15px 15px;
} }
.nav-item a {
white-space: nowrap;
}
div.w2p_flash.alert:hover { div.w2p_flash.alert:hover {
opacity: 1; opacity: 1;
} }
+4
View File
@@ -14,6 +14,10 @@ environment:
COVERAGE_PROCESS_START: gluon/tests/coverage.ini COVERAGE_PROCESS_START: gluon/tests/coverage.ini
PYTHON_ARCH: "64" PYTHON_ARCH: "64"
- PYTHON: "C:/Python37"
COVERAGE_PROCESS_START: gluon/tests/coverage.ini
PYTHON_ARCH: "64"
clone_depth: 50 clone_depth: 50
init: init:
+15 -2
View File
@@ -16,8 +16,7 @@ __all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE
import os import os
import sys import sys
try: try:
pydalpath = os.path.join( pydalpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "packages", "dal")
os.path.dirname(os.path.abspath(__file__)), "packages", "dal")
if pydalpath not in sys.path: if pydalpath not in sys.path:
sys.path.append(pydalpath) sys.path.append(pydalpath)
import pydal import pydal
@@ -30,6 +29,20 @@ except ImportError:
" git submodule update --init --recursive\n\n" + " git submodule update --init --recursive\n\n" +
"You can also download a complete copy from http://www.web2py.com." "You can also download a complete copy from http://www.web2py.com."
) )
try:
yatlpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "packages", "yatl")
if yatlpath not in sys.path:
sys.path.append(yatlpath)
import yatl
sys.modules['yatl'] = yatl
except ImportError:
raise RuntimeError(
"web2py depends on yatl, which apparently you have not installed.\n" +
"Probably you cloned the repository using git without '--recursive'" +
"\nTo fix this, please run (from inside your web2py folder):\n\n" +
" git submodule update --init --recursive\n\n" +
"You can also download a complete copy from http://www.web2py.com."
)
from .globals import current from .globals import current
from .html import * from .html import *
+1 -158
View File
@@ -1,163 +1,6 @@
import sys from pydal._compat import *
import hashlib
import os
PY2 = sys.version_info[0] == 2
_identity = lambda x: x
if PY2: if PY2:
import cPickle as pickle
from cStringIO import StringIO
import copy_reg as copyreg
from HTMLParser import HTMLParser
import urlparse
from htmlentitydefs import entitydefs, name2codepoint
import __builtin__ as builtin
import thread
import Cookie
import urllib2
import Queue
import ConfigParser as configparser
from email.MIMEBase import MIMEBase
from email.Header import Header
from email import Encoders, Charset
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.Charset import add_charset, QP as charset_QP
from urllib import FancyURLopener, urlencode, urlopen
from urllib import quote as urllib_quote, unquote as urllib_unquote, quote_plus as urllib_quote_plus
from string import maketrans
from types import ClassType
import cgi
import cookielib
from xmlrpclib import ProtocolError
from gluon.contrib import ipaddress from gluon.contrib import ipaddress
BytesIO = StringIO
reduce = reduce
reload = reload
hashlib_md5 = hashlib.md5
iterkeys = lambda d: d.iterkeys()
itervalues = lambda d: d.itervalues()
iteritems = lambda d: d.iteritems()
integer_types = (int, long)
string_types = (str, unicode)
text_type = unicode
basestring = basestring
xrange = xrange
long = long
unichr = unichr
unicodeT = unicode
def implements_bool(cls):
cls.__nonzero__ = cls.__bool__
del cls.__bool__
return cls
def implements_iterator(cls):
cls.next = cls.__next__
del cls.__next__
return cls
def to_bytes(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if isinstance(obj, (bytes, bytearray, buffer)):
return bytes(obj)
if hasattr(obj, 'encode'):
return obj.encode(charset, errors)
raise TypeError('Expected bytes')
def to_native(obj, charset='utf8', errors='strict'):
if obj is None or isinstance(obj, str):
return obj
return obj.encode(charset, errors)
else: else:
import pickle
from io import StringIO, BytesIO
import copyreg
from importlib import reload
from functools import reduce
from html.parser import HTMLParser
from http import cookies as Cookie
from urllib import parse as urlparse
from urllib import request as urllib2
from html.entities import entitydefs, name2codepoint
import builtins as builtin
import _thread as thread
import configparser
import queue as Queue
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email import encoders as Encoders
from email.header import Header
from email.charset import Charset, add_charset, QP as charset_QP
from urllib.request import FancyURLopener, urlopen
from urllib.parse import quote as urllib_quote, unquote as urllib_unquote, urlencode, quote_plus as urllib_quote_plus
from http import cookiejar as cookielib
from xmlrpc.client import ProtocolError
import html # warning, this is the python3 module and not the web2py html module
import ipaddress import ipaddress
hashlib_md5 = lambda s: hashlib.md5(bytes(s, 'utf8'))
iterkeys = lambda d: iter(d.keys())
itervalues = lambda d: iter(d.values())
iteritems = lambda d: iter(d.items())
integer_types = (int,)
string_types = (str,)
text_type = str
basestring = str
xrange = range
long = int
unichr = chr
unicodeT = str
maketrans = str.maketrans
ClassType = type
implements_iterator = _identity
implements_bool = _identity
def to_bytes(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if isinstance(obj, (bytes, bytearray, memoryview)):
return bytes(obj)
if hasattr(obj, 'encode'):
return obj.encode(charset, errors)
raise TypeError('Expected bytes')
def to_native(obj, charset='utf8', errors='strict'):
if obj is None or isinstance(obj, str):
return obj
return obj.decode(charset, errors)
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(meta):
__call__ = type.__call__
__init__ = type.__init__
def __new__(cls, name, this_bases, d):
if this_bases is None:
return type.__new__(cls, name, (), d)
return meta(name, bases, d)
return metaclass('temporary_class', None, {})
def to_unicode(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if not hasattr(obj, 'decode'):
return text_type(obj)
return obj.decode(charset, errors)
# shortcuts
pjoin = os.path.join
exists = os.path.exists
+1 -1
View File
@@ -9,7 +9,7 @@
Utility functions for the Admin application Utility functions for the Admin application
------------------------------------------- -------------------------------------------
""" """
from __future__ import print_function
import os import os
import sys import sys
import traceback import traceback
+1 -1
View File
@@ -988,7 +988,7 @@ class AuthAPI(object):
requires = table_user[passfield].requires requires = table_user[passfield].requires
if not isinstance(requires, (list, tuple)): if not isinstance(requires, (list, tuple)):
requires = [requires] requires = [requires]
requires = list(filter(lambda t: isinstance(t, CRYPT), requires)) requires = [t for t in requires if isinstance(t, CRYPT)]
if requires: if requires:
requires[0] = CRYPT(**requires[0].__dict__) # Copy the existing CRYPT attributes requires[0] = CRYPT(**requires[0].__dict__) # Copy the existing CRYPT attributes
requires[0].min_length = 0 # But do not enforce minimum length for the old password requires[0].min_length = 0 # But do not enforce minimum length for the old password
+1 -1
View File
@@ -619,7 +619,7 @@ class Cache(object):
if user_agent_ is True: if user_agent_ is True:
cache_key.append("%(is_mobile)s_%(is_tablet)s" % current.request.user_agent()) cache_key.append("%(is_mobile)s_%(is_tablet)s" % current.request.user_agent())
else: else:
cache_key.append(str(user_agent_.items())) cache_key.append(str(list(user_agent_.items())))
if vars_: if vars_:
cache_key.append(current.request.env.query_string) cache_key.append(current.request.env.query_string)
if lang_: if lang_:
+12 -8
View File
@@ -18,14 +18,15 @@ import fnmatch
import os, sys import os, sys
import copy import copy
import random import random
from gluon._compat import builtin, PY2, unicodeT, to_native, to_bytes, iteritems, basestring, reduce, xrange, long, reload from gluon._compat import builtin, PY2, unicodeT, to_native, to_bytes, iteritems, integer_types, basestring, reduce, xrange, long, reload
from gluon.storage import Storage, List from gluon.storage import Storage, List
from gluon.template import parse_template from gluon.template import parse_template
from gluon.restricted import restricted, compile2 from gluon.restricted import restricted, compile2
from gluon.fileutils import mktree, listdir, read_file, write_file from gluon.fileutils import mktree, listdir, read_file, write_file
from gluon.myregex import regex_expose, regex_longcomments from gluon.myregex import regex_expose, regex_longcomments
from gluon.languages import translator from gluon.languages import TranslatorFactory
from gluon.dal import DAL, Field from gluon.dal import DAL, Field
from gluon.validators import Validator
from pydal.base import BaseAdapter from pydal.base import BaseAdapter
from gluon.sqlhtml import SQLFORM, SQLTABLE from gluon.sqlhtml import SQLFORM, SQLTABLE
from gluon.cache import Cache from gluon.cache import Cache
@@ -177,7 +178,7 @@ def LOAD(c=None, f='index', args=None, vars=None,
else: else:
raise TypeError("Unsupported times argument type %s" % type(times)) raise TypeError("Unsupported times argument type %s" % type(times))
if timeout is not None: if timeout is not None:
if not isinstance(timeout, (int, long)): if not isinstance(timeout, integer_types):
raise ValueError("Timeout argument must be an integer or None") raise ValueError("Timeout argument must be an integer or None")
elif timeout <= 0: elif timeout <= 0:
raise ValueError( raise ValueError(
@@ -261,7 +262,7 @@ class LoadFactory(object):
if args is None: if args is None:
args = [] args = []
vars = Storage(vars or {}) vars = Storage(vars or {})
import globals from . import globals
target = target or 'c' + str(random.random())[2:] target = target or 'c' + str(random.random())[2:]
attr['_id'] = target attr['_id'] = target
request = current.request request = current.request
@@ -423,16 +424,19 @@ def build_environment(request, response, session, store_current=True):
r'^%s/%s/\w+\.py$' % (request.controller, request.function) r'^%s/%s/\w+\.py$' % (request.controller, request.function)
] ]
t = environment['T'] = translator(os.path.join(request.folder, 'languages'), T = environment['T'] = TranslatorFactory(os.path.join(request.folder, 'languages'),
request.env.http_accept_language) request.env.http_accept_language)
c = environment['cache'] = Cache(request) c = environment['cache'] = Cache(request)
# configure the validator to use the t translator
Validator.translator = T
if store_current: if store_current:
current.globalenv = environment current.globalenv = environment
current.request = request current.request = request
current.response = response current.response = response
current.session = session current.session = session
current.T = t current.T = T
current.cache = c current.cache = c
if is_jython: # jython hack if is_jython: # jython hack
@@ -631,7 +635,7 @@ def run_controller_in(controller, function, environment):
raise HTTP(404, raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badc, rewrite.THREAD_LOCAL.routes.error_message % badc,
web2py_error=badc) web2py_error=badc)
environment['__symbols__'] = environment.keys() environment['__symbols__'] = list(environment.keys())
code = read_file(filename) code = read_file(filename)
code += TEST_CODE code += TEST_CODE
ccode = compile2(code, filename) ccode = compile2(code, filename)
+5 -5
View File
@@ -108,8 +108,8 @@ def saml2_handler(session, request, config_filename = None, entityid = None):
config_filename = config_filename or os.path.join(request.folder,'private','sp_conf') config_filename = config_filename or os.path.join(request.folder,'private','sp_conf')
client = Saml2Client(config_file = config_filename) client = Saml2Client(config_file = config_filename)
if not entityid: if not entityid:
idps = client.metadata.with_descriptor("idpsso") idps = client.metadata.with_descriptor("idpsso")
entityid = idps.keys()[0] entityid = idps.keys()[0]
bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST] bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]
binding, destination = client.pick_binding( binding, destination = client.pick_binding(
"single_sign_on_service", bindings, "idpsso", entity_id=entityid) "single_sign_on_service", bindings, "idpsso", entity_id=entityid)
@@ -150,14 +150,14 @@ class Saml2Auth(object):
self.config_file = config_file self.config_file = config_file
self.maps = maps self.maps = maps
# URL for redirecting users to when they sign out # URL for redirecting users to when they sign out
self.saml_logout_url = logout_url self.saml_logout_url = logout_url
# URL to let users change their password in the IDP system # URL to let users change their password in the IDP system
self.saml_change_password_url = change_password_url self.saml_change_password_url = change_password_url
# URL to specify an IDP if using federation metadata or an MDQ # URL to specify an IDP if using federation metadata or an MDQ
self.entityid = entityid self.entityid = entityid
def login_url(self, next="/"): def login_url(self, next="/"):
d = saml2_handler(current.session, current.request, entityid=self.entityid) d = saml2_handler(current.session, current.request, entityid=self.entityid)
+2 -1
View File
@@ -1,5 +1,6 @@
from .markdown2 import * from .markdown2 import *
from gluon.html import XML from gluon.html import XML
from gluon._compat import to_unicode
def WIKI(text, encoding="utf8", safe_mode='escape', html4tags=False, **attributes): def WIKI(text, encoding="utf8", safe_mode='escape', html4tags=False, **attributes):
if not text: if not text:
@@ -9,7 +10,7 @@ def WIKI(text, encoding="utf8", safe_mode='escape', html4tags=False, **attribute
del attributes['extras'] del attributes['extras']
else: else:
extras=None extras=None
text = text.decode(encoding,'replace') text = to_unicode(text, encoding, 'replace')
return XML(markdown(text,extras=extras, return XML(markdown(text,extras=extras,
safe_mode=safe_mode, html4tags=html4tags)\ safe_mode=safe_mode, html4tags=html4tags)\
+8 -8
View File
@@ -82,14 +82,14 @@ def test():
def check(data, salt, iterations, keylen, expected): def check(data, salt, iterations, keylen, expected):
rv = pbkdf2_hex(data, salt, iterations, keylen) rv = pbkdf2_hex(data, salt, iterations, keylen)
if rv != expected: if rv != expected:
print 'Test failed:' print('Test failed:')
print ' Expected: %s' % expected print(' Expected: %s' % expected)
print ' Got: %s' % rv print(' Got: %s' % rv)
print ' Parameters:' print(' Parameters:')
print ' data=%s' % data print(' data=%s' % data)
print ' salt=%s' % salt print(' salt=%s' % salt)
print ' iterations=%d' % iterations print(' iterations=%d' % iterations)
print print()
failed.append(1) failed.append(1)
# From RFC 6070 # From RFC 6070
+2 -2
View File
@@ -57,8 +57,8 @@ def autoDetectXMLEncoding(buffer):
secret_decoder_ring = codecs.lookup(encoding)[1] secret_decoder_ring = codecs.lookup(encoding)[1]
(decoded, length) = secret_decoder_ring(buffer) (decoded, length) = secret_decoder_ring(buffer)
first_line = decoded.split("\n")[0] first_line = decoded.split("\n")[0]
if first_line and first_line.startswith(u"<?xml"): if first_line and first_line.startswith("<?xml"):
encoding_pos = first_line.find(u"encoding") encoding_pos = first_line.find("encoding")
if encoding_pos != -1: if encoding_pos != -1:
# look for double quote # look for double quote
quote_pos = first_line.find('"', encoding_pos) quote_pos = first_line.find('"', encoding_pos)
+9 -9
View File
@@ -6,9 +6,9 @@
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> | Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) | License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
""" """
from __future__ import print_function
from gluon._compat import xrange from pydal._compat import xrange
from gluon.utils import local_html_escape from yatl.sanitizer import xmlescape
import re import re
__all__ = ['highlight'] __all__ = ['highlight']
@@ -63,7 +63,7 @@ class Highlighter(object):
Callback for C specific highlighting. Callback for C specific highlighting.
""" """
value = local_html_escape(match.group(), quote=False) value = xmlescape(match.group(), quote=False)
self.change_style(token, style) self.change_style(token, style)
self.output.append(value) self.output.append(value)
@@ -77,7 +77,7 @@ class Highlighter(object):
Callback for python specific highlighting. Callback for python specific highlighting.
""" """
value = local_html_escape(match.group(), quote=False) value = xmlescape(match.group(), quote=False)
if token == 'MULTILINESTRING': if token == 'MULTILINESTRING':
self.change_style(token, style) self.change_style(token, style)
self.output.append(value) self.output.append(value)
@@ -114,7 +114,7 @@ class Highlighter(object):
Callback for HTML specific highlighting. Callback for HTML specific highlighting.
""" """
value = local_html_escape(match.group(), quote=False) value = xmlescape(match.group(), quote=False)
self.change_style(token, style) self.change_style(token, style)
self.output.append(value) self.output.append(value)
if token == 'GOTOPYTHON': if token == 'GOTOPYTHON':
@@ -286,13 +286,13 @@ color: #A0A0A0;
'WEB2PY']: 'WEB2PY']:
code = Highlighter(language, link, styles).highlight(code) code = Highlighter(language, link, styles).highlight(code)
else: else:
code = local_html_escape(code, quote=False) code = xmlescape(code, quote=False)
lines = code.split('\n') lines = code.split('\n')
if counter is None: if counter is None:
linenumbers = [''] * len(lines) linenumbers = [''] * len(lines)
elif isinstance(counter, str): elif isinstance(counter, str):
linenumbers = [local_html_escape(counter, quote=False)] * len(lines) linenumbers = [xmlescape(counter, quote=False)] * len(lines)
else: else:
linenumbers = [str(i + counter) + '.' for i in linenumbers = [str(i + counter) + '.' for i in
xrange(len(lines))] xrange(len(lines))]
@@ -325,7 +325,7 @@ color: #A0A0A0;
fa = ' '.join([key[1:].lower() for (key, value) in items if key[:1] fa = ' '.join([key[1:].lower() for (key, value) in items if key[:1]
== '_' and value is None] + ['%s="%s"' == '_' and value is None] + ['%s="%s"'
% (key[1:].lower(), str(value).replace('"', "'")) % (key[1:].lower(), str(value).replace('"', "'"))
for (key, value) in attributes.items() if key[:1] for (key, value) in items if key[:1]
== '_' and value]) == '_' and value])
if fa: if fa:
fa = ' ' + fa fa = ' ' + fa
+29 -6
View File
@@ -9,7 +9,6 @@
Template helpers Template helpers
-------------------------------------------- --------------------------------------------
""" """
from __future__ import print_function
import cgi import cgi
import os import os
@@ -18,25 +17,49 @@ import copy
import types import types
import urllib import urllib
import base64 import base64
from gluon import sanitizer, decoder
import itertools import itertools
from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, \ from pydal._compat import PY2, reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, \
urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type, long urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type, long
from gluon.utils import local_html_escape from yatl import sanitizer
import marshal import marshal
from gluon import decoder
from gluon.storage import Storage from gluon.storage import Storage
from gluon.utils import web2py_uuid, simple_hash, compare from gluon.utils import web2py_uuid, simple_hash, compare
from gluon.highlight import highlight from gluon.highlight import highlight
def local_html_escape(data, quote=False):
"""
Works with bytes.
Replace special characters "&", "<" and ">" to HTML-safe sequences.
If the optional flag quote is true (the default), the quotation mark
characters, both double quote (") and single quote (') characters are also
translated.
"""
if PY2:
import cgi
data = cgi.escape(data, quote)
return data.replace("'", "&#x27;") if quote else data
else:
import html
if isinstance(data, str):
return html.escape(data, quote=quote)
data = data.replace(b"&", b"&amp;") # Must be done first!
data = data.replace(b"<", b"&lt;")
data = data.replace(b">", b"&gt;")
if quote:
data = data.replace(b'"', b"&quot;")
data = data.replace(b'\'', b"&#x27;")
return data
regex_crlf = re.compile('\r|\n') regex_crlf = re.compile('\r|\n')
join = ''.join join = ''.join
# name2codepoint is incomplete respect to xhtml (and xml): 'apos' is missing. # name2codepoint is incomplete respect to xhtml (and xml): 'apos' is missing.
entitydefs = dict(map(lambda k_v: (k_v[0], unichr(k_v[1]).encode('utf-8')), iteritems(name2codepoint))) entitydefs = dict([(k_v[0], to_bytes(unichr(k_v[1]))) for k_v in iteritems(name2codepoint)])
entitydefs.setdefault('apos', u"'".encode('utf-8')) entitydefs.setdefault('apos', to_bytes("'"))
__all__ = [ __all__ = [
+8 -9
View File
@@ -19,12 +19,11 @@ import logging
from cgi import escape from cgi import escape
from threading import RLock from threading import RLock
from gluon.utils import local_html_escape from pydal._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, to_native, pjoin
from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, to_native, pjoin
from pydal.contrib.portalocker import read_locked, LockedFile from pydal.contrib.portalocker import read_locked, LockedFile
from yatl.sanitizer import xmlescape
from gluon.fileutils import listdir from gluon.fileutils import listdir
from gluon.cfs import getcfs from gluon.cfs import getcfs
from gluon.html import XML, xmlescape from gluon.html import XML, xmlescape
@@ -326,7 +325,7 @@ def write_plural_dict(filename, contents):
def sort_function(x): def sort_function(x):
return unicode(x, 'utf-8').lower() return to_unicode(x, 'utf-8').lower()
def write_dict(filename, contents): def write_dict(filename, contents):
@@ -428,7 +427,7 @@ class lazyT(object):
return len(str(self)) return len(str(self))
def xml(self): def xml(self):
return str(self) if self.M else local_html_escape(str(self), quote=False) return str(self) if self.M else xmlescape(str(self), quote=False)
def encode(self, *a, **b): def encode(self, *a, **b):
if PY2 and a[0] != 'utf8': if PY2 and a[0] != 'utf8':
@@ -457,7 +456,7 @@ def pickle_lazyT(c):
copyreg.pickle(lazyT, pickle_lazyT) copyreg.pickle(lazyT, pickle_lazyT)
class translator(object): class TranslatorFactory(object):
""" """
This class is instantiated by gluon.compileapp.build_environment This class is instantiated by gluon.compileapp.build_environment
as the T object as the T object
@@ -742,8 +741,8 @@ class translator(object):
try: try:
otherT = self.otherTs[index] otherT = self.otherTs[index]
except KeyError: except KeyError:
otherT = self.otherTs[index] = translator(self.langpath, otherT = self.otherTs[index] = TranslatorFactory(self.langpath,
self.http_accept_language) self.http_accept_language)
if language: if language:
otherT.force(language) otherT.force(language)
return otherT return otherT
+6 -7
View File
@@ -9,10 +9,9 @@
The gluon wsgi application The gluon wsgi application
--------------------------- ---------------------------
""" """
from __future__ import print_function
if False: if False:
import import_all # DO NOT REMOVE PART OF FREEZE PROCESS from . import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
import gc import gc
import os import os
@@ -249,7 +248,7 @@ class LazyWSGI(object):
def app(environ, start_response): def app(environ, start_response):
data = f() data = f()
start_response(self.response.status, start_response(self.response.status,
self.response.headers.items()) list(self.response.headers.items()))
if isinstance(data, list): if isinstance(data, list):
return data return data
return [data] return [data]
@@ -486,10 +485,10 @@ def wsgibase(environ, responder):
if request.ajax: if request.ajax:
if response.flash: if response.flash:
http_response.headers['web2py-component-flash'] = \ http_response.headers['web2py-component-flash'] = \
urllib2.quote(xmlescape(response.flash).replace(b'\n', b'')) urllib_quote(xmlescape(response.flash).replace(b'\n', b''))
if response.js: if response.js:
http_response.headers['web2py-component-command'] = \ http_response.headers['web2py-component-command'] = \
urllib2.quote(response.js.replace('\n', '')) urllib_quote(response.js.replace('\n', ''))
# ################################################## # ##################################################
# store cookies in headers # store cookies in headers
@@ -717,9 +716,9 @@ class HttpServer(object):
if isinstance(interfaces, list): if isinstance(interfaces, list):
for i in interfaces: for i in interfaces:
if not isinstance(i, tuple): if not isinstance(i, tuple):
raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/" raise AttributeError("Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/")
else: else:
raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/" raise AttributeError("Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/")
if path: if path:
# if a path is specified change the global variables so that web2py # if a path is specified change the global variables so that web2py
+11 -7
View File
@@ -1,9 +1,13 @@
import logging import logging
import os import os
import sys
try: try:
import Tkinter if sys.version_info[0] == 2:
except: import Tkinter as tkinter
else:
import tkinter
except ImportError:
Tkinter = None Tkinter = None
@@ -12,15 +16,15 @@ class MessageBoxHandler(logging.Handler):
logging.Handler.__init__(self) logging.Handler.__init__(self)
def emit(self, record): def emit(self, record):
if Tkinter: if tkinter:
msg = self.format(record) msg = self.format(record)
root = Tkinter.Tk() root = tkinter.Tk()
root.wm_title("web2py logger message") root.wm_title("web2py logger message")
text = Tkinter.Text() text = tkinter.Text()
text["height"] = 12 text["height"] = 12
text.insert(0.1, msg) text.insert(0.1, msg)
text.pack() text.pack()
button = Tkinter.Button(root, text="OK", command=root.destroy) button = tkinter.Button(root, text="OK", command=root.destroy)
button.pack() button.pack()
root.mainloop() root.mainloop()
@@ -30,6 +34,6 @@ class NotifySendHandler(logging.Handler):
logging.Handler.__init__(self) logging.Handler.__init__(self)
def emit(self, record): def emit(self, record):
if Tkinter: if tkinter:
msg = self.format(record) msg = self.format(record)
os.system("notify-send '%s'" % msg) os.system("notify-send '%s'" % msg)
+1 -5
View File
@@ -20,13 +20,9 @@ import re
import datetime import datetime
import platform import platform
from functools import reduce from functools import reduce
try:
import cPickle as pickle
except:
import pickle
from gluon.settings import global_settings from gluon.settings import global_settings
from gluon import fileutils from gluon import fileutils
from gluon._compat import to_bytes from gluon._compat import to_bytes, pickle
from pydal.contrib import portalocker from pydal.contrib import portalocker
logger = logging.getLogger("web2py.cron") logger = logging.getLogger("web2py.cron")
Submodule gluon/packages/yatl added at 7e905158ff
-1
View File
@@ -15,7 +15,6 @@ routes.py supports two styles of URL rewriting, depending on whether 'routers' i
Refer to router.example.py and routes.example.py for additional documentation. Refer to router.example.py and routes.example.py for additional documentation.
""" """
from __future__ import print_function
import os import os
import re import re
+8 -39
View File
@@ -5,14 +5,14 @@
# Modified by Massimo Di Pierro # Modified by Massimo Di Pierro
# Import System Modules # Import System Modules
from __future__ import print_function
import sys import sys
import errno import errno
import socket import socket
import logging import logging
import platform import platform
from gluon._compat import iteritems, to_bytes, StringIO from gluon._compat import iteritems, to_bytes, to_unicode, StringIO
from gluon._compat import urllib_unquote, to_native from gluon._compat import urllib_unquote, to_native, PY2
# Define Constants # Define Constants
VERSION = '1.2.6' VERSION = '1.2.6'
@@ -32,7 +32,7 @@ DEFAULTS = dict(LISTEN_QUEUE_SIZE=DEFAULT_LISTEN_QUEUE_SIZE,
MIN_THREADS=DEFAULT_MIN_THREADS, MIN_THREADS=DEFAULT_MIN_THREADS,
MAX_THREADS=DEFAULT_MAX_THREADS) MAX_THREADS=DEFAULT_MAX_THREADS)
PY3K = sys.version_info[0] > 2 PY3K = not PY2
class NullHandler(logging.Handler): class NullHandler(logging.Handler):
@@ -40,39 +40,8 @@ class NullHandler(logging.Handler):
def emit(self, record): def emit(self, record):
pass pass
if PY3K: b = to_bytes
def b(val): u = to_unicode
""" Convert string/unicode/bytes literals into bytes. This allows for
the same code to run on Python 2.x and 3.x. """
if isinstance(val, str):
return val.encode()
else:
return val
def u(val, encoding="us-ascii"):
""" Convert bytes into string/unicode. This allows for the
same code to run on Python 2.x and 3.x. """
if isinstance(val, bytes):
return val.decode(encoding)
else:
return val
else:
def b(val):
""" Convert string/unicode/bytes literals into bytes. This allows for
the same code to run on Python 2.x and 3.x. """
if isinstance(val, unicode):
return val.encode()
else:
return val
def u(val, encoding="us-ascii"):
""" Convert bytes into string/unicode. This allows for the
same code to run on Python 2.x and 3.x. """
if isinstance(val, str):
return val.decode(encoding)
else:
return val
# Import Package Modules # Import Package Modules
# package imports removed in monolithic build # package imports removed in monolithic build
@@ -613,9 +582,9 @@ import socket
import logging import logging
import traceback import traceback
from threading import Lock from threading import Lock
try: if PY3K:
from queue import Queue from queue import Queue
except ImportError: else:
from Queue import Queue from Queue import Queue
# Import Package Modules # Import Package Modules
-1
View File
@@ -1,4 +1,3 @@
from __future__ import print_function
# The following code is not part of Rocket but was added to # The following code is not part of Rocket but was added to
# web2py for testing purposes. # web2py for testing purposes.
+1 -219
View File
@@ -1,219 +1 @@
#!/usr/bin/env python from yatl.sanitizer import sanitize
# -*- coding: utf-8 -*-
"""
| From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942
| Submitter: Josh Goldfoot (other recipes)
| Last Updated: 2006/08/05
| Version: 1.0
Cross-site scripting (XSS) defense
-----------------------------------
"""
from gluon._compat import HTMLParser, urlparse, entitydefs, basestring
from gluon.utils import local_html_escape
from formatter import AbstractFormatter
from xml.sax.saxutils import quoteattr
__all__ = ['sanitize']
def xssescape(text):
"""Gets rid of < and > and & and, for good measure, :"""
return local_html_escape(text, quote=True).replace(':', '&#58;')
class XssCleaner(HTMLParser):
def __init__(
self,
permitted_tags=[
'a',
'b',
'blockquote',
'br/',
'i',
'li',
'ol',
'ul',
'p',
'cite',
'code',
'pre',
'img/',
],
allowed_attributes={'a': ['href', 'title'], 'img': ['src', 'alt'
], 'blockquote': ['type']},
strip_disallowed=False
):
HTMLParser.__init__(self)
self.result = ''
self.open_tags = []
self.permitted_tags = [i for i in permitted_tags if i[-1] != '/']
self.requires_no_close = [i[:-1] for i in permitted_tags
if i[-1] == '/']
self.permitted_tags += self.requires_no_close
self.allowed_attributes = allowed_attributes
# The only schemes allowed in URLs (for href and src attributes).
# Adding "javascript" or "vbscript" to this list would not be smart.
self.allowed_schemes = ['http', 'https', 'ftp', 'mailto']
#to strip or escape disallowed tags?
self.strip_disallowed = strip_disallowed
# there might be data after final closing tag, that is to be ignored
self.in_disallowed = [False]
def handle_data(self, data):
if data and not self.in_disallowed[-1]:
self.result += xssescape(data)
def handle_charref(self, ref):
if self.in_disallowed[-1]:
return
elif len(ref) < 7 and (ref.isdigit() or ref == 'x27'): # x27 is a special case for apostrophe
self.result += '&#%s;' % ref
else:
self.result += xssescape('&#%s' % ref)
def handle_entityref(self, ref):
if self.in_disallowed[-1]:
return
elif ref in entitydefs:
self.result += '&%s;' % ref
else:
self.result += xssescape('&%s' % ref)
def handle_comment(self, comment):
if self.in_disallowed[-1]:
return
elif comment:
self.result += xssescape('<!--%s-->' % comment)
def handle_starttag(
self,
tag,
attrs
):
if tag not in self.permitted_tags:
self.in_disallowed.append(True)
if (not self.strip_disallowed):
self.result += xssescape('<%s>' % tag)
else:
self.in_disallowed.append(False)
bt = '<' + tag
if tag in self.allowed_attributes:
attrs = dict(attrs)
self.allowed_attributes_here = [x for x in
self.allowed_attributes[tag] if x in attrs
and len(attrs[x]) > 0]
for attribute in self.allowed_attributes_here:
if attribute in ['href', 'src', 'background']:
if self.url_is_acceptable(attrs[attribute]):
bt += ' %s="%s"' % (attribute,
attrs[attribute])
else:
bt += ' %s=%s' % (xssescape(attribute),
quoteattr(attrs[attribute]))
# deal with <a> without href and <img> without src
if bt == '<a' or bt == '<img':
return
if tag in self.requires_no_close:
bt += ' /'
bt += '>'
self.result += bt
if tag not in self.requires_no_close: self.open_tags.insert(0, tag)
def handle_endtag(self, tag):
bracketed = '</%s>' % tag
self.in_disallowed and self.in_disallowed.pop()
if tag not in self.permitted_tags:
if (not self.strip_disallowed):
self.result += xssescape(bracketed)
elif tag in self.open_tags:
self.result += bracketed
self.open_tags.remove(tag)
def url_is_acceptable(self, url):
"""
Accepts relative, absolute, and mailto urls
"""
if url.startswith('#'):
return True
else:
parsed = urlparse.urlparse(url)
return ((parsed[0] in self.allowed_schemes and '.' in parsed[1]) or
(parsed[0] in self.allowed_schemes and '@' in parsed[2]) or
(parsed[0] == '' and parsed[2].startswith('/')))
def strip(self, rawstring, escape=True):
"""
Returns the argument stripped of potentially harmful
HTML or Javascript code
@type escape: boolean
@param escape: If True (default) it escapes the potentially harmful
content, otherwise remove it
"""
if not isinstance(rawstring, str):
return str(rawstring)
for tag in self.requires_no_close:
rawstring = rawstring.replace("<%s/>" % tag, "<%s />" % tag)
if not escape:
self.strip_disallowed = True
self.result = ''
self.feed(rawstring)
for endtag in self.open_tags:
if endtag not in self.requires_no_close:
self.result += '</%s>' % endtag
return self.result
def xtags(self):
"""
Returns a printable string informing the user which tags are allowed
"""
tg = ''
for x in sorted(self.permitted_tags):
tg += '<' + x
if x in self.allowed_attributes:
for y in self.allowed_attributes[x]:
tg += ' %s=""' % y
tg += '> '
return xssescape(tg.strip())
def sanitize(text, permitted_tags=[
'a',
'b',
'blockquote',
'br/',
'i',
'li',
'ol',
'ul',
'p',
'cite',
'code',
'pre',
'img/',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'table', 'tbody', 'thead', 'tfoot', 'tr', 'td', 'div',
'strong', 'span',
],
allowed_attributes={
'a': ['href', 'title'],
'img': ['src', 'alt'],
'blockquote': ['type'],
'td': ['colspan'],
},
escape=True):
if not isinstance(text, basestring):
return str(text)
return XssCleaner(permitted_tags=permitted_tags,
allowed_attributes=allowed_attributes).strip(text, escape)
+9 -11
View File
@@ -8,7 +8,6 @@
Background processes made simple Background processes made simple
--------------------------------- ---------------------------------
""" """
from __future__ import print_function
import os import os
import re import re
@@ -29,7 +28,7 @@ from json import loads, dumps
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB, IS_EMPTY_OR from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB, IS_EMPTY_OR
from gluon import IS_INT_IN_RANGE, IS_DATETIME, IS_IN_DB from gluon import IS_INT_IN_RANGE, IS_DATETIME, IS_IN_DB
from gluon.utils import web2py_uuid from gluon.utils import web2py_uuid
from gluon._compat import Queue, long, iteritems, PY2 from gluon._compat import Queue, long, iteritems, PY2, to_bytes, string_types, integer_types
from gluon.storage import Storage from gluon.storage import Storage
USAGE = """ USAGE = """
@@ -417,8 +416,8 @@ def _decode_list(lst):
return lst return lst
newlist = [] newlist = []
for i in lst: for i in lst:
if isinstance(i, unicode): if isinstance(i, string_types):
i = i.encode('utf-8') i = to_bytes(i)
elif isinstance(i, list): elif isinstance(i, list):
i = _decode_list(i) i = _decode_list(i)
newlist.append(i) newlist.append(i)
@@ -430,10 +429,9 @@ def _decode_dict(dct):
return dct return dct
newdict = {} newdict = {}
for k, v in iteritems(dct): for k, v in iteritems(dct):
if isinstance(k, unicode): k = to_bytes(k)
k = k.encode('utf-8') if isinstance(v, string_types):
if isinstance(v, unicode): v = to_bytes(v)
v = v.encode('utf-8')
elif isinstance(v, list): elif isinstance(v, list):
v = _decode_list(v) v = _decode_list(v)
newdict[k] = v newdict[k] = v
@@ -1572,7 +1570,7 @@ class Scheduler(MetaScheduler):
""" """
from pydal.objects import Query from pydal.objects import Query
sr, st = self.db.scheduler_run, self.db.scheduler_task sr, st = self.db.scheduler_run, self.db.scheduler_task
if isinstance(ref, (int, long)): if isinstance(ref, integer_types):
q = st.id == ref q = st.id == ref
elif isinstance(ref, str): elif isinstance(ref, str):
q = st.uuid == ref q = st.uuid == ref
@@ -1623,7 +1621,7 @@ class Scheduler(MetaScheduler):
Experimental Experimental
""" """
st, sw = self.db.scheduler_task, self.db.scheduler_worker st, sw = self.db.scheduler_task, self.db.scheduler_worker
if isinstance(ref, (int, long)): if isinstance(ref, integer_types):
q = st.id == ref q = st.id == ref
elif isinstance(ref, str): elif isinstance(ref, str):
q = st.uuid == ref q = st.uuid == ref
@@ -1723,7 +1721,7 @@ def main():
sys.path.append(path) sys.path.append(path)
print('importing tasks...') print('importing tasks...')
tasks = __import__(filename, globals(), locals(), [], -1).tasks tasks = __import__(filename, globals(), locals(), [], -1).tasks
print('tasks found: ' + ', '.join(tasks.keys())) print('tasks found: ' + ', '.join(list(tasks.keys())))
else: else:
tasks = {} tasks = {}
group_names = [x.strip() for x in options.group_names.split(',')] group_names = [x.strip() for x in options.group_names.split(',')]
+2 -2
View File
@@ -10,7 +10,7 @@ from gluon.html import TAG, XmlComponent, xmlescape
from gluon.languages import lazyT from gluon.languages import lazyT
import gluon.contrib.rss2 as rss2 import gluon.contrib.rss2 as rss2
import json as json_parser import json as json_parser
from gluon._compat import long, to_native, unicodeT from gluon._compat import long, to_native, unicodeT, integer_types
have_yaml = True have_yaml = True
try: try:
@@ -79,7 +79,7 @@ def custom_json(o):
datetime.datetime, datetime.datetime,
datetime.time)): datetime.time)):
return o.isoformat()[:19].replace('T', ' ') return o.isoformat()[:19].replace('T', ' ')
elif isinstance(o, (int, long)): elif isinstance(o, integer_types):
return int(o) return int(o)
elif isinstance(o, decimal.Decimal): elif isinstance(o, decimal.Decimal):
return str(o) return str(o)
+2 -1
View File
@@ -10,6 +10,7 @@
Web2py environment in the shell Web2py environment in the shell
-------------------------------- --------------------------------
""" """
from __future__ import print_function from __future__ import print_function
import os import os
@@ -95,7 +96,7 @@ def exec_environment(
if pyfile: if pyfile:
pycfile = pyfile + 'c' pycfile = pyfile + 'c'
if os.path.isfile(pycfile): if os.path.isfile(pycfile):
exec (read_pyc(pycfile), env) exec(read_pyc(pycfile), env)
else: else:
execfile(pyfile, env) execfile(pyfile, env)
return Storage(env) return Storage(env)
+62 -51
View File
@@ -717,7 +717,7 @@ class AutocompleteWidget(object):
compact=table_rows.compact) compact=table_rows.compact)
elif settings and settings.global_settings.web2py_runtime_gae: elif settings and settings.global_settings.web2py_runtime_gae:
rows = self.db(field.__ge__(kword) & rows = self.db(field.__ge__(kword) &
field.__lt__(kword + u'\ufffd') field.__lt__(kword + '\ufffd')
).select(orderby=self.orderby, ).select(orderby=self.orderby,
limitby=self.limitby, limitby=self.limitby,
*(self.fields + self.help_fields)) *(self.fields + self.help_fields))
@@ -1966,7 +1966,7 @@ class SQLFORM(FORM):
AUTOTYPES = { AUTOTYPES = {
type(''): ('string', None), type(''): ('string', None),
type(u''): ('string',None), type(''): ('string',None),
type(True): ('boolean', None), type(True): ('boolean', None),
type(1): ('integer', IS_INT_IN_RANGE(-1e12, +1e12)), type(1): ('integer', IS_INT_IN_RANGE(-1e12, +1e12)),
type(1.0): ('double', IS_FLOAT_IN_RANGE()), type(1.0): ('double', IS_FLOAT_IN_RANGE()),
@@ -2322,7 +2322,7 @@ class SQLFORM(FORM):
create = wenabled and create create = wenabled and create
editable = wenabled and editable editable = wenabled and editable
deletable = wenabled and deletable deletable = wenabled and deletable
details = details and not groupby details = not groupby and details
rows = None rows = None
# see issue 1980. Basically we can have keywords in get_vars # see issue 1980. Basically we can have keywords in get_vars
@@ -2385,6 +2385,12 @@ class SQLFORM(FORM):
b['args'] = args + b.get('args', []) b['args'] = args + b.get('args', [])
localvars = request.get_vars.copy() localvars = request.get_vars.copy()
localvars.update(b.get('vars', {})) localvars.update(b.get('vars', {}))
# avoid empty keywords in vars
if localvars.get('keywords') == '':
del localvars['keywords']
# avoid propagating order=None in vars
if localvars.get('order') == 'None':
del localvars['order']
b['vars'] = localvars b['vars'] = localvars
b['hash_vars'] = False b['hash_vars'] = False
b['user_signature'] = user_signature b['user_signature'] = user_signature
@@ -2414,7 +2420,7 @@ class SQLFORM(FORM):
def gridbutton(buttonclass='buttonadd', buttontext=T('Add'), def gridbutton(buttonclass='buttonadd', buttontext=T('Add'),
buttonurl=url(args=[]), callback=None, buttonurl=url(args=[]), callback=None,
delete=None, trap=True, noconfirm=None, title=None): delete=None, noconfirm=None, title=None):
if showbuttontext: if showbuttontext:
return A(SPAN(_class=ui.get(buttonclass)), CAT(' '), return A(SPAN(_class=ui.get(buttonclass)), CAT(' '),
SPAN(T(buttontext), _title=title or T(buttontext), SPAN(T(buttontext), _title=title or T(buttontext),
@@ -2444,11 +2450,6 @@ class SQLFORM(FORM):
tablenames = merge_tablemaps(tablenames, db._adapter.tables(join)) tablenames = merge_tablemaps(tablenames, db._adapter.tables(join))
tables = [db[tablename] for tablename in tablenames] tables = [db[tablename] for tablename in tablenames]
if fields: if fields:
# add missing tablename to virtual fields
for table in tables:
for k, f in iteritems(table):
if isinstance(f, Field.Virtual):
f.tablename = table._tablename
columns = [f for f in fields if f.tablename in tablenames and f.listable] columns = [f for f in fields if f.tablename in tablenames and f.listable]
else: else:
fields = [] fields = []
@@ -2456,8 +2457,8 @@ class SQLFORM(FORM):
filter1 = lambda f: isinstance(f, Field) and (f.type!='blob' or showblobs) filter1 = lambda f: isinstance(f, Field) and (f.type!='blob' or showblobs)
filter2 = lambda f: isinstance(f, Field) and f.readable and f.listable filter2 = lambda f: isinstance(f, Field) and f.readable and f.listable
for table in tables: for table in tables:
fields += filter(filter1, table) fields += list(filter(filter1, table))
columns += filter(filter2, table) columns += list(filter(filter2, table))
for k, f in iteritems(table): for k, f in iteritems(table):
if not k.startswith('_'): if not k.startswith('_'):
if isinstance(f, Field.Virtual) and f.readable: if isinstance(f, Field.Virtual) and f.readable:
@@ -2543,7 +2544,7 @@ class SQLFORM(FORM):
table = db[request.args[-2]] table = db[request.args[-2]]
record = table(request.args[-1]) or redirect(referrer) record = table(request.args[-1]) or redirect(referrer)
if represent_none is not None: if represent_none is not None:
for field in record.iterkeys(): for field in record.keys():
if record[field] is None: if record[field] is None:
record[field] = represent_none record[field] = represent_none
sqlformargs = dict(upload=upload, ignore_rw=ignore_rw, sqlformargs = dict(upload=upload, ignore_rw=ignore_rw,
@@ -2645,20 +2646,18 @@ class SQLFORM(FORM):
orderby = fix_orderby(orderby) orderby = fix_orderby(orderby)
# expcolumns start with the visible columns, which
# includes visible virtual fields
expcolumns = [str(f) for f in columns] expcolumns = [str(f) for f in columns]
selectable_columns = [str(f) for f in columns if not isinstance(f, Field.Virtual)] selectable_columns = [str(f) for f in columns if not isinstance(f, Field.Virtual)]
if export_type.endswith('with_hidden_cols'): if export_type.endswith('with_hidden_cols'):
# expcolumns = [] start with the visible columns, which
# includes visible virtual fields
selectable_columns = []
# like expcolumns but excluding virtual
for table in tables: for table in tables:
for field in table: for field in table:
if field.readable and field.tablename in tablenames: if field.readable and field.tablename in tablenames:
if not str(field) in expcolumns: if not str(field) in expcolumns:
expcolumns.append(str(field)) expcolumns.append(str(field))
if not(isinstance(field, Field.Virtual)): if not(isinstance(field, Field.Virtual)):
selectable_columns.append(str(field)) selectable_columns.append(str(field))
# look for virtual fields not displayed (and virtual method # look for virtual fields not displayed (and virtual method
# fields to be added here?) # fields to be added here?)
for (field_name, field) in iteritems(table): for (field_name, field) in iteritems(table):
@@ -2672,7 +2671,7 @@ class SQLFORM(FORM):
# the query should be constructed using searchable # the query should be constructed using searchable
# fields but not virtual fields # fields but not virtual fields
is_searchable = lambda f: f.readable and not isinstance(f, Field.Virtual) and f.searchable is_searchable = lambda f: f.readable and not isinstance(f, Field.Virtual) and f.searchable
sfields = reduce(lambda a, b: a + b, [filter(is_searchable, t) for t in tables]) sfields = reduce(lambda a, b: a + b, [list(filter(is_searchable, t)) for t in tables])
# use custom_query using searchable # use custom_query using searchable
if callable(searchable): if callable(searchable):
dbset = dbset(searchable(sfields, keywords)) dbset = dbset(searchable(sfields, keywords))
@@ -2680,13 +2679,13 @@ class SQLFORM(FORM):
dbset = dbset(SQLFORM.build_query( dbset = dbset(SQLFORM.build_query(
sfields, keywords)) sfields, keywords))
rows = dbset.select(left=left, orderby=orderby, rows = dbset.select(left=left, orderby=orderby,
cacheable=True, *expcolumns) cacheable=True, *selectable_columns)
except Exception as e: except Exception as e:
response.flash = T('Internal Error') response.flash = T('Internal Error')
rows = [] rows = []
else: else:
rows = dbset.select(left=left, orderby=orderby, rows = dbset.select(left=left, orderby=orderby,
cacheable=True, *expcolumns) cacheable=True, *selectable_columns)
value = exportManager[export_type] value = exportManager[export_type]
clazz = value[0] if hasattr(value, '__getitem__') else value clazz = value[0] if hasattr(value, '__getitem__') else value
@@ -2723,7 +2722,7 @@ class SQLFORM(FORM):
if searchable: if searchable:
sfields = reduce(lambda a, b: a + b, sfields = reduce(lambda a, b: a + b,
[[f for f in t if f.readable] for t in tables]) [[f for f in t if f.readable and f.searchable] for t in tables])
if isinstance(search_widget, dict): if isinstance(search_widget, dict):
search_widget = search_widget[tablename] search_widget = search_widget[tablename]
if search_widget == 'default': if search_widget == 'default':
@@ -2732,7 +2731,7 @@ class SQLFORM(FORM):
spanel_id = '%s_query_fields' % prefix spanel_id = '%s_query_fields' % prefix
sfields_id = '%s_query_panel' % prefix sfields_id = '%s_query_panel' % prefix
skeywords_id = '%s_keywords' % prefix skeywords_id = '%s_keywords' % prefix
# hidden fields to presever keywords in url after the submit # hidden fields to preserve keywords in url after the submit
hidden_fields = [INPUT(_type='hidden', _value=v, _name=k) for k, v in request.get_vars.items() if k not in ['keywords', 'page']] hidden_fields = [INPUT(_type='hidden', _value=v, _name=k) for k, v in request.get_vars.items() if k not in ['keywords', 'page']]
search_widget = lambda sfield, url: CAT(FORM( search_widget = lambda sfield, url: CAT(FORM(
INPUT(_name='keywords', _value=keywords, INPUT(_name='keywords', _value=keywords,
@@ -2748,14 +2747,17 @@ class SQLFORM(FORM):
form = search_widget and search_widget(sfields, url()) or '' form = search_widget and search_widget(sfields, url()) or ''
console.append(add) console.append(add)
console.append(form) console.append(form)
try: if keywords:
if callable(searchable): try:
subquery = searchable(sfields, keywords) if callable(searchable):
else: subquery = searchable(sfields, keywords)
subquery = SQLFORM.build_query(sfields, keywords) else:
except RuntimeError: subquery = SQLFORM.build_query(sfields, keywords)
except RuntimeError:
subquery = None
error = T('Invalid query')
else:
subquery = None subquery = None
error = T('Invalid query')
else: else:
subquery = None subquery = None
@@ -2768,12 +2770,15 @@ class SQLFORM(FORM):
error = T('Unsupported query') error = T('Unsupported query')
order = request.vars.order or '' order = request.vars.order or ''
asc_icon, desc_icon = sorter_icons
if sortable: if sortable:
if order and not order == 'None': if order and not order == 'None':
otablename, ofieldname = order.split('~')[-1].split('.', 1) otablename, ofieldname = order.split('~')[-1].split('.', 1)
sort_field = db[otablename][ofieldname] sort_field = db[otablename][ofieldname]
# invert order direction on date/time fields
exception = sort_field.type in ('date', 'datetime', 'time') exception = sort_field.type in ('date', 'datetime', 'time')
if exception: if exception:
desc_icon, asc_icon = sorter_icons
orderby = (order[:1] == '~' and sort_field) or ~sort_field orderby = (order[:1] == '~' and sort_field) or ~sort_field
else: else:
orderby = (order[:1] == '~' and ~sort_field) or sort_field orderby = (order[:1] == '~' and ~sort_field) or sort_field
@@ -2782,30 +2787,30 @@ class SQLFORM(FORM):
if selectable: if selectable:
headcols.append(TH(_class=ui.get('default'))) headcols.append(TH(_class=ui.get('default')))
ordermatch, marker = orderby, '' ordermatch = orderby; marker = ''
if orderby: if orderby:
# if orderby is a single column, remember to put the marker # if orderby is a single column, remember to put the marker
if isinstance(orderby, Expression): if isinstance(orderby, Expression):
if orderby.first and not orderby.second: if orderby.first and not orderby.second:
ordermatch, marker = orderby.first, '~' ordermatch = orderby.first; marker = '~'
ordermatch = marker + str(ordermatch) ordermatch = marker + str(ordermatch)
for field in columns: for field in columns:
if not field.readable: if not field.readable:
continue continue
key = str(field) key = str(field)
header = headers.get(str(field), field.label or key) header = headers.get(key, field.label or key)
if sortable and not isinstance(field, Field.Virtual): if sortable and not isinstance(field, Field.Virtual):
marker = '' marker = ''
if order: if order:
if key == order: if key == order:
key, marker = '~' + order, sorter_icons[0] key = '~' + order; marker = asc_icon
elif key == order[1:]: elif key == order[1:]:
marker = sorter_icons[1] key = 'None'; marker = desc_icon
else: else:
if key == ordermatch: if key == ordermatch:
key, marker = '~' + ordermatch, sorter_icons[0] key = '~' + ordermatch; marker = asc_icon
elif key == ordermatch[1:]: elif key == ordermatch[1:]:
marker = sorter_icons[1] marker = desc_icon
header = A(header, marker, _href=url(vars=dict( header = A(header, marker, _href=url(vars=dict(
keywords=keywords, keywords=keywords,
order=key)), cid=request.cid) order=key)), cid=request.cid)
@@ -2927,7 +2932,7 @@ class SQLFORM(FORM):
paginator.append(LI(self_link('<<', 0))) paginator.append(LI(self_link('<<', 0)))
if page > NPAGES: if page > NPAGES:
paginator.append(LI(self_link('<', page - 1))) paginator.append(LI(self_link('<', page - 1)))
pages = range(max(0, page - NPAGES), min(page + NPAGES, npages)) pages = list(range(max(0, page - NPAGES), min(page + NPAGES, npages)))
for p in pages: for p in pages:
if p == page: if p == page:
paginator.append(LI(A(p + 1, _onclick='return false'), paginator.append(LI(A(p + 1, _onclick='return false'),
@@ -3114,7 +3119,7 @@ class SQLFORM(FORM):
link = url2(vars=dict( link = url2(vars=dict(
order=request.vars.order or '', order=request.vars.order or '',
_export_type=k, _export_type=k,
keywords=keywords or '')) keywords=keywords))
export_links.append(A(T(label), _href=link, _title=title, _class='btn btn-default btn-secondary')) export_links.append(A(T(label), _href=link, _title=title, _class='btn btn-default btn-secondary'))
export_menu = \ export_menu = \
DIV(T('Export:'), _class="w2p_export_menu", *export_links) DIV(T('Export:'), _class="w2p_export_menu", *export_links)
@@ -3415,16 +3420,22 @@ class SQLTABLE(TABLE):
(components, row) = (self.components, []) (components, row) = (self.components, [])
if not sqlrows: if not sqlrows:
return return
REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD fieldlist = sqlrows.colnames_fields
fieldmap = dict(zip(sqlrows.colnames, sqlrows.fields)) fieldmap = dict(zip(sqlrows.colnames, fieldlist))
tablemap = dict(((f.tablename, f.table) if isinstance(f, Field) else (f._table._tablename, f._table) for f in fieldmap.values())) if columns:
for table in tablemap.values(): tablenames = []
pref = table._tablename + '.' for colname, field in fieldmap.items():
fieldmap.update(((pref+f.name, f) for f in table._virtual_fields)) if isinstance(field, (Field, Field.Virtual)):
fieldmap.update(((pref+f.name, f) for f in table._virtual_methods)) tablenames.append(field.tablename)
field_types = (Field, Field.Virtual, Field.Method) elif isinstance(field, Expression):
if not columns: tablenames.append(field._table._tablename)
for tablename in set(tablenames):
table = sqlrows.db[tablename]
fieldmap.update((("%s.%s" % (tablename, f.name), f) for f in table._virtual_fields + table._virtual_methods))
else:
columns = list(sqlrows.colnames) columns = list(sqlrows.colnames)
field_types = (Field, Field.Virtual, Field.Method)
header_func = { header_func = {
'fieldname:capitalize': lambda f: f.name.replace('_', ' ').title(), 'fieldname:capitalize': lambda f: f.name.replace('_', ' ').title(),
'labels': lambda f: f.label 'labels': lambda f: f.label
@@ -3697,7 +3708,7 @@ class ExporterTSV(ExportClass):
def __init__(self, rows): def __init__(self, rows):
ExportClass.__init__(self, rows) ExportClass.__init__(self, rows)
def export(self): # export TSV with rows.represent def export(self): # export TSV with field.represent
if self.rows: if self.rows:
s = StringIO() s = StringIO()
self.rows.export_to_csv_file(s, represent=True,delimiter='\t',newline='\n') self.rows.export_to_csv_file(s, represent=True,delimiter='\t',newline='\n')
@@ -3714,7 +3725,7 @@ class ExporterTSV_hidden(ExportClass):
def __init__(self, rows): def __init__(self, rows):
ExportClass.__init__(self, rows) ExportClass.__init__(self, rows)
def export(self): # export TSV with rows.represent def export(self):
if self.rows: if self.rows:
s = StringIO() s = StringIO()
self.rows.export_to_csv_file(s,delimiter='\t',newline='\n') self.rows.export_to_csv_file(s,delimiter='\t',newline='\n')
@@ -3732,7 +3743,7 @@ class ExporterCSV(ExportClass):
def __init__(self, rows): def __init__(self, rows):
ExportClass.__init__(self, rows) ExportClass.__init__(self, rows)
def export(self): # export CSV with rows.represent def export(self): # export CSV with field.represent
if self.rows: if self.rows:
s = StringIO() s = StringIO()
self.rows.export_to_csv_file(s, represent=True) self.rows.export_to_csv_file(s, represent=True)
+1 -938
View File
@@ -1,938 +1 @@
#!/usr/bin/env python from yatl.template import render, parse_template
# -*- coding: utf-8 -*-
"""
| This file is part of the web2py Web Framework
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| Author: Thadeus Burgess
| Contributors:
| - Massimo Di Pierro for creating the original gluon/template.py
| - Jonathan Lundell for extensively testing the regex on Jython.
| - Limodou (creater of uliweb) who inspired the block-element support for web2py.
Templating syntax
------------------
"""
import os
import cgi
import logging
from re import compile, sub, escape, DOTALL
from gluon._compat import StringIO, unicodeT, to_unicode, to_bytes, to_native, basestring
try:
# have web2py
from gluon.restricted import RestrictedError
from gluon.globals import current
except ImportError:
# do not have web2py
current = None
def RestrictedError(a, b, c):
logging.error(str(a) + ':' + str(b) + ':' + str(c))
return RuntimeError
class Node(object):
"""
Basic Container Object
"""
def __init__(self, value=None, pre_extend=False):
self.value = value
self.pre_extend = pre_extend
def __str__(self):
return str(self.value)
class SuperNode(Node):
def __init__(self, name='', pre_extend=False):
self.name = name
self.value = None
self.pre_extend = pre_extend
def __str__(self):
if self.value:
return str(self.value)
else:
# raise SyntaxError("Undefined parent block ``%s``. \n" % self.name + "You must define a block before referencing it.\nMake sure you have not left out an ``{{end}}`` tag." )
return ''
def __repr__(self):
return "%s->%s" % (self.name, self.value)
def output_aux(node, blocks):
# If we have a block level
# If we can override this block.
# Override block from vars.
# Else we take the default
# Else its just a string
return (blocks[node.name].output(blocks)
if node.name in blocks else
node.output(blocks)) \
if isinstance(node, BlockNode) \
else str(node)
class BlockNode(Node):
"""
Block Container.
This Node can contain other Nodes and will render in a hierarchical order
of when nodes were added.
ie::
{{ block test }}
This is default block test
{{ end }}
"""
def __init__(self, name='', pre_extend=False, delimiters=('{{', '}}')):
"""
name - Name of this Node.
"""
self.nodes = []
self.name = name
self.pre_extend = pre_extend
self.left, self.right = delimiters
def __repr__(self):
lines = ['%sblock %s%s' % (self.left, self.name, self.right)]
lines += [str(node) for node in self.nodes]
lines.append('%send%s' % (self.left, self.right))
return ''.join(lines)
def __str__(self):
"""
Get this BlockNodes content, not including child Nodes
"""
return ''.join(str(node) for node in self.nodes
if not isinstance(node, BlockNode))
def append(self, node):
"""
Adds an element to the nodes.
Args:
node: Node object or string to append.
"""
if isinstance(node, str) or isinstance(node, Node):
self.nodes.append(node)
else:
raise TypeError("Invalid type; must be instance of ``str`` or ``BlockNode``. %s" % node)
def extend(self, other):
"""
Extends the list of nodes with another BlockNode class.
Args:
other: BlockNode or Content object to extend from.
"""
if isinstance(other, BlockNode):
self.nodes.extend(other.nodes)
else:
raise TypeError(
"Invalid type; must be instance of ``BlockNode``. %s" % other)
def output(self, blocks):
"""
Merges all nodes into a single string.
Args:
blocks: Dictionary of blocks that are extending from this template.
"""
return ''.join(output_aux(node, blocks) for node in self.nodes)
class Content(BlockNode):
"""
Parent Container -- Used as the root level BlockNode.
Contains functions that operate as such.
Args:
name: Unique name for this BlockNode
"""
def __init__(self, name="ContentBlock", pre_extend=False):
self.name = name
self.nodes = []
self.blocks = {}
self.pre_extend = pre_extend
def __str__(self):
return ''.join(output_aux(node, self.blocks) for node in self.nodes)
def _insert(self, other, index=0):
"""
Inserts object at index.
"""
if isinstance(other, (str, Node)):
self.nodes.insert(index, other)
else:
raise TypeError(
"Invalid type, must be instance of ``str`` or ``Node``.")
def insert(self, other, index=0):
"""
Inserts object at index.
You may pass a list of objects and have them inserted.
"""
if isinstance(other, (list, tuple)):
# Must reverse so the order stays the same.
other.reverse()
for item in other:
self._insert(item, index)
else:
self._insert(other, index)
def append(self, node):
"""
Adds a node to list. If it is a BlockNode then we assign a block for it.
"""
if isinstance(node, (str, Node)):
self.nodes.append(node)
if isinstance(node, BlockNode):
self.blocks[node.name] = node
else:
raise TypeError("Invalid type, must be instance of ``str`` or ``BlockNode``. %s" % node)
def extend(self, other):
"""
Extends the objects list of nodes with another objects nodes
"""
if isinstance(other, BlockNode):
self.nodes.extend(other.nodes)
self.blocks.update(other.blocks)
else:
raise TypeError(
"Invalid type; must be instance of ``BlockNode``. %s" % other)
def clear_content(self):
self.nodes = []
class TemplateParser(object):
"""Parse all blocks
Args:
text: text to parse
context: context to parse in
path: folder path to templates
writer: string of writer class to use
lexers: dict of custom lexers to use.
delimiters: for example `('{{','}}')`
_super_nodes: a list of nodes to check for inclusion
this should only be set by "self.extend"
It contains a list of SuperNodes from a child
template that need to be handled.
"""
default_delimiters = ('{{', '}}')
r_tag = compile(r'(\{\{.*?\}\})', DOTALL)
r_multiline = compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', DOTALL)
# These are used for re-indentation.
# Indent + 1
re_block = compile('^(elif |else:|except:|except |finally:).*$', DOTALL)
# Indent - 1
re_unblock = compile('^(return|continue|break|raise)( .*)?$', DOTALL)
# Indent - 1
re_pass = compile('^pass( .*)?$', DOTALL)
def __init__(self, text,
name="ParserContainer",
context=dict(),
path='views/',
writer='response.write',
lexers={},
delimiters=('{{', '}}'),
_super_nodes = [],
):
# Keep a root level name.
self.name = name
# Raw text to start parsing.
self.text = text
# Writer to use (refer to the default for an example).
# This will end up as
# "%s(%s, escape=False)" % (self.writer, value)
self.writer = writer
# Dictionary of custom name lexers to use.
if isinstance(lexers, dict):
self.lexers = lexers
else:
self.lexers = {}
# Path of templates
self.path = path
# Context for templates.
self.context = context
# allow optional alternative delimiters
if delimiters != self.default_delimiters:
escaped_delimiters = (escape(delimiters[0]),
escape(delimiters[1]))
self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, DOTALL)
elif hasattr(context.get('response', None), 'delimiters'):
if (context['response'].delimiters != self.default_delimiters) and (context['response'].delimiters != None):
delimiters = context['response'].delimiters
escaped_delimiters = (
escape(delimiters[0]),
escape(delimiters[1]))
self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters,
DOTALL)
self.delimiters = delimiters
# Create a root level Content that everything will go into.
self.content = Content(name=name)
# Stack will hold our current stack of nodes.
# As we descend into a node, it will be added to the stack
# And when we leave, it will be removed from the stack.
# self.content should stay on the stack at all times.
self.stack = [self.content]
# This variable will hold a reference to every super block
# that we come across in this template.
self.super_nodes = []
# This variable will hold a reference to the child
# super nodes that need handling.
self.child_super_nodes = _super_nodes
# This variable will hold a reference to every block
# that we come across in this template
self.blocks = {}
# Begin parsing.
self.parse(text)
def to_string(self):
"""
Returns the parsed template with correct indentation.
Used to make it easier to port to python3.
"""
return self.reindent(str(self.content))
def __str__(self):
"Makes sure str works exactly the same as python 3"
return self.to_string()
def __unicode__(self):
"Makes sure str works exactly the same as python 3"
return self.to_string()
def reindent(self, text):
"""
Reindents a string of unindented python code.
"""
# Get each of our lines into an array.
lines = text.split('\n')
# Our new lines
new_lines = []
# Keeps track of how many indents we have.
# Used for when we need to drop a level of indentation
# only to reindent on the next line.
credit = 0
# Current indentation
k = 0
#################
# THINGS TO KNOW
#################
# k += 1 means indent
# k -= 1 means unindent
# credit = 1 means unindent on the next line.
for raw_line in lines:
line = raw_line.strip()
# ignore empty lines
if not line:
continue
# If we have a line that contains python code that
# should be unindented for this line of code.
# and then reindented for the next line.
if TemplateParser.re_block.match(line):
k = k + credit - 1
# We obviously can't have a negative indentation
k = max(k, 0)
# Add the indentation!
new_lines.append(' ' * (4 * k) + line)
# Bank account back to 0 again :(
credit = 0
# If we are a pass block, we obviously de-dent.
if TemplateParser.re_pass.match(line):
k -= 1
# If we are any of the following, de-dent.
# However, we should stay on the same level
# But the line right after us will be de-dented.
# So we add one credit to keep us at the level
# while moving back one indentation level.
if TemplateParser.re_unblock.match(line):
credit = 1
k -= 1
# If we are an if statement, a try, or a semi-colon we
# probably need to indent the next line.
if line.endswith(':') and not line.startswith('#'):
k += 1
# This must come before so that we can raise an error with the
# right content.
new_text = '\n'.join(new_lines)
if k > 0:
self._raise_error('missing "pass" in view', new_text)
elif k < 0:
self._raise_error('too many "pass" in view', new_text)
return new_text
def _raise_error(self, message='', text=None):
"""
Raises an error using itself as the filename and textual content.
"""
raise RestrictedError(self.name, text or self.text, message)
def _get_file_text(self, filename):
"""
Attempts to open ``filename`` and retrieve its text.
This will use self.path to search for the file.
"""
# If they didn't specify a filename, how can we find one!
if not filename.strip():
self._raise_error('Invalid template filename')
# Allow Views to include other views dynamically
context = self.context
if current and "response" not in context:
context["response"] = getattr(current, 'response', None)
# Get the filename; filename looks like ``"template.html"``.
# We need to eval to remove the quotes and get the string type.
filename = eval(filename, context)
# Allow empty filename for conditional extend and include directives.
if not filename:
return ''
# Get the path of the file on the system.
filepath = self.path and os.path.join(self.path, filename) or filename
# try to read the text.
try:
fileobj = open(filepath, 'rb')
text = fileobj.read()
fileobj.close()
except IOError:
self._raise_error('Unable to open included view file: ' + filepath)
text = to_native(text)
return text
def include(self, content, filename):
"""
Includes ``filename`` here.
"""
text = self._get_file_text(filename)
t = TemplateParser(text,
name=filename,
context=self.context,
path=self.path,
writer=self.writer,
delimiters=self.delimiters)
content.append(t.content)
def extend(self, filename):
"""
Extends `filename`. Anything not declared in a block defined by the
parent will be placed in the parent templates `{{include}}` block.
"""
# If no filename, create a dummy layout with only an {{include}}.
text = self._get_file_text(filename) or '%sinclude%s' % tuple(self.delimiters)
# Create out nodes list to send to the parent
super_nodes = []
# We want to include any non-handled nodes.
super_nodes.extend(self.child_super_nodes)
# And our nodes as well.
super_nodes.extend(self.super_nodes)
t = TemplateParser(text,
name=filename,
context=self.context,
path=self.path,
writer=self.writer,
delimiters=self.delimiters,
_super_nodes=super_nodes)
# Make a temporary buffer that is unique for parent
# template.
buf = BlockNode(
name='__include__' + filename, delimiters=self.delimiters)
pre = []
# Iterate through each of our nodes
for node in self.content.nodes:
# If a node is a block
if isinstance(node, BlockNode):
# That happens to be in the parent template
if node.name in t.content.blocks:
# Do not include it
continue
if isinstance(node, Node):
# Or if the node was before the extension
# we should not include it
if node.pre_extend:
pre.append(node)
continue
# Otherwise, it should go int the
# Parent templates {{include}} section.
buf.append(node)
else:
buf.append(node)
# Clear our current nodes. We will be replacing this with
# the parent nodes.
self.content.nodes = []
t_content = t.content
# Set our include, unique by filename
t_content.blocks['__include__' + filename] = buf
# Make sure our pre_extended nodes go first
t_content.insert(pre)
# Then we extend our blocks
t_content.extend(self.content)
# Work off the parent node.
self.content = t_content
def parse(self, text):
# Basically, r_tag.split will split the text into
# an array containing, 'non-tag', 'tag', 'non-tag', 'tag'
# so if we alternate this variable, we know
# what to look for. This is alternate to
# line.startswith("{{")
in_tag = False
extend = None
pre_extend = True
# Use a list to store everything in
# This is because later the code will "look ahead"
# for missing strings or brackets.
ij = self.r_tag.split(text)
# j = current index
# i = current item
stack = self.stack
for j in range(len(ij)):
i = ij[j]
if i:
if not stack:
self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag')
# Our current element in the stack.
top = stack[-1]
if in_tag:
line = i
# 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:
continue
# We do not want to replace the newlines in code,
# only in block comments.
def remove_newline(re_val):
# Take the entire match and replace newlines with
# escaped newlines.
return re_val.group(0).replace('\n', '\\n')
# Perform block comment escaping.
# This performs escaping ON anything
# in between """ and """
line = sub(TemplateParser.r_multiline,
remove_newline,
line)
if line.startswith('='):
# IE: {{=response.title}}
name, value = '=', line[1:].strip()
else:
v = line.split(' ', 1)
if len(v) == 1:
# Example
# {{ include }}
# {{ end }}
name = v[0]
value = ''
else:
# Example
# {{ block pie }}
# {{ include "layout.html" }}
# {{ for i in range(10): }}
name = v[0]
value = v[1]
# This will replace newlines in block comments
# with the newline character. This is so that they
# retain their formatting, but squish down to one
# line in the rendered template.
# First check if we have any custom lexers
if name in self.lexers:
# Pass the information to the lexer
# and allow it to inject in the environment
# You can define custom names such as
# '{{<<variable}}' which could potentially
# write unescaped version of the variable.
self.lexers[name](parser=self,
value=value,
top=top,
stack=stack)
elif name == '=':
# So we have a variable to insert into
# the template
buf = "\n%s(%s)" % (self.writer, value)
top.append(Node(buf, pre_extend=pre_extend))
elif name == 'block' and not value.startswith('='):
# Make a new node with name.
node = BlockNode(name=value.strip(),
pre_extend=pre_extend,
delimiters=self.delimiters)
# Append this node to our active node
top.append(node)
# Make sure to add the node to the stack.
# so anything after this gets added
# to this node. This allows us to
# "nest" nodes.
stack.append(node)
elif name == 'end' and not value.startswith('='):
# We are done with this node.
# Save an instance of it
self.blocks[top.name] = top
# Pop it.
stack.pop()
elif name == 'super' and not value.startswith('='):
# Get our correct target name
# If they just called {{super}} without a name
# attempt to assume the top blocks name.
if value:
target_node = value
else:
target_node = top.name
# Create a SuperNode instance
node = SuperNode(name=target_node,
pre_extend=pre_extend)
# Add this to our list to be taken care of
self.super_nodes.append(node)
# And put in in the tree
top.append(node)
elif name == 'include' and not value.startswith('='):
# If we know the target file to include
if value:
self.include(top, value)
# Otherwise, make a temporary include node
# That the child node will know to hook into.
else:
include_node = BlockNode(
name='__include__' + self.name,
pre_extend=pre_extend,
delimiters=self.delimiters)
top.append(include_node)
elif name == 'extend' and not value.startswith('='):
# We need to extend the following
# template.
extend = value
pre_extend = False
else:
# If we don't know where it belongs
# we just add it anyways without formatting.
if line and in_tag:
# Split on the newlines >.<
tokens = line.split('\n')
# We need to look for any instances of
# for i in range(10):
# = i
# pass
# So we can properly put a response.write() in place.
continuation = False
len_parsed = 0
for k, token in enumerate(tokens):
token = tokens[k] = token.strip()
len_parsed += len(token)
if token.startswith('='):
if token.endswith('\\'):
continuation = True
tokens[k] = "\n%s(%s" % (
self.writer, token[1:].strip())
else:
tokens[k] = "\n%s(%s)" % (
self.writer, token[1:].strip())
elif continuation:
tokens[k] += ')'
continuation = False
buf = "\n%s" % '\n'.join(tokens)
top.append(Node(buf, pre_extend=pre_extend))
else:
# It is HTML so just include it.
buf = "\n%s(%r, escape=False)" % (self.writer, i)
top.append(Node(buf, pre_extend=pre_extend))
# Remember: tag, not tag, tag, not tag
in_tag = not in_tag
# Make a list of items to remove from child
to_rm = []
# Go through each of the children nodes
for node in self.child_super_nodes:
# If we declared a block that this node wants to include
if node.name in self.blocks:
# Go ahead and include it!
node.value = self.blocks[node.name]
# Since we processed this child, we don't need to
# pass it along to the parent
to_rm.append(node)
# Remove some of the processed nodes
for node in to_rm:
# Since this is a pointer, it works beautifully.
# Sometimes I miss C-Style pointers... I want my asterisk...
self.child_super_nodes.remove(node)
# If we need to extend a template.
if extend:
self.extend(extend)
# We need this for integration with gluon
def parse_template(filename,
path='views/',
context=dict(),
lexers={},
delimiters=('{{', '}}')
):
"""
Args:
filename: can be a view filename in the views folder or an input stream
path: is the path of a views folder
context: is a dictionary of symbols used to render the template
lexers: dict of custom lexers to use
delimiters: opening and closing tags
"""
# First, if we have a str try to open the file
if isinstance(filename, basestring):
fname = os.path.join(path, filename)
try:
with open(fname, 'rb') as fp:
text = fp.read()
except IOError:
raise RestrictedError(filename, '', 'Unable to find the file')
else:
text = filename.read()
text = to_native(text)
# Use the file contents to get a parsed template and return it.
return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
def get_parsed(text):
"""
Returns the indented python code of text. Useful for unit testing.
"""
return str(TemplateParser(text))
class DummyResponse():
def __init__(self):
self.body = StringIO()
def write(self, data, escape=True):
if not escape:
self.body.write(str(data))
elif hasattr(data, 'xml') and callable(data.xml):
self.body.write(data.xml())
else:
# make it a string
if not isinstance(data, (str, unicodeT)):
data = str(data)
elif isinstance(data, unicodeT):
data = data.encode('utf8', 'xmlcharrefreplace')
data = cgi.escape(data, True).replace("'", "&#x27;")
self.body.write(data)
class NOESCAPE():
"""
A little helper to avoid escaping.
"""
def __init__(self, text):
self.text = text
def xml(self):
return self.text
# And this is a generic render function.
# Here for integration with gluon.
def render(content="hello world",
stream=None,
filename=None,
path=None,
context={},
lexers={},
delimiters=('{{', '}}'),
writer='response.write'
):
"""
Generic render function
Args:
content: default content
stream: file-like obj to read template from
filename: where to find template
path: base path for templates
context: env
lexers: custom lexers to use
delimiters: opening and closing tags
writer: where to inject the resulting stream
Example::
>>> render()
'hello world'
>>> render(content='abc')
'abc'
>>> render(content="abc'")
"abc'"
>>> render(content=''''a"'bc''')
'a"'bc'
>>> render(content='a\\nbc')
'a\\nbc'
>>> render(content='a"bcd"e')
'a"bcd"e'
>>> render(content="'''a\\nc'''")
"'''a\\nc'''"
>>> render(content="'''a\\'c'''")
"'''a\'c'''"
>>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5))
'0<br />1<br />2<br />3<br />4<br />'
>>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}'))
'0<br />1<br />2<br />3<br />4<br />'
>>> render(content="{{='''hello\\nworld'''}}")
'hello\\nworld'
>>> render(content='{{for i in range(3):\\n=i\\npass}}')
'012'
"""
# here to avoid circular Imports
try:
from gluon.globals import Response
except ImportError:
# Working standalone. Build a mock Response object.
Response = DummyResponse
# Add it to the context so we can use it.
if 'NOESCAPE' not in context:
context['NOESCAPE'] = NOESCAPE
if isinstance(content, unicodeT):
content = content.encode('utf8')
# save current response class
if context and 'response' in context:
old_response_body = context['response'].body
context['response'].body = StringIO()
else:
old_response_body = None
context['response'] = Response()
# If we don't have anything to render, why bother?
if not content and not stream and not filename:
raise SyntaxError("Must specify a stream or filename or content")
# Here for legacy purposes, probably can be reduced to
# something more simple.
close_stream = False
if not stream:
if filename:
stream = open(filename, 'rb')
close_stream = True
elif content:
stream = StringIO(to_native(content))
# Execute the template.
code = str(TemplateParser(stream.read(
), context=context, path=path, lexers=lexers, delimiters=delimiters, writer=writer))
try:
exec(code, context)
except Exception:
# for i,line in enumerate(code.split('\n')): print i,line
raise
if close_stream:
stream.close()
# Returned the rendered content.
text = context['response'].body.getvalue()
if old_response_body is not None:
context['response'].body = old_response_body
return text
+1 -3
View File
@@ -1,4 +1,3 @@
from .test_http import * from .test_http import *
from .test_contenttype import * from .test_contenttype import *
from .test_fileutils import * from .test_fileutils import *
@@ -7,7 +6,6 @@ from .test_recfile import *
from .test_storage import * from .test_storage import *
from .test_dal import * from .test_dal import *
from .test_cache import * from .test_cache import *
from .test_template import *
from .test_html import * from .test_html import *
from .test_contribs import * from .test_contribs import *
from .test_routes import * from .test_routes import *
@@ -22,9 +20,9 @@ from .test_compileapp import *
from .test_appadmin import * from .test_appadmin import *
from .test_web import * from .test_web import *
from .test_sqlhtml import * from .test_sqlhtml import *
from .test_scheduler import *
from .test_cron import * from .test_cron import *
from .test_is_url import * from .test_is_url import *
from .test_scheduler import *
if sys.version[:3] == '2.7': if sys.version[:3] == '2.7':
from .test_old_doctests import * from .test_old_doctests import *
+3 -4
View File
@@ -4,14 +4,14 @@
""" """
Unit tests for gluon.sqlhtml Unit tests for gluon.sqlhtml
""" """
from __future__ import print_function
import os import os
import sys import sys
import unittest import unittest
from gluon.compileapp import run_controller_in, run_view_in, compile_application, remove_compiled_application from gluon.compileapp import run_controller_in, run_view_in, compile_application, remove_compiled_application
from gluon.languages import translator from gluon.languages import TranslatorFactory
from gluon.storage import Storage, List from gluon.storage import Storage, List
from gluon import fileutils from gluon import fileutils
from gluon.dal import DAL, Field, Table from gluon.dal import DAL, Field, Table
@@ -50,7 +50,7 @@ class TestAppAdmin(unittest.TestCase):
request.env.remote_addr = '127.0.0.1' request.env.remote_addr = '127.0.0.1'
response = Response() response = Response()
session = Session() session = Session()
T = translator('', 'en') T = TranslatorFactory('', 'en')
session.connect(request, response) session.connect(request, response)
current.request = request current.request = request
current.response = response current.response = response
@@ -196,4 +196,3 @@ class TestAppAdmin(unittest.TestCase):
data['id'] = '1' data['id'] = '1'
request._vars = data request._vars = data
self.assertRaises(HTTP, self.run_function) self.assertRaises(HTTP, self.run_function)
+2 -2
View File
@@ -5,7 +5,7 @@
import os import os
import unittest import unittest
from gluon.globals import Request, Response, Session from gluon.globals import Request, Response, Session
from gluon.languages import translator from gluon.languages import TranslatorFactory
from gluon.dal import DAL, Field from gluon.dal import DAL, Field
from gluon.authapi import AuthAPI from gluon.authapi import AuthAPI
from gluon.storage import Storage from gluon.storage import Storage
@@ -24,7 +24,7 @@ class TestAuthAPI(unittest.TestCase):
self.request.folder = 'applications/admin' self.request.folder = 'applications/admin'
self.response = Response() self.response = Response()
self.session = Session() self.session = Session()
T = translator('', 'en') T = TranslatorFactory('', 'en')
self.session.connect(self.request, self.response) self.session.connect(self.request, self.response)
from gluon.globals import current from gluon.globals import current
self.current = current self.current = current
+1 -2
View File
@@ -120,7 +120,7 @@ class TestCache(unittest.TestCase):
s = Storage({'application': 'admin', s = Storage({'application': 'admin',
'folder': 'applications/admin'}) 'folder': 'applications/admin'})
cache = Cache(s) cache = Cache(s)
db = DAL(check_reserved=['all']) db = DAL('sqlite:memory', check_reserved=['all'])
db.define_table('t_a', Field('f_a')) db.define_table('t_a', Field('f_a'))
db.t_a.insert(f_a='test') db.t_a.insert(f_a='test')
db.commit() db.commit()
@@ -142,4 +142,3 @@ class TestCache(unittest.TestCase):
self.assertEqual(a.as_csv(), h.as_csv()) self.assertEqual(a.as_csv(), h.as_csv())
db.t_a.drop() db.t_a.drop()
db.close() db.close()
-2
View File
@@ -20,5 +20,3 @@ class TestCron(unittest.TestCase):
def test_crondance(self): def test_crondance(self):
#TODO update crondance to return something #TODO update crondance to return something
crondance(os.getcwd()) crondance(os.getcwd())
+2 -2
View File
@@ -32,7 +32,7 @@ class TestDALSubclass(unittest.TestCase):
def testSerialization(self): def testSerialization(self):
from gluon._compat import pickle from gluon._compat import pickle
db = DAL(check_reserved=['all']) db = DAL('sqlite:memory', check_reserved=['all'])
db.define_table('t_a', Field('f_a')) db.define_table('t_a', Field('f_a'))
db.t_a.insert(f_a='test') db.t_a.insert(f_a='test')
a = db(db.t_a.id > 0).select(cacheable=True) a = db(db.t_a.id > 0).select(cacheable=True)
@@ -56,7 +56,7 @@ def _prepare_exec_for_file(filename):
elif os.path.split(filename)[1] == '__init__.py': elif os.path.split(filename)[1] == '__init__.py':
filename = os.path.dirname(filename) filename = os.path.dirname(filename)
else: else:
raise 'The file provided (%s) does is not a valid Python file.' raise IOError('The file provided (%s) is not a valid Python file.')
filename = os.path.realpath(filename) filename = os.path.realpath(filename)
dirpath = filename dirpath = filename
while True: while True:
+2 -3
View File
@@ -252,11 +252,10 @@ class testResponse(unittest.TestCase):
def test_include_meta(self): def test_include_meta(self):
response = Response() response = Response()
response.meta[u'web2py'] = 'web2py' response.meta['web2py'] = 'web2py'
response.include_meta() response.include_meta()
self.assertEqual(response.body.getvalue(), '\n<meta name="web2py" content="web2py" />\n') self.assertEqual(response.body.getvalue(), '\n<meta name="web2py" content="web2py" />\n')
response = Response() response = Response()
response.meta[u'meta_dict'] = {u'tag_name':'tag_value'} response.meta['meta_dict'] = {'tag_name':'tag_value'}
response.include_meta() response.include_meta()
self.assertEqual(response.body.getvalue(), '\n<meta tag_name="tag_value" />\n') self.assertEqual(response.body.getvalue(), '\n<meta tag_name="tag_value" />\n')
+5 -5
View File
@@ -88,14 +88,14 @@ class TestTranslations(unittest.TestCase):
pass pass
def test_plain(self): def test_plain(self):
T = languages.translator(self.langpath, self.http_accept_language) T = languages.TranslatorFactory(self.langpath, self.http_accept_language)
self.assertEqual(str(T('Hello World')), self.assertEqual(str(T('Hello World')),
'Hello World') 'Hello World')
self.assertEqual(str(T('Hello World## comment')), self.assertEqual(str(T('Hello World## comment')),
'Hello World') 'Hello World')
self.assertEqual(str(T.M('**Hello World**')), self.assertEqual(str(T.M('**Hello World**')),
'<strong>Hello World</strong>') '<strong>Hello World</strong>')
# sub_tuple testing # sub_tuple testing
self.assertEqual(str(T('%s %%{shop}', 1)), self.assertEqual(str(T('%s %%{shop}', 1)),
'1 shop') '1 shop')
self.assertEqual(str(T('%s %%{shop}', 2)), self.assertEqual(str(T('%s %%{shop}', 2)),
@@ -192,7 +192,7 @@ class TestTranslations(unittest.TestCase):
'2') '2')
self.assertEqual(str(T('%i%%{?st?[0]}', 0)), self.assertEqual(str(T('%i%%{?st?[0]}', 0)),
'0') '0')
# sub_dict testing # sub_dict testing
self.assertEqual(str(T('%(key)s %%{is(key)}', dict(key=1))), self.assertEqual(str(T('%(key)s %%{is(key)}', dict(key=1))),
'1 is') '1 is')
self.assertEqual(str(T('%(key)i %%{is(key)}', dict(key=2))), self.assertEqual(str(T('%(key)i %%{is(key)}', dict(key=2))),
@@ -340,7 +340,7 @@ class TestMessages(unittest.TestCase):
pass pass
def test_decode(self): def test_decode(self):
T = languages.translator(self.langpath, self.http_accept_language) T = languages.TranslatorFactory(self.langpath, self.http_accept_language)
messages = Messages(T) messages = Messages(T)
messages.update({'email_sent':'Email sent', 'test': "ä"}) messages.update({'email_sent':'Email sent', 'test': "ä"})
self.assertEqual(to_unicode(messages.email_sent, 'utf-8'), 'Email sent') self.assertEqual(to_unicode(messages.email_sent, 'utf-8'), 'Email sent')
@@ -359,7 +359,7 @@ class TestHTMLTag(unittest.TestCase):
pass pass
def test_decode(self): def test_decode(self):
T = languages.translator(self.langpath, self.http_accept_language) T = languages.TranslatorFactory(self.langpath, self.http_accept_language)
elem = SPAN(T("Complete")) elem = SPAN(T("Complete"))
self.assertEqual(elem.flatten(), "Complete") self.assertEqual(elem.flatten(), "Complete")
elem = SPAN(T("Cannot be empty", language="ru")) elem = SPAN(T("Cannot be empty", language="ru"))
-1
View File
@@ -71,4 +71,3 @@ class TestRecfile(unittest.TestCase):
self.assertFalse(recfile.exists(filename)) self.assertFalse(recfile.exists(filename))
self.assertRaises(IOError, recfile.remove, filename) self.assertRaises(IOError, recfile.remove, filename)
self.assertRaises(IOError, recfile.open, filename, "r") self.assertRaises(IOError, recfile.open, filename, "r")
+1 -2
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Unit tests for rewrite.py routers option""" """Unit tests for rewrite.py routers option"""
from __future__ import print_function
import os import os
import unittest import unittest
import tempfile import tempfile
@@ -1555,4 +1555,3 @@ class TestRouter(unittest.TestCase):
load(rdict=router_collide) load(rdict=router_collide)
self.assertEqual(filter_url('http://welcome.com/welcome/admin/index', self.assertEqual(filter_url('http://welcome.com/welcome/admin/index',
domain='welcome', out=True), "/welcome/admin") domain='welcome', out=True), "/welcome/admin")
+4 -5
View File
@@ -11,7 +11,7 @@ import datetime
import sys import sys
from gluon.storage import Storage from gluon.storage import Storage
from gluon.languages import translator from gluon.languages import TranslatorFactory
from gluon.scheduler import JobGraph, Scheduler, CronParser from gluon.scheduler import JobGraph, Scheduler, CronParser
from gluon.dal import DAL from gluon.dal import DAL
@@ -25,7 +25,7 @@ class BaseTestScheduler(unittest.TestCase):
'folder': 'applications/welcome', 'folder': 'applications/welcome',
'controller': 'default'}) 'controller': 'default'})
current.request = s current.request = s
T = translator('', 'en') T = TranslatorFactory('', 'en')
current.T = T current.T = T
self.db = DAL('sqlite://dummy2.db', check_reserved=['all']) self.db = DAL('sqlite://dummy2.db', check_reserved=['all'])
@@ -551,12 +551,12 @@ class TestsForSchedulerAPIs(BaseTestScheduler):
def isnotqueued(result): def isnotqueued(result):
self.assertEqual(result.id, None) self.assertEqual(result.id, None)
self.assertEqual(result.uuid, None) self.assertEqual(result.uuid, None)
self.assertEqual(len(result.errors.keys()) > 0, True) self.assertEqual(len(list(result.errors.keys())) > 0, True)
def isqueued(result): def isqueued(result):
self.assertNotEqual(result.id, None) self.assertNotEqual(result.id, None)
self.assertNotEqual(result.uuid, None) self.assertNotEqual(result.uuid, None)
self.assertEqual(len(result.errors.keys()), 0) self.assertEqual(len(list(result.errors.keys())), 0)
s = Scheduler(self.db) s = Scheduler(self.db)
fname = 'foo' fname = 'foo'
@@ -629,7 +629,6 @@ sched_dal = DAL('sqlite://%s' % db_dal, folder=os.path.dirname(db_dal))
sched = Scheduler(sched_dal, max_empty_runs=15, migrate=False, heartbeat=1) sched = Scheduler(sched_dal, max_empty_runs=15, migrate=False, heartbeat=1)
def termination(): def termination():
sched.terminate() sched.terminate()
sched_dal.commit()
""" """
with open(fdest, 'w') as q: with open(fdest, 'w') as q:
q.write(initlines) q.write(initlines)
+2 -2
View File
@@ -13,7 +13,7 @@ import decimal
from gluon.serializers import * from gluon.serializers import *
from gluon.storage import Storage from gluon.storage import Storage
# careful with the import path 'cause of isinstance() checks # careful with the import path 'cause of isinstance() checks
from gluon.languages import translator from gluon.languages import TranslatorFactory
from gluon.html import SPAN from gluon.html import SPAN
@@ -46,7 +46,7 @@ class TestSerializers(unittest.TestCase):
obj = {'a': decimal.Decimal('4.312312312312')} obj = {'a': decimal.Decimal('4.312312312312')}
self.assertEqual(json(obj), u'{"a": "4.312312312312"}') self.assertEqual(json(obj), u'{"a": "4.312312312312"}')
# lazyT translated # lazyT translated
T = translator('', 'en') T = TranslatorFactory('', 'en')
lazy_translation = T('abc') lazy_translation = T('abc')
self.assertEqual(json(lazy_translation), u'"abc"') self.assertEqual(json(lazy_translation), u'"abc"')
# html helpers are xml()ed before too # html helpers are xml()ed before too
+3 -3
View File
@@ -18,7 +18,7 @@ from pydal.objects import Table
from gluon.tools import Auth, Mail from gluon.tools import Auth, Mail
from gluon.globals import Request, Response, Session from gluon.globals import Request, Response, Session
from gluon.storage import Storage from gluon.storage import Storage
from gluon.languages import translator from gluon.languages import TranslatorFactory
from gluon.http import HTTP from gluon.http import HTTP
from gluon.validators import * from gluon.validators import *
@@ -254,7 +254,7 @@ class TestSQLFORM(unittest.TestCase):
request.folder = 'applications/admin' request.folder = 'applications/admin'
response = Response() response = Response()
session = Session() session = Session()
T = translator('', 'en') T = TranslatorFactory('', 'en')
session.connect(request, response) session.connect(request, response)
from gluon.globals import current from gluon.globals import current
current.request = request current.request = request
@@ -355,7 +355,7 @@ class TestSQLTABLE(unittest.TestCase):
request.folder = 'applications/admin' request.folder = 'applications/admin'
response = Response() response = Response()
session = Session() session = Session()
T = translator('', 'en') T = TranslatorFactory('', 'en')
session.connect(request, response) session.connect(request, response)
from gluon.globals import current from gluon.globals import current
current.request = request current.request = request
-135
View File
@@ -1,135 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit tests for gluon.template
"""
import unittest
from gluon import template
from gluon.template import render
class TestTemplate(unittest.TestCase):
def testRun(self):
self.assertEqual(render(content='{{for i in range(n):}}{{=i}}{{pass}}',
context=dict(n=3)), '012')
self.assertEqual(render(content='{{if n>2:}}ok{{pass}}',
context=dict(n=3)), 'ok')
self.assertEqual(
render(content='{{try:}}{{n/0}}{{except:}}fail{{pass}}',
context=dict(n=3)), 'fail')
self.assertEqual(render(content='{{="<&>"}}'), '&lt;&amp;&gt;')
self.assertEqual(render(content='"abc"'), '"abc"')
self.assertEqual(render(content='"a\'bc"'), '"a\'bc"')
self.assertEqual(render(content='"a\"bc"'), '"a\"bc"')
self.assertEqual(render(content=r'''"a\"bc"'''), r'"a\"bc"')
self.assertEqual(render(content=r'''"""abc\""""'''), r'"""abc\""""')
def testEqualWrite(self):
"test generation of response.write from ="
self.assertEqual(render(content='{{=2+2}}'), '4')
self.assertEqual(render(content='{{="abc"}}'), 'abc')
# whitespace is stripped
self.assertEqual(render(content='{{ ="abc"}}'), 'abc')
self.assertEqual(render(content='{{ ="abc" }}'), 'abc')
self.assertEqual(render(content='{{pass\n="abc" }}'), 'abc')
# = recognized only at the beginning of a physical line
self.assertEqual(render(
content='{{xyz = "xyz"\n="abc"\n="def"\n=xyz }}'), 'abcdefxyz')
# = in python blocks
self.assertEqual(render(content='{{if True:\n="abc"\npass }}'), 'abc')
self.assertEqual(
render(content='{{if True:\n="abc"\npass\n="def" }}'), 'abcdef')
self.assertEqual(
render(content='{{if False:\n="abc"\npass\n="def" }}'), 'def')
self.assertEqual(render(
content='{{if True:\n="abc"\nelse:\n="def"\npass }}'), 'abc')
self.assertEqual(render(
content='{{if False:\n="abc"\nelse:\n="def"\npass }}'), 'def')
# codeblock-leading = handles internal newlines, escaped or not
self.assertEqual(render(content='{{=list((1,2,3))}}'), '[1, 2, 3]')
self.assertEqual(render(content='{{=list((1,2,\\\n3))}}'), '[1, 2, 3]')
self.assertEqual(render(content='{{=list((1,2,\n3))}}'), '[1, 2, 3]')
# ...but that means no more = operators in the codeblock
self.assertRaises(SyntaxError, render, content='{{="abc"\n="def" }}')
# = embedded in codeblock won't handle newlines in its argument
self.assertEqual(
render(content='{{pass\n=list((1,2,\\\n3))}}'), '[1, 2, 3]')
self.assertRaises(
SyntaxError, render, content='{{pass\n=list((1,2,\n3))}}')
def testWithDummyFileSystem(self):
from os.path import join as pjoin
import contextlib
from gluon._compat import StringIO
from gluon.restricted import RestrictedError
@contextlib.contextmanager
def monkey_patch(module, fn_name, patch):
try:
unpatch = getattr(module, fn_name)
except AttributeError:
unpatch = None
setattr(module, fn_name, patch)
try:
yield
finally:
if unpatch is None:
delattr(module, fn_name)
else:
setattr(module, fn_name, unpatch)
def dummy_open(path, mode):
if path == pjoin('views', 'layout.html'):
return StringIO("{{block left_sidebar}}left{{end}}"
"{{include}}"
"{{block right_sidebar}}right{{end}}")
elif path == pjoin('views', 'layoutbrackets.html'):
return StringIO("[[block left_sidebar]]left[[end]]"
"[[include]]"
"[[block right_sidebar]]right[[end]]")
elif path == pjoin('views', 'default', 'index.html'):
return StringIO("{{extend 'layout.html'}}"
"{{block left_sidebar}}{{super}} {{end}}"
"to"
"{{block right_sidebar}} {{super}}{{end}}")
elif path == pjoin('views', 'default', 'indexbrackets.html'):
return StringIO("[[extend 'layoutbrackets.html']]"
"[[block left_sidebar]][[super]] [[end]]"
"to"
"[[block right_sidebar]] [[super]][[end]]")
elif path == pjoin('views', 'default', 'missing.html'):
return StringIO("{{extend 'wut'}}"
"{{block left_sidebar}}{{super}} {{end}}"
"to"
"{{block right_sidebar}} {{super}}{{end}}")
elif path == pjoin('views', 'default', 'noescape.html'):
return StringIO("""{{=NOESCAPE('<script></script>')}}""")
raise IOError
with monkey_patch(template, 'open', dummy_open):
self.assertEqual(
render(filename=pjoin('views', 'default', 'index.html'),
path='views'),
'left to right')
self.assertEqual(
render(filename=pjoin('views', 'default', 'indexbrackets.html'),
path='views', delimiters=('[[', ']]')),
'left to right')
self.assertRaises(
RestrictedError,
render,
filename=pjoin('views', 'default', 'missing.html'),
path='views')
response = template.DummyResponse()
response.delimiters = ('[[', ']]')
self.assertEqual(
render(filename=pjoin('views', 'default', 'indexbrackets.html'),
path='views', context={'response': response}),
'left to right')
self.assertEqual(
render(filename=pjoin('views', 'default', 'noescape.html'),
context={'NOESCAPE': template.NOESCAPE}),
'<script></script>')
+6 -14
View File
@@ -18,10 +18,10 @@ from gluon.dal import DAL, Field
from pydal.objects import Table from pydal.objects import Table
from gluon import tools from gluon import tools
from gluon.tools import Auth, Mail, Recaptcha2, prettydate, Expose from gluon.tools import Auth, Mail, Recaptcha2, prettydate, Expose
from gluon._compat import PY2 from gluon._compat import PY2, to_bytes
from gluon.globals import Request, Response, Session from gluon.globals import Request, Response, Session
from gluon.storage import Storage from gluon.storage import Storage
from gluon.languages import translator from gluon.languages import TranslatorFactory
from gluon.http import HTTP from gluon.http import HTTP
from gluon import SPAN, H3, TABLE, TR, TD, A, URL, current from gluon import SPAN, H3, TABLE, TR, TD, A, URL, current
@@ -193,7 +193,7 @@ class TestMail(unittest.TestCase):
message = TestMail.DummySMTP.inbox.pop() message = TestMail.DummySMTP.inbox.pop()
attachment = message.parsed_payload.get_payload(1).get_payload(decode=True) attachment = message.parsed_payload.get_payload(1).get_payload(decode=True)
with open(module_file, 'rb') as mf: with open(module_file, 'rb') as mf:
self.assertEqual(attachment.decode('utf-8'), mf.read().decode('utf-8')) self.assertEqual(to_bytes(attachment), to_bytes(mf.read()))
# Test missing attachment name error # Test missing attachment name error
stream = open(module_file) stream = open(module_file)
self.assertRaises(Exception, lambda *args, **kwargs: Mail.Attachment(*args, **kwargs), stream) self.assertRaises(Exception, lambda *args, **kwargs: Mail.Attachment(*args, **kwargs), stream)
@@ -208,13 +208,6 @@ class TestMail(unittest.TestCase):
self.assertTrue('Content-Id: <trololo>' in message.payload) self.assertTrue('Content-Id: <trololo>' in message.payload)
# class TestRecaptcha2(unittest.TestCase):
# def test_Recaptcha2(self):
# from html import FORM
# form = FORM(Recaptcha2(public_key='public_key', private_key='private_key'))
# rtn = '<form action="#" enctype="multipart/form-data" method="post"><div><script async="" defer="" src="https://www.google.com/recaptcha/api.js"></script><div class="g-recaptcha" data-sitekey="public_key"></div><noscript>\n<div style="width: 302px; height: 352px;">\n<div style="width: 302px; height: 352px; position: relative;">\n <div style="width: 302px; height: 352px; position: absolute;">\n <iframe src="https://www.google.com/recaptcha/api/fallback?k=public_key"\n frameborder="0" scrolling="no"\n style="width: 302px; height:352px; border-style: none;">\n </iframe>\n </div>\n <div style="width: 250px; height: 80px; position: absolute; border-style: none;\n bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">\n <textarea id="g-recaptcha-response" name="g-recaptcha-response"\n class="g-recaptcha-response"\n style="width: 250px; height: 80px; border: 1px solid #c1c1c1;\n margin: 0px; padding: 0px; resize: none;" value="">\n </textarea>\n </div>\n</div>\n</div></noscript></div></form>'
# self.assertEqual(form.xml(), rtn)
# TODO: class TestAuthJWT(unittest.TestCase): # TODO: class TestAuthJWT(unittest.TestCase):
class TestAuthJWT(unittest.TestCase): class TestAuthJWT(unittest.TestCase):
def setUp(self): def setUp(self):
@@ -278,7 +271,7 @@ class TestAuthJWT(unittest.TestCase):
# request.folder = 'applications/admin' # request.folder = 'applications/admin'
# response = Response() # response = Response()
# session = Session() # session = Session()
# T = translator('', 'en') # T = TranslatorFactory('', 'en')
# session.connect(request, response) # session.connect(request, response)
# from gluon.globals import current # from gluon.globals import current
# current.request = request # current.request = request
@@ -441,7 +434,7 @@ class TestAuthJWT(unittest.TestCase):
# # request.folder = 'applications/admin' # # request.folder = 'applications/admin'
# # response = Response() # # response = Response()
# # session = Session() # # session = Session()
# # T = translator('', 'en') # # T = TranslatorFactory('', 'en')
# # session.connect(request, response) # # session.connect(request, response)
# # from gluon.globals import current # # from gluon.globals import current
# # current.request = request # # current.request = request
@@ -502,7 +495,7 @@ class TestAuth(unittest.TestCase):
self.request.folder = 'applications/admin' self.request.folder = 'applications/admin'
self.response = Response() self.response = Response()
self.session = Session() self.session = Session()
T = translator('', 'en') T = TranslatorFactory('', 'en')
self.session.connect(self.request, self.response) self.session.connect(self.request, self.response)
from gluon.globals import current from gluon.globals import current
self.current = current self.current = current
@@ -1387,4 +1380,3 @@ class TestExpose(unittest.TestCase):
def test_not_authorized(self): def test_not_authorized(self):
with self.assertRaises(HTTP): with self.assertRaises(HTTP):
self.make_expose(base='inside', show='link_to_file3') self.make_expose(base='inside', show='link_to_file3')
+9 -2
View File
@@ -20,8 +20,7 @@ class TestValidators(unittest.TestCase):
def test_MISC(self): def test_MISC(self):
""" Test miscelaneous utility functions and some general behavior guarantees """ """ Test miscelaneous utility functions and some general behavior guarantees """
from gluon.validators import translate, options_sorter, Validator, UTC from gluon.validators import options_sorter, Validator, UTC
self.assertEqual(translate(None), None)
self.assertEqual(options_sorter(('a', 'a'), ('a', 'a')), -1) self.assertEqual(options_sorter(('a', 'a'), ('a', 'a')), -1)
self.assertEqual(options_sorter(('A', 'A'), ('a', 'a')), -1) self.assertEqual(options_sorter(('A', 'A'), ('a', 'a')), -1)
self.assertEqual(options_sorter(('b', 'b'), ('a', 'a')), 1) self.assertEqual(options_sorter(('b', 'b'), ('a', 'a')), 1)
@@ -943,6 +942,14 @@ class TestValidators(unittest.TestCase):
'May not include any lowercase letters', 'May not include any lowercase letters',
'May not include any numbers'])) 'May not include any numbers']))
) )
rtn = IS_STRONG(special=0, es=True)('Abcde1!')
self.assertEqual(rtn,
('Abcde1!',
'|'.join(['Minimum length is 8',
'May not contain any of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|']))
)
rtn = IS_STRONG(upper=False, number=False, special=False, es=True)('Abcde1!')
self.assertEqual(rtn, ('Abcde1!', 'Minimum length is 8'))
def test_IS_IMAGE(self): def test_IS_IMAGE(self):
class DummyImageFile(object): class DummyImageFile(object):
+1 -1
View File
@@ -3,7 +3,7 @@
""" """
Unit tests for running web2py Unit tests for running web2py
""" """
from __future__ import print_function
import sys import sys
import os import os
import unittest import unittest
+32 -32
View File
@@ -15,8 +15,9 @@ from functools import reduce
from gluon._compat import pickle, thread, urllib2, Cookie, StringIO, urlencode from gluon._compat import pickle, thread, urllib2, Cookie, StringIO, urlencode
from gluon._compat import configparser, MIMEBase, MIMEMultipart, MIMEText, Header from gluon._compat import configparser, MIMEBase, MIMEMultipart, MIMEText, Header
from gluon._compat import Encoders, Charset, long, urllib_quote, iteritems from gluon._compat import Encoders, Charset, long, urllib_quote, iteritems
from gluon._compat import to_bytes, to_native, add_charset from gluon._compat import to_bytes, to_native, add_charset, string_types
from gluon._compat import charset_QP, basestring, unicodeT, to_unicode from gluon._compat import charset_QP, basestring, unicodeT, to_unicode
from gluon._compat import urllib2, urlopen
import datetime import datetime
import logging import logging
import sys import sys
@@ -902,13 +903,13 @@ class Recaptcha2(DIV):
'secret': self.private_key, 'secret': self.private_key,
'remoteip': remoteip, 'remoteip': remoteip,
'response': recaptcha_response_field, 'response': recaptcha_response_field,
}) }).encode('utf-8')
request = urllib2.Request( request = urllib2.Request(
url=self.VERIFY_SERVER, url=self.VERIFY_SERVER,
data=to_bytes(params), data=to_bytes(params),
headers={'Content-type': 'application/x-www-form-urlencoded', headers={'Content-type': 'application/x-www-form-urlencoded',
'User-agent': 'reCAPTCHA Python'}) 'User-agent': 'reCAPTCHA Python'})
httpresp = urllib2.urlopen(request) httpresp = urlopen(request)
content = httpresp.read() content = httpresp.read()
httpresp.close() httpresp.close()
try: try:
@@ -1037,7 +1038,7 @@ class AuthJWT(object):
Example: Example:
def mybefore_authorization(tokend): def mybefore_authorization(tokend):
if not tokend['my_name_is'] == 'bond,james bond': if not tokend['my_name_is'] == 'bond,james bond':
raise HTTP(400, u'Invalid JWT my_name_is claim') raise HTTP(400, 'Invalid JWT my_name_is claim')
- max_header_length: check max length to avoid load()ing unusually large tokens (could mean crafted, e.g. in a DDoS.) - max_header_length: check max length to avoid load()ing unusually large tokens (could mean crafted, e.g. in a DDoS.)
Basic Usage: Basic Usage:
@@ -1161,7 +1162,7 @@ class AuthJWT(object):
b64h, b64b = body.split(b'.', 1) b64h, b64b = body.split(b'.', 1)
if b64h != self.cached_b64h: if b64h != self.cached_b64h:
# header not the same # header not the same
raise HTTP(400, u'Invalid JWT Header') raise HTTP(400, 'Invalid JWT Header')
secret = self.secret_key secret = self.secret_key
tokend = serializers.loads_json(to_native(self.jwt_b64d(b64b))) tokend = serializers.loads_json(to_native(self.jwt_b64d(b64b)))
if self.salt: if self.salt:
@@ -1172,11 +1173,11 @@ class AuthJWT(object):
secret = to_bytes(secret, 'ascii', 'ignore') secret = to_bytes(secret, 'ascii', 'ignore')
if not self.verify_signature(body, sig, secret): if not self.verify_signature(body, sig, secret):
# signature verification failed # signature verification failed
raise HTTP(400, u'Token signature is invalid') raise HTTP(400, 'Token signature is invalid')
if self.verify_expiration: if self.verify_expiration:
now = time.mktime(datetime.datetime.utcnow().timetuple()) now = time.mktime(datetime.datetime.utcnow().timetuple())
if tokend['exp'] + self.leeway < now: if tokend['exp'] + self.leeway < now:
raise HTTP(400, u'Token is expired') raise HTTP(400, 'Token is expired')
if callable(self.before_authorization): if callable(self.before_authorization):
self.before_authorization(tokend) self.before_authorization(tokend)
return tokend return tokend
@@ -1209,11 +1210,11 @@ class AuthJWT(object):
orig_exp = orig_payload['exp'] orig_exp = orig_payload['exp']
if orig_exp + self.leeway < now: if orig_exp + self.leeway < now:
# token already expired, can't be used for refresh # token already expired, can't be used for refresh
raise HTTP(400, u'Token already expired') raise HTTP(400, 'Token already expired')
orig_iat = orig_payload.get('orig_iat') or orig_payload['iat'] orig_iat = orig_payload.get('orig_iat') or orig_payload['iat']
if orig_iat + self.refresh_expiration_delta < now: if orig_iat + self.refresh_expiration_delta < now:
# refreshed too long ago # refreshed too long ago
raise HTTP(400, u'Token issued too long ago') raise HTTP(400, 'Token issued too long ago')
expires = now + self.expiration expires = now + self.expiration
orig_payload.update( orig_payload.update(
orig_iat=orig_iat, orig_iat=orig_iat,
@@ -1259,7 +1260,7 @@ class AuthJWT(object):
pass pass
if token: if token:
if not self.allow_refresh: if not self.allow_refresh:
raise HTTP(403, u'Refreshing token is not allowed') raise HTTP(403, 'Refreshing token is not allowed')
tokend = self.load_token(token) tokend = self.load_token(token)
# verification can fail here # verification can fail here
refreshed = self.refresh_token(tokend) refreshed = self.refresh_token(tokend)
@@ -1277,9 +1278,9 @@ class AuthJWT(object):
ret = {'token': self.generate_token(payload)} ret = {'token': self.generate_token(payload)}
elif ret is None: elif ret is None:
raise HTTP(401, raise HTTP(401,
u'Not Authorized - need to be logged in, to pass a token ' 'Not Authorized - need to be logged in, to pass a token '
u'for refresh or username and password for login', 'for refresh or username and password for login',
**{'WWW-Authenticate': u'JWT realm="%s"' % self.realm}) **{'WWW-Authenticate': 'JWT realm="%s"' % self.realm})
response.headers['Content-Type'] = 'application/json' response.headers['Content-Type'] = 'application/json'
return serializers.json(ret) return serializers.json(ret)
@@ -1303,9 +1304,9 @@ class AuthJWT(object):
if token_in_header: if token_in_header:
parts = token_in_header.split() parts = token_in_header.split()
if parts[0].lower() != self.header_prefix.lower(): if parts[0].lower() != self.header_prefix.lower():
raise HTTP(400, u'Invalid JWT header') raise HTTP(400, 'Invalid JWT header')
elif len(parts) == 1: elif len(parts) == 1:
raise HTTP(400, u'Invalid JWT header, missing token') raise HTTP(400, 'Invalid JWT header, missing token')
elif len(parts) > 2: elif len(parts) > 2:
raise HTTP(400, 'Invalid JWT header, token contains spaces') raise HTTP(400, 'Invalid JWT header, token contains spaces')
token = parts[1] token = parts[1]
@@ -2243,11 +2244,11 @@ class Auth(AuthAPI):
if basic_auth_realm: if basic_auth_realm:
if callable(basic_auth_realm): if callable(basic_auth_realm):
basic_auth_realm = basic_auth_realm() basic_auth_realm = basic_auth_realm()
elif isinstance(basic_auth_realm, (unicode, str)): elif isinstance(basic_auth_realm, string_types):
basic_realm = unicode(basic_auth_realm) # Warning python 3.5 does not have method unicod basic_realm = to_unicode(basic_auth_realm)
elif basic_auth_realm is True: elif basic_auth_realm is True:
basic_realm = u'' + current.request.application basic_realm = '' + current.request.application
http_401 = HTTP(401, u'Not Authorized', **{'WWW-Authenticate': u'Basic realm="' + basic_realm + '"'}) http_401 = HTTP(401, 'Not Authorized', **{'WWW-Authenticate': 'Basic realm="' + basic_realm + '"'})
if not basic or not basic[:6].lower() == 'basic ': if not basic or not basic[:6].lower() == 'basic ':
if basic_auth_realm: if basic_auth_realm:
raise http_401 raise http_401
@@ -2260,9 +2261,9 @@ class Auth(AuthAPI):
def _get_login_settings(self): def _get_login_settings(self):
table_user = self.table_user() table_user = self.table_user()
userfield = self.settings.login_userfield or 'username' \ userfield = self.settings.login_userfield or ('username' \
if self.settings.login_userfield or 'username' \ if self.settings.login_userfield or 'username' \
in table_user.fields else 'email' in table_user.fields else 'email')
passfield = self.settings.password_field passfield = self.settings.password_field
return Storage({'table_user': table_user, return Storage({'table_user': table_user,
'userfield': userfield, 'userfield': userfield,
@@ -2637,9 +2638,9 @@ class Auth(AuthAPI):
# invalid login # invalid login
session.flash = specific_error if self.settings.login_specify_error else self.messages.invalid_login session.flash = specific_error if self.settings.login_specify_error else self.messages.invalid_login
callback(onfail, None) callback(onfail, None)
redirect( if 'password' in request.post_vars:
self.url(args=request.args, vars=request.get_vars), del request.post_vars['password']
client_side=settings.client_side) redirect(self.url(args=request.args, vars=request.vars),client_side=settings.client_side)
else: # use a central authentication server else: # use a central authentication server
cas = settings.login_form cas = settings.login_form
@@ -3574,7 +3575,7 @@ class Auth(AuthAPI):
requires = table_user[passfield].requires requires = table_user[passfield].requires
if not isinstance(requires, (list, tuple)): if not isinstance(requires, (list, tuple)):
requires = [requires] requires = [requires]
requires = list(filter(lambda t: isinstance(t, CRYPT), requires)) requires = [t for t in requires if isinstance(t, CRYPT)]
if requires: if requires:
requires[0] = CRYPT(**requires[0].__dict__) # Copy the existing CRYPT attributes requires[0] = CRYPT(**requires[0].__dict__) # Copy the existing CRYPT attributes
requires[0].min_length = 0 # But do not enforce minimum length for the old password requires[0].min_length = 0 # But do not enforce minimum length for the old password
@@ -4614,7 +4615,6 @@ class Crud(object): # pragma: no cover
results = None results = None
return form, results return form, results
urllib2.install_opener(urllib2.build_opener(urllib2.HTTPCookieProcessor())) urllib2.install_opener(urllib2.build_opener(urllib2.HTTPCookieProcessor()))
@@ -4632,7 +4632,7 @@ def fetch(url, data=None, headers=None,
from google.appengine.api import urlfetch from google.appengine.api import urlfetch
except ImportError: except ImportError:
req = urllib2.Request(url, data, headers) req = urllib2.Request(url, data, headers)
html = urllib2.urlopen(req).read() html = urlopen(req).read()
else: else:
method = ((data is None) and urlfetch.GET) or urlfetch.POST method = ((data is None) and urlfetch.GET) or urlfetch.POST
while url is not None: while url is not None:
@@ -5000,7 +5000,7 @@ class Service(object):
elif r and not isinstance(r, types.GeneratorType) and isinstance(r[0], (dict, Storage)): elif r and not isinstance(r, types.GeneratorType) and isinstance(r[0], (dict, Storage)):
import csv import csv
writer = csv.writer(s) writer = csv.writer(s)
writer.writerow(r[0].keys()) writer.writerow(list(r[0].keys()))
for line in r: for line in r:
writer.writerow([none_exception(v) writer.writerow([none_exception(v)
for v in line.values()]) for v in line.values()])
@@ -5217,7 +5217,7 @@ class Service(object):
def serve_xmlrpc(self): def serve_xmlrpc(self):
request = current.request request = current.request
response = current.response response = current.response
services = self.xmlrpc_procedures.values() services = list(self.xmlrpc_procedures.values())
return response.xmlrpc(request, services) return response.xmlrpc(request, services)
def serve_amfrpc(self, version=0): def serve_amfrpc(self, version=0):
@@ -5572,7 +5572,7 @@ class PluginManager(object):
return self.__dict__[key] return self.__dict__[key]
def keys(self): def keys(self):
return self.__dict__.keys() return list(self.__dict__.keys())
def __contains__(self, key): def __contains__(self, key):
return key in self.__dict__ return key in self.__dict__
@@ -5818,7 +5818,7 @@ class Wiki(object):
settings.templates = templates settings.templates = templates
settings.controller = controller settings.controller = controller
settings.function = function settings.function = function
settings.groups = auth.user_groups.values() \ settings.groups = list(auth.user_groups.values()) \
if groups is None else groups if groups is None else groups
db = auth.db db = auth.db
@@ -5914,7 +5914,7 @@ class Wiki(object):
if (auth.user and if (auth.user and
check_credentials(current.request, gae_login=False) and check_credentials(current.request, gae_login=False) and
'wiki_editor' not in auth.user_groups.values() and 'wiki_editor' not in auth.user_groups.values() and
self.settings.groups == auth.user_groups.values()): self.settings.groups == list(auth.user_groups.values())):
group = db.auth_group(role='wiki_editor') group = db.auth_group(role='wiki_editor')
gid = group.id if group else db.auth_group.insert( gid = group.id if group else db.auth_group.insert(
role='wiki_editor') role='wiki_editor')
-25
View File
@@ -439,31 +439,6 @@ def getipaddrinfo(host):
return [] return []
def local_html_escape(data, quote=False):
"""
Works with bytes.
Replace special characters "&", "<" and ">" to HTML-safe sequences.
If the optional flag quote is true (the default), the quotation mark
characters, both double quote (") and single quote (') characters are also
translated.
"""
if PY2:
import cgi
data = cgi.escape(data, quote)
return data.replace("'", "&#x27;") if quote else data
else:
import html
if isinstance(data, str):
return html.escape(data, quote=quote)
data = data.replace(b"&", b"&amp;") # Must be done first!
data = data.replace(b"<", b"&lt;")
data = data.replace(b">", b"&gt;")
if quote:
data = data.replace(b'"', b"&quot;")
data = data.replace(b'\'', b"&#x27;")
return data
def unlocalised_http_header_date(data): def unlocalised_http_header_date(data):
""" """
Converts input datetime to format defined by RFC 7231, section 7.1.1.1 Converts input datetime to format defined by RFC 7231, section 7.1.1.1
+115 -116
View File
@@ -12,25 +12,27 @@ Validators
""" """
import os import os
import re import re
import math
import datetime import datetime
import time import time
import cgi import cgi
import json import json
import urllib
import struct import struct
import decimal import decimal
import unicodedata import unicodedata
import encodings.idna
from gluon._compat import StringIO, integer_types, basestring, unicodeT, urllib_unquote, unichr, to_bytes, PY2, \ from pydal._compat import StringIO, integer_types, basestring, unicodeT, urllib_unquote, \
to_unicode, to_native, string_types, urlparse unichr, to_bytes, PY2, to_unicode, to_native, string_types, urlparse
from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE from pydal.objects import Field, FieldVirtual, FieldMethod, Table
from pydal.objects import Field, FieldVirtual, FieldMethod
from functools import reduce from functools import reduce
from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE
from gluon._compat import ipaddress
regex_isint = re.compile('^[+-]?\d+$') regex_isint = re.compile('^[+-]?\d+$')
JSONErrors = (NameError, TypeError, ValueError, AttributeError, JSONErrors = (NameError, TypeError, ValueError, AttributeError, KeyError)
KeyError)
__all__ = [ __all__ = [
'ANY_OF', 'ANY_OF',
@@ -71,25 +73,19 @@ __all__ = [
'IS_JSON', 'IS_JSON',
] ]
try:
from gluon.globals import current
have_current = True
except ImportError:
have_current = False
def translate(text):
if text is None:
return None
elif isinstance(text, (str, unicodeT)) and have_current:
if hasattr(current, 'T'):
return str(current.T(text))
return str(text)
def options_sorter(x, y): def options_sorter(x, y):
return (str(x[1]).upper() > str(y[1]).upper() and 1) or -1 return (str(x[1]).upper() > str(y[1]).upper() and 1) or -1
def translator(text):
if text is None:
return None
text = Validator.translator(text)
return str(text)
class ValidationError(Exception):
def __init__(self, error):
self.error = error
class Validator(object): class Validator(object):
""" """
@@ -129,6 +125,8 @@ class Validator(object):
Notice that default error messages are not translated. Notice that default error messages are not translated.
""" """
translator = staticmethod(lambda text: text)
def formatter(self, value): def formatter(self, value):
""" """
For some validators returns a formatted version (matching the validator) For some validators returns a formatted version (matching the validator)
@@ -136,9 +134,15 @@ class Validator(object):
""" """
return value return value
def __call__(self, value): def validate(self, value):
raise NotImplementedError raise NotImplementedError
def __call__(self, value):
try:
return self.validate(value), None
except ValidationError as e:
return value, e.message
class IS_MATCH(Validator): class IS_MATCH(Validator):
""" """
@@ -211,7 +215,7 @@ class IS_MATCH(Validator):
match = self.regex.search(value.encode('utf8')) match = self.regex.search(value.encode('utf8'))
if match is not None: if match is not None:
return (self.extract and match.group() or value, None) return (self.extract and match.group() or value, None)
return (value, translate(self.error_message)) return (value, translator(self.error_message))
class IS_EQUAL_TO(Validator): class IS_EQUAL_TO(Validator):
@@ -240,7 +244,7 @@ class IS_EQUAL_TO(Validator):
def __call__(self, value): def __call__(self, value):
if value == self.expression: if value == self.expression:
return (value, None) return (value, None)
return (value, translate(self.error_message)) return (value, translator(self.error_message))
class IS_EXPR(Validator): class IS_EXPR(Validator):
@@ -274,7 +278,7 @@ class IS_EXPR(Validator):
exec('__ret__=' + self.expression, self.environment) exec('__ret__=' + self.expression, self.environment)
if self.environment['__ret__']: if self.environment['__ret__']:
return (value, None) return (value, None)
return (value, translate(self.error_message)) return (value, translator(self.error_message))
class IS_LENGTH(Validator): class IS_LENGTH(Validator):
@@ -353,8 +357,8 @@ class IS_LENGTH(Validator):
return (value, None) return (value, None)
elif self.minsize <= len(str(value)) <= self.maxsize: elif self.minsize <= len(str(value)) <= self.maxsize:
return (str(value), None) return (str(value), None)
return (value, translate(self.error_message) return (value, translator(self.error_message) %
% dict(min=self.minsize, max=self.maxsize)) dict(min=self.minsize, max=self.maxsize))
class IS_JSON(Validator): class IS_JSON(Validator):
@@ -384,7 +388,7 @@ class IS_JSON(Validator):
else: else:
return (json.loads(value), None) return (json.loads(value), None)
except JSONErrors: except JSONErrors:
return (value, translate(self.error_message)) return (value, translator(self.error_message))
def formatter(self, value): def formatter(self, value):
if value is None: if value is None:
@@ -437,7 +441,7 @@ class IS_IN_SET(Validator):
self.multiple = multiple self.multiple = multiple
if isinstance(theset, dict): if isinstance(theset, dict):
self.theset = [str(item) for item in theset] self.theset = [str(item) for item in theset]
self.labels = theset.values() self.labels = list(theset.values())
elif theset and isinstance(theset, (tuple, list)) \ elif theset and isinstance(theset, (tuple, list)) \
and isinstance(theset[0], (tuple, list)) and len(theset[0]) == 2: and isinstance(theset[0], (tuple, list)) and len(theset[0]) == 2:
self.theset = [str(item) for item, label in theset] self.theset = [str(item) for item, label in theset]
@@ -474,17 +478,17 @@ class IS_IN_SET(Validator):
thestrset = [str(x) for x in self.theset] thestrset = [str(x) for x in self.theset]
failures = [x for x in values if not str(x) in thestrset] failures = [x for x in values if not str(x) in thestrset]
if failures and self.theset: if failures and self.theset:
return (value, translate(self.error_message)) return (value, translator(self.error_message))
if self.multiple: if self.multiple:
if isinstance(self.multiple, (tuple, list)) and \ if isinstance(self.multiple, (tuple, list)) and \
not self.multiple[0] <= len(values) < self.multiple[1]: not self.multiple[0] <= len(values) < self.multiple[1]:
return (values, translate(self.error_message)) return (values, translator(self.error_message))
return (values, None) return (values, None)
return (value, None) return (value, None)
regex1 = re.compile('\w+\.\w+') regex1 = re.compile(r'\w+\.\w+')
regex2 = re.compile('%\(([^\)]+)\)\d*(?:\.\d+)?[a-zA-Z]') regex2 = re.compile(r'%\(([^\)]+)\)\d*(?:\.\d+)?[a-zA-Z]')
class IS_IN_DB(Validator): class IS_IN_DB(Validator):
@@ -516,7 +520,6 @@ class IS_IN_DB(Validator):
delimiter=None, delimiter=None,
auto_add=False, auto_add=False,
): ):
from pydal.objects import Table
if hasattr(dbset, 'define_table'): if hasattr(dbset, 'define_table'):
self.dbset = dbset() self.dbset = dbset()
else: else:
@@ -575,7 +578,7 @@ class IS_IN_DB(Validator):
else: else:
fields = [table[k] for k in self.fieldnames] fields = [table[k] for k in self.fieldnames]
ignore = (FieldVirtual, FieldMethod) ignore = (FieldVirtual, FieldMethod)
fields = filter(lambda f: not isinstance(f, ignore), fields) fields = [f for f in fields if not isinstance(f, ignore)]
if self.dbset.db._dbname != 'gae': if self.dbset.db._dbname != 'gae':
orderby = self.orderby or reduce(lambda a, b: a | b, fields) orderby = self.orderby or reduce(lambda a, b: a | b, fields)
groupby = self.groupby groupby = self.groupby
@@ -637,19 +640,19 @@ class IS_IN_DB(Validator):
if self.auto_add: if self.auto_add:
value = str(self.maybe_add(table, self.fieldnames[0], value)) value = str(self.maybe_add(table, self.fieldnames[0], value))
else: else:
return (values, translate(self.error_message)) return (values, translator(self.error_message))
new_values.append(value) new_values.append(value)
values = new_values values = new_values
if isinstance(self.multiple, (tuple, list)) and \ if isinstance(self.multiple, (tuple, list)) and \
not self.multiple[0] <= len(values) < self.multiple[1]: not self.multiple[0] <= len(values) < self.multiple[1]:
return (values, translate(self.error_message)) return (values, translator(self.error_message))
if self.theset: if self.theset:
if not [v for v in values if v not in self.theset]: if not [v for v in values if v not in self.theset]:
return (values, None) return (values, None)
else: else:
def count(values, s=self.dbset, f=field): def count(values, s=self.dbset, f=field):
return s(f.belongs(map(int, values))).count() return s(f.belongs(list(map(int, values)))).count()
if self.dbset.db._adapter.dbengine == "google:datastore": if self.dbset.db._adapter.dbengine == "google:datastore":
range_ids = range(0, len(values), 30) range_ids = range(0, len(values), 30)
@@ -665,12 +668,12 @@ class IS_IN_DB(Validator):
elif self.auto_add: elif self.auto_add:
value = self.maybe_add(table, self.fieldnames[0], value) value = self.maybe_add(table, self.fieldnames[0], value)
else: else:
return (value, translate(self.error_message)) return (value, translator(self.error_message))
try: try:
value = int(value) value = int(value)
except TypeError: except TypeError:
return (value, translate(self.error_message)) return (value, translator(self.error_message))
if self.theset: if self.theset:
if str(value) in self.theset: if str(value) in self.theset:
@@ -684,7 +687,7 @@ class IS_IN_DB(Validator):
return self._and(value) return self._and(value)
else: else:
return (value, None) return (value, None)
return (value, translate(self.error_message)) return (value, translator(self.error_message))
class IS_NOT_IN_DB(Validator): class IS_NOT_IN_DB(Validator):
@@ -705,8 +708,6 @@ class IS_NOT_IN_DB(Validator):
allowed_override=[], allowed_override=[],
ignore_common_filters=False, ignore_common_filters=False,
): ):
from pydal.objects import Table
if isinstance(field, Table): if isinstance(field, Table):
field = field._id field = field._id
@@ -726,7 +727,7 @@ class IS_NOT_IN_DB(Validator):
def __call__(self, value): def __call__(self, value):
value = to_native(str(value)) value = to_native(str(value))
if not value.strip(): if not value.strip():
return (value, translate(self.error_message)) return (value, translator(self.error_message))
if value in self.allowed_override: if value in self.allowed_override:
return (value, None) return (value, None)
(tablename, fieldname) = str(self.field).split('.') (tablename, fieldname) = str(self.field).split('.')
@@ -739,11 +740,11 @@ class IS_NOT_IN_DB(Validator):
fields = [table[f] for f in id] fields = [table[f] for f in id]
row = subset.select(*fields, **dict(limitby=(0, 1), orderby_on_limitby=False)).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): if row and any(str(row[f]) != str(id[f]) for f in id):
return (value, translate(self.error_message)) return (value, translator(self.error_message))
else: else:
row = subset.select(table._id, field, limitby=(0, 1), orderby_on_limitby=False).first() row = subset.select(table._id, field, limitby=(0, 1), orderby_on_limitby=False).first()
if row and str(row[table._id]) != str(id): if row and str(row[table._id]) != str(id):
return (value, translate(self.error_message)) return (value, translator(self.error_message))
return (value, None) return (value, None)
@@ -759,7 +760,7 @@ def range_error_message(error_message, what_to_enter, minimum, maximum):
error_message += ' less than or equal to %(max)g' error_message += ' less than or equal to %(max)g'
if type(maximum) in integer_types: if type(maximum) in integer_types:
maximum -= 1 maximum -= 1
return translate(error_message) % dict(min=minimum, max=maximum) return translator(error_message) % dict(min=minimum, max=maximum)
class IS_INT_IN_RANGE(Validator): class IS_INT_IN_RANGE(Validator):
@@ -1044,7 +1045,7 @@ class IS_NOT_EMPTY(Validator):
def __call__(self, value): def __call__(self, value):
value, empty = is_empty(value, empty_regex=self.empty_regex) value, empty = is_empty(value, empty_regex=self.empty_regex)
if empty: if empty:
return (value, translate(self.error_message)) return (value, translator(self.error_message))
return (value, None) return (value, None)
@@ -1158,7 +1159,7 @@ class IS_EMAIL(Validator):
""" """
body_regex = re.compile(''' body_regex = re.compile(r'''
^(?!\.) # name may not begin with a dot ^(?!\.) # name may not begin with a dot
( (
[-a-z0-9!\#$%&'*+/=?^_`{|}~] # all legal characters except dot [-a-z0-9!\#$%&'*+/=?^_`{|}~] # all legal characters except dot
@@ -1167,7 +1168,7 @@ class IS_EMAIL(Validator):
)+ )+
(?<!\.)$ # name may not end with a dot (?<!\.)$ # name may not end with a dot
''', re.VERBOSE | re.IGNORECASE) ''', re.VERBOSE | re.IGNORECASE)
domain_regex = re.compile(''' domain_regex = re.compile(r'''
( (
localhost localhost
| |
@@ -1184,7 +1185,7 @@ class IS_EMAIL(Validator):
)$ )$
''', re.VERBOSE | re.IGNORECASE) ''', re.VERBOSE | re.IGNORECASE)
regex_proposed_but_failed = re.compile('^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$', re.VERBOSE | re.IGNORECASE) regex_proposed_but_failed = re.compile(r'^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$', re.VERBOSE | re.IGNORECASE)
def __init__(self, def __init__(self,
banned=None, banned=None,
@@ -1200,7 +1201,7 @@ class IS_EMAIL(Validator):
def __call__(self, value): def __call__(self, value):
if not(isinstance(value, (basestring, unicodeT))) or not value or '@' not in value: if not(isinstance(value, (basestring, unicodeT))) or not value or '@' not in value:
return (value, translate(self.error_message)) return (value, translator(self.error_message))
body, domain = value.rsplit('@', 1) body, domain = value.rsplit('@', 1)
@@ -1223,7 +1224,7 @@ class IS_EMAIL(Validator):
if (not self.banned or not self.banned.match(domain)) \ if (not self.banned or not self.banned.match(domain)) \
and (not self.forced or self.forced.match(domain)): and (not self.forced or self.forced.match(domain)):
return (value, None) return (value, None)
return (value, translate(self.error_message)) return (value, translator(self.error_message))
class IS_LIST_OF_EMAILS(object): class IS_LIST_OF_EMAILS(object):
@@ -1254,7 +1255,7 @@ class IS_LIST_OF_EMAILS(object):
return (value, None) return (value, None)
else: else:
return (value, return (value,
translate(self.error_message) % ', '.join(bad_emails)) translator(self.error_message) % ', '.join(bad_emails))
def formatter(self, value, row=None): def formatter(self, value, row=None):
return ', '.join(value or []) return ', '.join(value or [])
@@ -1457,7 +1458,6 @@ def unicode_to_ascii_authority(authority):
# We use the ToASCII operation because we are about to put the authority # We use the ToASCII operation because we are about to put the authority
# into an IDN-unaware slot # into an IDN-unaware slot
asciiLabels = [] asciiLabels = []
import encodings.idna
for label in labels: for label in labels:
if label: if label:
asciiLabels.append(to_native(encodings.idna.ToASCII(label))) asciiLabels.append(to_native(encodings.idna.ToASCII(label)))
@@ -1614,7 +1614,7 @@ class IS_GENERIC_URL(Validator):
# if we dont have anything or the URL misuses the '%' character # if we dont have anything or the URL misuses the '%' character
if not value or self.GENERIC_URL.search(value): if not value or self.GENERIC_URL.search(value):
return (value, translate(self.error_message)) return (value, translator(self.error_message))
if '#' in value: if '#' in value:
url, fragment_part = value.split('#') url, fragment_part = value.split('#')
@@ -1626,7 +1626,7 @@ class IS_GENERIC_URL(Validator):
try: try:
components = urlparse.urlparse(urllib_unquote(value))._asdict() components = urlparse.urlparse(urllib_unquote(value))._asdict()
except ValueError: except ValueError:
return (value, translate(self.error_message)) return (value, translator(self.error_message))
# Clean up the scheme before we check it # Clean up the scheme before we check it
scheme = components['scheme'] scheme = components['scheme']
@@ -1654,7 +1654,7 @@ class IS_GENERIC_URL(Validator):
else: else:
return (value, None) return (value, None)
# else the URL is not valid # else the URL is not valid
return (value, translate(self.error_message)) return (value, translator(self.error_message))
# Sources (obtained 2017-Nov-11): # Sources (obtained 2017-Nov-11):
# http://data.iana.org/TLD/tlds-alpha-by-domain.txt # http://data.iana.org/TLD/tlds-alpha-by-domain.txt
@@ -1997,8 +1997,8 @@ class IS_HTTP_URL(Validator):
""" """
GENERIC_VALID_IP = re.compile( GENERIC_VALID_IP = re.compile(
"([\w.!~*'|;:&=+$,-]+@)?\d+\.\d+\.\d+\.\d+(:\d*)*$") r"([\w.!~*'|;:&=+$,-]+@)?\d+\.\d+\.\d+\.\d+(:\d*)*$")
GENERIC_VALID_DOMAIN = re.compile("([\w.!~*'|;:&=+$,-]+@)?(([A-Za-z0-9]+[A-Za-z0-9\-]*[A-Za-z0-9]+\.)*([A-Za-z0-9]+\.)*)*([A-Za-z]+[A-Za-z0-9\-]*[A-Za-z0-9]+)\.?(:\d*)*$") GENERIC_VALID_DOMAIN = re.compile(r"([\w.!~*'|;:&=+$,-]+@)?(([A-Za-z0-9]+[A-Za-z0-9\-]*[A-Za-z0-9]+\.)*([A-Za-z0-9]+\.)*)*([A-Za-z]+[A-Za-z0-9\-]*[A-Za-z0-9]+)\.?(:\d*)*$")
def __init__( def __init__(
self, self,
@@ -2090,7 +2090,7 @@ class IS_HTTP_URL(Validator):
except: except:
pass pass
# else the HTTP URL is not valid # else the HTTP URL is not valid
return (value, translate(self.error_message)) return (value, translator(self.error_message))
class IS_URL(Validator): class IS_URL(Validator):
@@ -2240,7 +2240,7 @@ class IS_URL(Validator):
except Exception as e: except Exception as e:
# If we are not able to convert the unicode url into a # If we are not able to convert the unicode url into a
# US-ASCII URL, then the URL is not valid # US-ASCII URL, then the URL is not valid
return (value, translate(self.error_message)) return (value, translator(self.error_message))
methodResult = subMethod(asciiValue) methodResult = subMethod(asciiValue)
# if the validation of the US-ASCII version of the value failed # if the validation of the US-ASCII version of the value failed
if not methodResult[1] is None: if not methodResult[1] is None:
@@ -2322,7 +2322,7 @@ class IS_TIME(Validator):
pass pass
except ValueError: except ValueError:
pass pass
return (ivalue, translate(self.error_message)) return (ivalue, translator(self.error_message))
# A UTC class. # A UTC class.
@@ -2353,7 +2353,7 @@ class IS_DATE(Validator):
def __init__(self, format='%Y-%m-%d', def __init__(self, format='%Y-%m-%d',
error_message='Enter date as %(format)s'): error_message='Enter date as %(format)s'):
self.format = translate(format) self.format = translator(format)
self.error_message = str(error_message) self.error_message = str(error_message)
self.extremes = {} self.extremes = {}
@@ -2368,7 +2368,7 @@ class IS_DATE(Validator):
return (value, None) return (value, None)
except: except:
self.extremes.update(IS_DATETIME.nice(self.format)) self.extremes.update(IS_DATETIME.nice(self.format))
return (ovalue, translate(self.error_message) % self.extremes) return (ovalue, translator(self.error_message) % self.extremes)
def formatter(self, value): def formatter(self, value):
if value is None: if value is None:
@@ -2417,7 +2417,7 @@ class IS_DATETIME(Validator):
def __init__(self, format='%Y-%m-%d %H:%M:%S', def __init__(self, format='%Y-%m-%d %H:%M:%S',
error_message='Enter date and time as %(format)s', error_message='Enter date and time as %(format)s',
timezone=None): timezone=None):
self.format = translate(format) self.format = translator(format)
self.error_message = str(error_message) self.error_message = str(error_message)
self.extremes = {} self.extremes = {}
self.timezone = timezone self.timezone = timezone
@@ -2436,7 +2436,7 @@ class IS_DATETIME(Validator):
return (value, None) return (value, None)
except: except:
self.extremes.update(IS_DATETIME.nice(self.format)) self.extremes.update(IS_DATETIME.nice(self.format))
return (ovalue, translate(self.error_message) % self.extremes) return (ovalue, translator(self.error_message) % self.extremes)
def formatter(self, value): def formatter(self, value):
if value is None: if value is None:
@@ -2504,9 +2504,9 @@ class IS_DATE_IN_RANGE(IS_DATE):
if msg is not None: if msg is not None:
return (value, msg) return (value, msg)
if self.minimum and self.minimum > value: if self.minimum and self.minimum > value:
return (ovalue, translate(self.error_message) % self.extremes) return (ovalue, translator(self.error_message) % self.extremes)
if self.maximum and value > self.maximum: if self.maximum and value > self.maximum:
return (ovalue, translate(self.error_message) % self.extremes) return (ovalue, translator(self.error_message) % self.extremes)
return (value, None) return (value, None)
@@ -2560,9 +2560,9 @@ class IS_DATETIME_IN_RANGE(IS_DATETIME):
if msg is not None: if msg is not None:
return (value, msg) return (value, msg)
if self.minimum and self.minimum > value: if self.minimum and self.minimum > value:
return (ovalue, translate(self.error_message) % self.extremes) return (ovalue, translator(self.error_message) % self.extremes)
if self.maximum and value > self.maximum: if self.maximum and value > self.maximum:
return (ovalue, translate(self.error_message) % self.extremes) return (ovalue, translator(self.error_message) % self.extremes)
return (value, None) return (value, None)
@@ -2580,11 +2580,11 @@ class IS_LIST_OF(Validator):
ivalue = [ivalue] ivalue = [ivalue]
ivalue = [i for i in ivalue if str(i).strip()] ivalue = [i for i in ivalue if str(i).strip()]
if self.minimum is not None and len(ivalue) < self.minimum: if self.minimum is not None and len(ivalue) < self.minimum:
return (ivalue, translate(self.error_message or return (ivalue, translator(self.error_message or 'Minimum length is %(min)s') %
'Minimum length is %(min)s') % dict(min=self.minimum, max=self.maximum)) dict(min=self.minimum, max=self.maximum))
if self.maximum is not None and len(ivalue) > self.maximum: if self.maximum is not None and len(ivalue) > self.maximum:
return (ivalue, translate(self.error_message or return (ivalue, translator(self.error_message or 'Maximum length is %(max)s') %
'Maximum length is %(max)s') % dict(min=self.minimum, max=self.maximum)) dict(min=self.minimum, max=self.maximum))
new_value = [] new_value = []
other = self.other other = self.other
if self.other: if self.other:
@@ -2722,7 +2722,7 @@ class IS_SLUG(Validator):
def __call__(self, value): def __call__(self, value):
if self.check and value != urlify(value, self.maxlen, self.keep_underscores): if self.check and value != urlify(value, self.maxlen, self.keep_underscores):
return (value, translate(self.error_message)) return (value, translator(self.error_message))
return (urlify(value, self.maxlen, self.keep_underscores), None) return (urlify(value, self.maxlen, self.keep_underscores), None)
@@ -2751,7 +2751,7 @@ class ANY_OF(Validator):
if error is None: if error is None:
break break
if error is not None and self.error_message is not None: if error is not None and self.error_message is not None:
error = translate(self.error_message) error = translator(self.error_message)
return value, error return value, error
def formatter(self, value): def formatter(self, value):
@@ -3039,7 +3039,7 @@ class CRYPT(object):
def __call__(self, value): def __call__(self, value):
v = value and str(value)[:self.max_length] v = value and str(value)[:self.max_length]
if not v or len(v) < self.min_length: if not v or len(v) < self.min_length:
return ('', translate(self.error_message)) return ('', translator(self.error_message))
if isinstance(value, LazyCrypt): if isinstance(value, LazyCrypt):
return (value, None) return (value, None)
return (LazyCrypt(self, value), None) return (LazyCrypt(self, value), None)
@@ -3057,7 +3057,6 @@ otherset = frozenset(
def calc_entropy(string): def calc_entropy(string):
""" calculates a simple entropy for a given string """ """ calculates a simple entropy for a given string """
import math
alphabet = 0 # alphabet size alphabet = 0 # alphabet size
other = set() other = set()
seen = set() seen = set()
@@ -3150,45 +3149,49 @@ class IS_STRONG(object):
if self.entropy is not None: if self.entropy is not None:
entropy = calc_entropy(value) entropy = calc_entropy(value)
if entropy < self.entropy: if entropy < self.entropy:
failures.append(translate("Entropy (%(have)s) less than required (%(need)s)") failures.append(translator("Entropy (%(have)s) less than required (%(need)s)")
% dict(have=entropy, need=self.entropy)) % dict(have=entropy, need=self.entropy))
if isinstance(self.min, int) and self.min > 0: if isinstance(self.min, int) and self.min > 0:
if not len(value) >= self.min: if not len(value) >= self.min:
failures.append(translate("Minimum length is %s") % self.min) failures.append(translator("Minimum length is %s") % self.min)
if isinstance(self.max, int) and self.max > 0: if isinstance(self.max, int) and self.max > 0:
if not len(value) <= self.max: if not len(value) <= self.max:
failures.append(translate("Maximum length is %s") % self.max) failures.append(translator("Maximum length is %s") % self.max)
if isinstance(self.special, int): if isinstance(self.special, int):
all_special = [ch in value for ch in self.specials] all_special = [ch in value for ch in self.specials]
if self.special > 0: if self.special > 0:
if not all_special.count(True) >= self.special: if not all_special.count(True) >= self.special:
failures.append(translate("Must include at least %s of the following: %s") failures.append(translator("Must include at least %s of the following: %s")
% (self.special, self.specials)) % (self.special, self.specials))
elif self.special is 0:
if len(all_special) > 0:
failures.append(translator("May not contain any of the following: %s")
% self.specials)
if self.invalid: if self.invalid:
all_invalid = [ch in value for ch in self.invalid] all_invalid = [ch in value for ch in self.invalid]
if all_invalid.count(True) > 0: if all_invalid.count(True) > 0:
failures.append(translate("May not contain any of the following: %s") failures.append(translator("May not contain any of the following: %s")
% self.invalid) % self.invalid)
if isinstance(self.upper, int): if isinstance(self.upper, int):
all_upper = re.findall("[A-Z]", value) all_upper = re.findall("[A-Z]", value)
if self.upper > 0: if self.upper > 0:
if not len(all_upper) >= self.upper: if not len(all_upper) >= self.upper:
failures.append(translate("Must include at least %s uppercase") failures.append(translator("Must include at least %s uppercase")
% str(self.upper)) % str(self.upper))
else: elif self.upper is 0:
if len(all_upper) > 0: if len(all_upper) > 0:
failures.append( failures.append(
translate("May not include any uppercase letters")) translator("May not include any uppercase letters"))
if isinstance(self.lower, int): if isinstance(self.lower, int):
all_lower = re.findall("[a-z]", value) all_lower = re.findall("[a-z]", value)
if self.lower > 0: if self.lower > 0:
if not len(all_lower) >= self.lower: if not len(all_lower) >= self.lower:
failures.append(translate("Must include at least %s lowercase") failures.append(translator("Must include at least %s lowercase")
% str(self.lower)) % str(self.lower))
else: elif self.lower is 0:
if len(all_lower) > 0: if len(all_lower) > 0:
failures.append( failures.append(
translate("May not include any lowercase letters")) translator("May not include any lowercase letters"))
if isinstance(self.number, int): if isinstance(self.number, int):
all_number = re.findall("[0-9]", value) all_number = re.findall("[0-9]", value)
if self.number > 0: if self.number > 0:
@@ -3196,20 +3199,19 @@ class IS_STRONG(object):
if self.number > 1: if self.number > 1:
numbers = "numbers" numbers = "numbers"
if not len(all_number) >= self.number: if not len(all_number) >= self.number:
failures.append(translate("Must include at least %s %s") failures.append(translator("Must include at least %s %s")
% (str(self.number), numbers)) % (str(self.number), numbers))
else: elif self.number is 0:
if len(all_number) > 0: if len(all_number) > 0:
failures.append(translate("May not include any numbers")) failures.append(translator("May not include any numbers"))
if len(failures) == 0: if len(failures) == 0:
return (value, None) return (value, None)
if not self.error_message: if not self.error_message:
if self.estring: if self.estring:
return (value, '|'.join(failures)) return (value, '|'.join(map(str, failures)))
from gluon.html import XML return (value, ', '.join(failures))
return (value, XML('<br />'.join(failures)))
else: else:
return (value, translate(self.error_message)) return (value, translator(self.error_message))
class IS_IMAGE(Validator): class IS_IMAGE(Validator):
@@ -3301,7 +3303,7 @@ class IS_IMAGE(Validator):
value.file.seek(0) value.file.seek(0)
return (value, None) return (value, None)
except Exception as e: except Exception as e:
return (value, translate(self.error_message)) return (value, translator(self.error_message))
def __bmp(self, stream): def __bmp(self, stream):
if stream.read(2) == b'BM': if stream.read(2) == b'BM':
@@ -3394,7 +3396,7 @@ class IS_UPLOAD_FILENAME(Validator):
try: try:
string = value.filename string = value.filename
except: except:
return (value, translate(self.error_message)) return (value, translator(self.error_message))
if self.case == 1: if self.case == 1:
string = string.lower() string = string.lower()
elif self.case == 2: elif self.case == 2:
@@ -3406,9 +3408,9 @@ class IS_UPLOAD_FILENAME(Validator):
if dot == -1: if dot == -1:
dot = len(string) dot = len(string)
if self.filename and not self.filename.match(string[:dot]): if self.filename and not self.filename.match(string[:dot]):
return (value, translate(self.error_message)) return (value, translator(self.error_message))
elif self.extension and not self.extension.match(string[dot + 1:]): elif self.extension and not self.extension.match(string[dot + 1:]):
return (value, translate(self.error_message)) return (value, translator(self.error_message))
else: else:
return (value, None) return (value, None)
@@ -3513,7 +3515,7 @@ class IS_IPV4(Validator):
""" """
regex = re.compile( regex = re.compile(
'^(([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.){3}([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])$') r'^(([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.){3}([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])$')
numbers = (16777216, 65536, 256, 1) numbers = (16777216, 65536, 256, 1)
localhost = 2130706433 localhost = 2130706433
private = ((2886729728, 2886795263), (3232235520, 3232301055)) private = ((2886729728, 2886795263), (3232235520, 3232301055))
@@ -3533,7 +3535,7 @@ class IS_IPV4(Validator):
if isinstance(value, str): if isinstance(value, str):
temp.append(value.split('.')) temp.append(value.split('.'))
elif isinstance(value, (list, tuple)): elif isinstance(value, (list, tuple)):
if len(value) == len(list(filter(lambda item: isinstance(item, int), value))) == 4: if len(value) == len([item for item in value if isinstance(item, int)]) == 4:
temp.append(value) temp.append(value)
else: else:
for item in value: for item in value:
@@ -3578,7 +3580,7 @@ class IS_IPV4(Validator):
ok = False ok = False
if ok: if ok:
return (value, None) return (value, None)
return (value, translate(self.error_message)) return (value, translator(self.error_message))
class IS_IPV6(Validator): class IS_IPV6(Validator):
@@ -3671,13 +3673,11 @@ class IS_IPV6(Validator):
self.error_message = error_message self.error_message = error_message
def __call__(self, value): def __call__(self, value):
from gluon._compat import ipaddress
try: try:
ip = ipaddress.IPv6Address(to_unicode(value)) ip = ipaddress.IPv6Address(to_unicode(value))
ok = True ok = True
except ipaddress.AddressValueError: except ipaddress.AddressValueError:
return (value, translate(self.error_message)) return (value, translator(self.error_message))
if self.subnets: if self.subnets:
# iterate through self.subnets to see if value is a member # iterate through self.subnets to see if value is a member
@@ -3688,7 +3688,7 @@ class IS_IPV6(Validator):
try: try:
ipnet = ipaddress.IPv6Network(to_unicode(network)) ipnet = ipaddress.IPv6Network(to_unicode(network))
except (ipaddress.NetmaskValueError, ipaddress.AddressValueError): except (ipaddress.NetmaskValueError, ipaddress.AddressValueError):
return (value, translate('invalid subnet provided')) return (value, translator('invalid subnet provided'))
if ip in ipnet: if ip in ipnet:
ok = True ok = True
@@ -3719,7 +3719,7 @@ class IS_IPV6(Validator):
if ok: if ok:
return (value, None) return (value, None)
return (value, translate(self.error_message)) return (value, translator(self.error_message))
class IS_IPADDRESS(Validator): class IS_IPADDRESS(Validator):
@@ -3894,7 +3894,6 @@ class IS_IPADDRESS(Validator):
self.error_message = error_message self.error_message = error_message
def __call__(self, value): def __call__(self, value):
from gluon._compat import ipaddress
IPAddress = ipaddress.ip_address IPAddress = ipaddress.ip_address
IPv6Address = ipaddress.IPv6Address IPv6Address = ipaddress.IPv6Address
IPv4Address = ipaddress.IPv4Address IPv4Address = ipaddress.IPv4Address
@@ -3902,12 +3901,12 @@ class IS_IPADDRESS(Validator):
try: try:
ip = IPAddress(to_unicode(value)) ip = IPAddress(to_unicode(value))
except ValueError: except ValueError:
return (value, translate(self.error_message)) return (value, translator(self.error_message))
if self.is_ipv4 and isinstance(ip, IPv6Address): if self.is_ipv4 and isinstance(ip, IPv6Address):
retval = (value, translate(self.error_message)) retval = (value, translator(self.error_message))
elif self.is_ipv6 and isinstance(ip, IPv4Address): elif self.is_ipv6 and isinstance(ip, IPv4Address):
retval = (value, translate(self.error_message)) retval = (value, translator(self.error_message))
elif self.is_ipv4 or isinstance(ip, IPv4Address): elif self.is_ipv4 or isinstance(ip, IPv4Address):
retval = IS_IPV4( retval = IS_IPV4(
minip=self.minip, minip=self.minip,
@@ -3931,6 +3930,6 @@ class IS_IPADDRESS(Validator):
error_message=self.error_message error_message=self.error_message
)(value) )(value)
else: else:
retval = (value, translate(self.error_message)) retval = (value, translator(self.error_message))
return retval return retval
+4 -2
View File
@@ -9,7 +9,6 @@
The widget is called from web2py The widget is called from web2py
---------------------------------- ----------------------------------
""" """
from __future__ import print_function
import datetime import datetime
import sys import sys
@@ -31,6 +30,9 @@ from gluon.settings import global_settings
from gluon.shell import run, test from gluon.shell import run, test
from gluon.utils import is_valid_ip_address, is_loopback_ip_address, getipaddrinfo from gluon.utils import is_valid_ip_address, is_loopback_ip_address, getipaddrinfo
if PY2:
input = raw_input
ProgramName = 'web2py Web Framework' ProgramName = 'web2py Web Framework'
ProgramAuthor = 'Created by Massimo Di Pierro, Copyright 2007-' + str( ProgramAuthor = 'Created by Massimo Di Pierro, Copyright 2007-' + str(
@@ -952,7 +954,7 @@ def console():
if options.gae: if options.gae:
if not os.path.exists('app.yaml'): if not os.path.exists('app.yaml'):
name = raw_input("Your GAE app name: ") name = input("Your GAE app name: ")
content = open(os.path.join('examples', 'app.example.yaml'), 'rb').read() content = open(os.path.join('examples', 'app.example.yaml'), 'rb').read()
open('app.yaml', 'wb').write(content.replace("yourappname", name)) open('app.yaml', 'wb').write(content.replace("yourappname", name))
else: else:
+2 -2
View File
@@ -1,6 +1,6 @@
echo "This script will: echo "This script will:
1) Install modules needed to run web2py on Fedora and CentOS/RHEL 1) Install modules needed to run web2py on Fedora and CentOS/RHEL
2) Install Python 2.6 to /opt and recompile wsgi if not provided 2) Install Python 3.7 to /opt and recompile wsgi if not provided
2) Install web2py in /opt/web-apps/ 2) Install web2py in /opt/web-apps/
3) Configure SELinux and iptables 3) Configure SELinux and iptables
5) Create a self signed ssl certificate 5) Create a self signed ssl certificate
@@ -56,7 +56,7 @@ echo
yum update yum update
# Install required packages # Install required packages
yum install httpd mod_ssl mod_wsgi wget python unzip yum install httpd mod_ssl mod_wsgi wget python3 unzip
### ###
### Phase 2 - Install web2py ### Phase 2 - Install web2py
+2 -2
View File
@@ -38,8 +38,8 @@ apt-get -y install libapache2-mod-wsgi
apt-get -y install python-psycopg2 apt-get -y install python-psycopg2
apt-get -y install postfix apt-get -y install postfix
apt-get -y install wget apt-get -y install wget
apt-get -y install python-matplotlib apt-get -y install python3-matplotlib
apt-get -y install python-reportlab apt-get -y install python3-reportlab
apt-get -y install mercurial apt-get -y install mercurial
/etc/init.d/postgresql restart /etc/init.d/postgresql restart
+2 -2
View File
@@ -1,6 +1,6 @@
echo "This script will: echo "This script will:
1) Install modules needed to run web2py on Fedora and CentOS/RHEL 1) Install modules needed to run web2py on Fedora and CentOS/RHEL
2) Install Python 2.6 to /opt and recompile wsgi if not provided 2) Install Python 3.7 to /opt and recompile wsgi if not provided
2) Install web2py in /opt/web-apps/ 2) Install web2py in /opt/web-apps/
3) Configure SELinux and iptables 3) Configure SELinux and iptables
5) Create a self signed ssl certificate 5) Create a self signed ssl certificate
@@ -54,7 +54,7 @@ echo
yum update yum update
# Install required packages # Install required packages
yum install httpd mod_ssl mod_wsgi wget python yum install httpd mod_ssl mod_wsgi wget python3
# Verify we have at least Python 2.5 # Verify we have at least Python 2.5
typeset -i version_major typeset -i version_major
+2 -2
View File
@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
echo "This script will: echo "This script will:
1) Install modules needed to run web2py on Fedora and CentOS/RHEL 1) Install modules needed to run web2py on Fedora and CentOS/RHEL
2) Install Python 2.6 to /opt and recompile wsgi if not provided 2) Install Python 3.7 to /opt and recompile wsgi if not provided
2) Install web2py in /opt/web-apps/ 2) Install web2py in /opt/web-apps/
3) Configure SELinux and iptables 3) Configure SELinux and iptables
5) Create a self signed ssl certificate 5) Create a self signed ssl certificate
@@ -55,7 +55,7 @@ echo
yum update yum update
# Install required packages # Install required packages
yum install httpd mod_ssl mod_wsgi wget python yum install httpd mod_ssl mod_wsgi wget python3
# Verify we have at least Python 2.5 # Verify we have at least Python 2.5
typeset -i version_major typeset -i version_major
+6 -7
View File
@@ -31,18 +31,17 @@ apt-get -y install zip unzip
apt-get -y install tar apt-get -y install tar
apt-get -y install openssh-server apt-get -y install openssh-server
apt-get -y install build-essential apt-get -y install build-essential
apt-get -y install python apt-get -y install python3
#apt-get -y install python2.5 apt-get -y install ipython3
apt-get -y install ipython apt-get -y install python3-dev
apt-get -y install python-dev
apt-get -y install postgresql apt-get -y install postgresql
apt-get -y install apache2 apt-get -y install apache2
apt-get -y install libapache2-mod-wsgi apt-get -y install libapache2-mod-wsgi
apt-get -y install python2.5-psycopg2 apt-get -y install python3-psycopg2
apt-get -y install postfix apt-get -y install postfix
apt-get -y install wget apt-get -y install wget
apt-get -y install python-matplotlib apt-get -y install python3-matplotlib
apt-get -y install python-reportlab apt-get -y install python3-reportlab
apt-get -y install mercurial apt-get -y install mercurial
/etc/init.d/postgresql restart /etc/init.d/postgresql restart
+6 -10
View File
@@ -14,15 +14,14 @@ parentdir = os.path.dirname(currentdir)
sys.path.insert(0, parentdir) sys.path.insert(0, parentdir)
from gluon.cfs import getcfs from gluon.cfs import getcfs
from gluon.utf8 import Utf8
from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, to_native, pjoin from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, to_native, pjoin
from gluon.languages import findT from gluon.languages import findT, sort_function
# This script can be run with no arguments (which sets the application folder to the current working directory, and default language to English), one argument (which sets the default language), or two arguments (application folder path and default language). # This script can be run with no arguments (which sets the application folder to the current working directory, and default language to English), one argument (which sets the default language), or two arguments (application folder path and default language).
# When run, it will update the default language, as well as strip all of the strings found in the non-default languages but not in the default language, and add the strings found in the default language to the non-default languages it is not, making sure translators don't do additional work that will never be used. # When run, it will update the default language, as well as strip all of the strings found in the non-default languages but not in the default language, and add the strings found in the default language to the non-default languages it is not, making sure translators don't do additional work that will never be used.
def read_dict_aux(filename): def read_dict_aux(filename):
lang_text = open(filename, 'r').read().replace(b'\r\n', b'\n') lang_text = open(filename, 'r').read().replace('\r\n', '\n')
try: try:
return safe_eval(to_native(lang_text)) or {} return safe_eval(to_native(lang_text)) or {}
except Exception: except Exception:
@@ -41,14 +40,11 @@ def safe_eval(text):
return eval(text, {}, {}) return eval(text, {}, {})
return None return None
def sort_function(x, y):
return cmp(unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())
def write_file(file, contents): def write_file(file, contents):
file.write('# -*- coding: utf-8 -*-\n{\n') file.write('# -*- coding: utf-8 -*-\n{\n')
for key in sorted(contents, sort_function): for key in sorted(contents, key = sort_function):
file.write('%s: %s,\n' % (repr(Utf8(key)), file.write('%s: %s,\n' % (repr(to_unicode(key)),
repr(Utf8(contents[key])))) repr(to_unicode(contents[key]))))
file.write('}\n') file.write('}\n')
file.close() file.close()
@@ -67,7 +63,7 @@ def update_languages(cwd, default_lang):
if phrase in default: if phrase in default:
new_dict[phrase] = i18n[phrase] new_dict[phrase] = i18n[phrase]
write_file(open(os.path.join(cwd, "languages", lang), 'w'), new_dict) write_file(open(os.path.join(cwd, "languages", lang), 'w'), new_dict)
print lang print(lang)
if __name__ == "__main__": if __name__ == "__main__":
cwd = os.getcwd() cwd = os.getcwd()