diff --git a/Makefile b/Makefile index 844548ac..aa14e01b 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ update: echo "remember that pymysql was tweaked" src: ### Use semantic versioning - echo 'Version 2.5.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION + echo 'Version 2.6.0-development+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION ### rm -f all junk files make clean ### clean up baisc apps diff --git a/VERSION b/VERSION index 8477ccdf..ee2c404b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.5.1-stable+timestamp.2013.06.24.14.23.41 +Version 2.6.0-development+timestamp.2013.06.30.10.46.28 diff --git a/applications/admin/languages/de.py b/applications/admin/languages/de.py index eddd03c8..d5750418 100644 --- a/applications/admin/languages/de.py +++ b/applications/admin/languages/de.py @@ -3,54 +3,62 @@ '!langcode!': 'de', '!langname!': 'Deutsch', '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"Update" ist ein optionaler Ausdruck wie "Feld1 = \'newvalue". JOIN Ergebnisse können nicht aktualisiert oder gelöscht werden', -'%s %%{row} deleted': '%s Zeilen gelöscht', -'%s %%{row} updated': '%s Zeilen aktualisiert', -'%Y-%m-%d': '%Y-%m-%d', -'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', -'(requires internet access)': '(requires internet access)', +'%s %%{row} deleted': '%s %%{row} Zeilen gelöscht', +'%s %%{row} updated': '%s %%{row} Zeilen aktualisiert', +'%Y-%m-%d': '%d.%m.%Y', +'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S', +'(requires internet access)': '(Internet Zugang wir benötigt)', +'(requires internet access, experimental)': '(benötigt Internet Zugang)', '(something like "it-it")': '(so etwas wie "it-it")', -'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files', +'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)', +'@markmin\x01Searching: **%s** %%{file}': '@markmin\x01Suche: **%s** Dateien', 'A new version of web2py is available': 'Eine neue Version von web2py ist verfügbar', 'A new version of web2py is available: %s': 'Eine neue Version von web2py ist verfügbar: %s', 'Abort': 'Abbrechen', -'About': 'Über', -'About application': 'Über die Anwendung', -'Additional code for your application': 'Additional code for your application', -'additional code for your application': 'zusätzlicher Code für Ihre Anwendung', -'admin disabled because no admin password': ' admin ist deaktiviert, weil kein Admin-Passwort gesetzt ist', +'About': 'über', +'About application': 'über die Anwendung', +'Additional code for your application': 'zusätzlicher Code für Ihre Anwendung', +'admin disabled because no admin password': 'admin ist deaktiviert, weil kein Admin-Passwort gesetzt ist', 'admin disabled because not supported on google apps engine': 'admin ist deaktiviert, es existiert dafür keine Unterstützung auf der google apps engine', 'admin disabled because unable to access password file': 'admin ist deaktiviert, weil kein Zugriff auf die Passwortdatei besteht', 'Admin is disabled because insecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals', 'Admin is disabled because unsecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals', -'Admin language': 'Admin language', -'administrative interface': 'administrative interface', +'Admin language': 'Admin-Sprache', +'administrative interface': 'Administrative Schnittstelle', 'Administrator Password:': 'Administrator Passwort:', +'An error occured, please %s the page': 'Ein Fehler ist aufgetereten, bitte %s die Seite', 'and rename it (required):': 'und benenne sie um (erforderlich):', 'and rename it:': ' und benenne sie um:', 'appadmin': 'appadmin', 'appadmin is disabled because insecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals', +'Application': 'Anwendung', 'application "%s" uninstalled': 'Anwendung "%s" deinstalliert', 'application compiled': 'Anwendung kompiliert', 'application is compiled and cannot be designed': 'Die Anwendung ist kompiliert kann deswegen nicht mehr geändert werden', -'Application name:': 'Application name:', +'Application name:': 'Name der Applikation:', +'are not used': 'wird nicht verwendet', +'are not used yet': 'wird 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 this object?': 'Are you sure you want to delete this object?', +'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 diese Anwendung deinstallieren wollen "%s"?', 'Are you sure you want to upgrade web2py now?': 'Sind Sie sich sicher, dass Sie web2py jetzt upgraden möchten?', 'arguments': 'arguments', +'at char %s': 'at char %s', +'at line %s': 'at line %s', 'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ACHTUNG: Die Einwahl benötigt eine sichere (HTTPS) Verbindung. Es sei denn sie läuft Lokal(localhost).', 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ACHTUNG: Testen ist nicht threadsicher. Führen sie also nicht mehrere Tests gleichzeitig aus.', 'ATTENTION: This is an experimental feature and it needs more testing.': 'ACHTUNG: Dies ist eine experimentelle Funktion und benötigt noch weitere Tests.', 'ATTENTION: you cannot edit the running application!': 'ACHTUNG: Eine laufende Anwendung kann nicht editiert werden!', 'Authentication': 'Authentifizierung', -'Available databases and tables': 'Verfügbare Datenbanken und Tabellen', +'Available databases and tables': 'verfügbare Datenbanken und Tabellen', 'back': 'zurück', -'beautify': 'beautify', -'cache': 'Cache', +'beautify': 'verschönern', +'cache': 'Pufferspeicher', 'cache, errors and sessions cleaned': 'Zwischenspeicher (cache), Fehler und Sitzungen (sessions) gelöscht', -'call': 'call', -'can be a git repo': 'can be a git repo', +'call': 'Aufruf', +'can be a git repo': 'kann ein git Repository sein', +'Cancel': 'Cancel', 'Cannot be empty': 'Darf nicht leer sein', 'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Nicht Kompilierbar:Es sind Fehler in der Anwendung. Beseitigen Sie die Fehler und versuchen Sie es erneut.', 'cannot create file': 'Kann Datei nicht erstellen', @@ -62,29 +70,31 @@ 'Check for upgrades': 'check for upgrades', 'Check to delete': 'Markiere zum löschen', 'Checking for upgrades...': 'Auf Updates überprüfen...', -'Clean': 'löschen', +'Clean': 'leeren', 'click here for online examples': 'hier klicken für online Beispiele', 'click here for the administrative interface': 'hier klicken für die Administrationsoberfläche ', +'Click row to expand traceback': 'Klicke auf die Zeile für Fehlerverfolgung', 'click to check for upgrades': 'hier klicken um nach Upgrades zu suchen', 'Client IP': 'Client IP', 'code': 'code', -'collapse/expand all': 'collapse/expand all', +'collapse/expand all': 'alles zu- bzw. aufklappen', 'Compile': 'kompilieren', 'compiled application removed': 'kompilierte Anwendung gelöscht', 'Controller': 'Controller', 'Controllers': 'Controller', 'controllers': 'Controllers', 'Copyright': 'Urheberrecht', +'Count': 'Anzahl', 'Create': 'erstellen', 'create file with filename:': 'erzeuge Datei mit Dateinamen:', 'create new application:': 'erzeuge neue Anwendung:', 'Create new simple application': 'Erzeuge neue Anwendung', -'created by': 'created by', +'created by': 'erstellt von', 'crontab': 'crontab', 'Current request': 'Aktuelle Anfrage (request)', 'Current response': 'Aktuelle Antwort (response)', 'Current session': 'Aktuelle Sitzung (session)', -'currently running': 'currently running', +'currently running': 'aktuell in Betrieb', 'currently saved or': 'des derzeit gespeicherten oder', 'customize me!': 'pass mich an!', 'data uploaded': 'Daten hochgeladen', @@ -97,23 +107,27 @@ 'DB Model': 'DB Modell', 'Debug': 'Debug', 'defines tables': 'definiere Tabellen', -'Delete': 'Löschen', +'Delete': 'löschen', 'delete': 'löschen', 'delete all checked': 'lösche alle markierten', 'delete plugin': 'Plugin löschen', -'Delete:': 'Löschen:', -'Deploy': 'deploy', +'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)', +'Delete:': 'löschen:', +'Deploy': 'Installieren', 'Deploy on Google App Engine': 'Auf Google App Engine installieren', -'Deploy to OpenShift': 'Deploy to OpenShift', +'Deploy to OpenShift': 'Auf OpenShift installieren', 'Description': 'Beschreibung', 'design': 'design', 'DESIGN': 'design', 'Design for': 'Design für', +'Detailed traceback description': 'Detailed traceback description', 'direction: ltr': 'direction: ltr', -'Disable': 'Disable', +'Disable': 'Deaktivieren', +'docs': 'docs', 'documentation': 'Dokumentation', 'done!': 'fertig!', -'download layouts': 'download layouts', +'Download .w2p': 'Download .w2p', +'download layouts': 'Layouts herunterladen', 'download plugins': 'download plugins', 'E-mail': 'E-mail', 'EDIT': 'BEARBEITEN', @@ -128,16 +142,23 @@ 'Editing file': 'Bearbeite Datei', 'Editing file "%s"': 'Bearbeite Datei "%s"', 'Editing Language file': 'Sprachdatei bearbeiten', +'Enable': 'Enable', 'Enterprise Web Framework': 'Enterprise Web Framework', +'Error': 'Fehler', 'Error logs for "%(app)s"': 'Fehlerprotokoll für "%(app)s"', +'Error snapshot': 'Error snapshot', +'Error ticket': 'Error ticket', 'Errors': 'Fehler', 'escape': 'escape', 'Exception instance attributes': 'Atribute der Ausnahmeinstanz', 'Expand Abbreviation': 'Kürzel erweitern', 'export as csv file': 'Exportieren als CSV-Datei', 'exposes': 'stellt zur Verfügung', +'exposes:': 'exposes:', 'extends': 'erweitert', +'failed to compile file because:': 'failed to compile file because:', 'failed to reload module': 'neu laden des Moduls fehlgeschlagen', +'File': 'Datei', 'file "%(filename)s" created': 'Datei "%(filename)s" erstellt', 'file "%(filename)s" deleted': 'Datei "%(filename)s" gelöscht', 'file "%(filename)s" uploaded': 'Datei "%(filename)s" hochgeladen', @@ -148,25 +169,33 @@ 'file saved on %(time)s': 'Datei gespeichert am %(time)s', 'file saved on %s': 'Datei gespeichert auf %s', 'filter': 'filter', +'Find Next': 'Find Next', +'Find Previous': 'Find Previous', 'First name': 'Vorname', +'Frames': 'Frames', 'Functions with no doctests will result in [passed] tests.': 'Funktionen ohne doctests erzeugen [passed] in Tests', 'Get from URL:': 'Get from URL:', 'Git Pull': 'Git Pull', 'Git Push': 'Git Push', 'Go to Matching Pair': 'gehe zum übereinstimmenden Paar', +'Goto': 'Goto', +'graph model': 'graph model', 'Group ID': 'Gruppen ID', 'Hello World': 'Hallo Welt', 'Help': 'Hilfe', +'Hide/Show Translated strings': 'Hide/Show Translated strings', +'Home': 'Home', 'htmledit': 'htmledit', -'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Falls der obere Test eine Fehler-Ticketnummer enthält deutet das auf einen Fehler in der Ausführung des Controllers hin, noch bevor der Doctest ausgeführt werden konnte. Gewöhnlich führen fehlerhafte Einrückungen oder fehlerhafter Code ausserhalb der Funktion zu solchen Fehlern. Ein grüner Titel deutet darauf hin, dass alle Test(wenn sie vorhanden sind) erfolgreich durchlaufen wurden. In diesem Fall werden die Testresultate nicht angezeigt.', +'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Falls der obere Test eine Fehler-Ticketnummer enthält deutet das auf einen Fehler in der Ausführung des Controllers hin, noch bevor der Doctest ausgeführt werden konnte. Gewöhnlich Führen fehlerhafte Einrückungen oder fehlerhafter Code ausserhalb der Funktion zu solchen Fehlern. Ein grüner Titel deutet darauf hin, dass alle Test(wenn sie vorhanden sind) erfolgreich durchlaufen wurden. In diesem Fall werden die Testresultate nicht angezeigt.', 'If you answer "yes", be patient, it may take a while to download': '', 'If you answer yes, be patient, it may take a while to download': 'If you answer yes, be patient, it may take a while to download', 'Import/Export': 'Importieren/Exportieren', 'includes': 'Einfügen', 'Index': 'Index', 'index': 'index', -'insert new': 'neu einfügen', -'insert new %s': 'neu einfügen %s', +'insert new': 'neu Einfügen', +'insert new %s': 'neu Einfügen %s', +'inspect attributes': 'inspect attributes', 'Install': 'installieren', 'Installed applications': 'Installierte Anwendungen', 'internal error': 'interner Fehler', @@ -175,9 +204,10 @@ 'Invalid email': 'Ungültige Email', 'invalid password': 'Ungültiges Passwort', 'Invalid Query': 'Ungültige Abfrage', -'invalid request': 'ungültige Anfrage', -'invalid ticket': 'ungültiges Ticket', +'invalid request': 'Ungültige Anfrage', +'invalid ticket': 'Ungültiges Ticket', 'Key bindings': 'Tastenbelegungen', +'Key bindings for ZenCoding Plugin': 'Key bindings for ZenCoding Plugin', 'Key bindings for ZenConding Plugin': 'Tastenbelegungen für das ZenConding Plugin', 'language file "%(filename)s" created/updated': 'Sprachdatei "%(filename)s" erstellt/aktualisiert', 'Language files (static strings) updated': 'Sprachdatei (statisch Strings) aktualisiert', @@ -189,6 +219,7 @@ 'Layout': 'Layout', 'License for': 'Lizenz für', 'loading...': 'lade...', +'locals': 'locals', 'located in the file': 'located in Datei', 'Login': 'Anmelden', 'login': 'anmelden', @@ -196,7 +227,8 @@ 'Logout': 'abmelden', 'Lost Password': 'Passwort vergessen', 'lost password?': 'Passwort vergessen?', -'Main Menu': 'Menú principal', +'Main Menu': 'Menü principal', +'Manage': 'Verwalten', 'Match Pair': 'Paare finden', 'Menu Model': 'Menü Modell', 'merge': 'verbinden', @@ -207,22 +239,28 @@ 'modules': 'Module', 'Name': 'Name', 'new application "%s" created': 'neue Anwendung "%s" erzeugt', -'New application wizard': 'New application wizard', +'New application wizard': 'Neue Anwendung per Assistent', +'new plugin installed': 'new plugin installed', 'New Record': 'Neuer Datensatz', 'new record inserted': 'neuer Datensatz eingefügt', -'New simple application': 'New simple application', +'New simple application': 'Neue einfache Anwendung', 'next 100 rows': 'nächsten 100 Zeilen', 'Next Edit Point': 'nächster Bearbeitungsschritt', 'NO': 'NEIN', 'No databases in this application': 'Keine Datenbank in dieser Anwendung', +'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder', +'online designer': 'online designer', +'or alternatively': 'or alternatively', +'Or Get from URL:': 'oder hole es von folgender URL:', 'or import from csv file': 'oder importieren von cvs Datei', 'or provide app url:': 'oder geben Sie eine Anwendungs-URL an:', 'or provide application url:': 'oder geben Sie eine Anwendungs-URL an:', 'Origin': 'Herkunft', -'Original/Translation': 'Original/Übersetzung', +'Original/Translation': 'Original/übersetzung', 'Overwrite installed app': 'installierte Anwendungen überschreiben', 'Pack all': 'verpacke alles', 'Pack compiled': 'Verpacke kompiliert', +'Pack custom': 'Verpacke individuell', 'pack plugin': 'Plugin verpacken', 'Password': 'Passwort', 'Peeking at file': 'Dateiansicht', @@ -230,9 +268,13 @@ 'Plugin "%s" in application': 'Plugin "%s" in Anwendung', 'plugins': 'plugins', 'Plugins': 'Plugins', +'Plural-Forms:': 'Plural-Forms:', 'Powered by': 'Unterstützt von', 'previous 100 rows': 'vorherige 100 zeilen', 'Previous Edit Point': 'vorheriger Bearbeitungsschritt', +'Private files': 'Private files', +'private files': 'private files', +'Project Progress': 'Projekt Fortschritt', 'Query:': 'Abfrage:', 'record': 'Datensatz', 'record does not exist': 'Datensatz existiert nicht', @@ -241,26 +283,39 @@ 'register': 'Registrierung', 'Register': 'registrieren', 'Registration key': 'Registrierungsschlüssel', -'Reload routes': 'Reload routes', -'Remove compiled': 'kompilat gelöscht', +'reload': 'Neu laden', +'Reload routes': 'Routen neu laden', +'Remove compiled': 'Bytecode löschen', +'Replace': 'Replace', +'Replace All': 'Replace All', +'request': 'request', 'Reset Password key': 'Passwortschlüssel zurücksetzen', 'Resolve Conflict file': 'bereinige Konflikt-Datei', +'response': 'Antwort', 'restore': 'wiederherstellen', 'revert': 'zurückkehren', 'Role': 'Rolle', 'Rows in table': 'Zeilen in Tabelle', 'Rows selected': 'Zeilen ausgewählt', -'Running on %s': 'Running on %s', +'rules are not defined': 'rules are not defined', +"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')", +'Running on %s': 'läuft auf %s', +'Save': 'Save', 'save': 'sichern', +'Save file:': 'Save file:', 'Save via Ajax': 'via Ajax sichern', 'Saved file hash:': 'Gespeicherter Datei-Hash:', +'Select Files to Package': 'Dateien zum Paketieren wählen', 'selected': 'ausgewählt(e)', -'session expired': 'Sitzung Abgelaufen', +'session': 'Sitzung', +'session expired': 'Sitzung abgelaufen', 'shell': 'shell', 'Site': 'Seite', 'some files could not be removed': 'einige Dateien konnten nicht gelöscht werden', -'Start wizard': 'start wizard', +'Start searching': 'Start searching', +'Start wizard': 'Assistent starten', 'state': 'Status', +'Static': 'Static', 'static': 'statische Dateien', 'Static files': 'statische Dateien', 'Stylesheet': 'Stylesheet', @@ -283,27 +338,35 @@ 'The data representation, define database tables and sets': 'The data representation, define database tables and sets', 'The output of the file is a dictionary that was rendered by the view': 'The output of the file is a dictionary that was rendered by the view', 'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates', -'the presentations layer, views are also known as templates': 'Die Präsentationsschicht, Views sind auch bekannt als Vorlagen/Templates', +'the presentations layer, views are also known as templates': 'Die präsentationsschicht, Views sind auch bekannt als Vorlagen/Templates', 'There are no controllers': 'Keine Controller vorhanden', 'There are no models': 'Keine Modelle vorhanden', 'There are no modules': 'Keine Module vorhanden', -'There are no plugins': 'There are no plugins', +'There are no plugins': 'Keine Plugins vorhanden', +'There are no private files': 'There are no private files', 'There are no static files': 'Keine statischen Dateien vorhanden', -'There are no translators, only default language is supported': 'Keine Übersetzungen vorhanden, nur die voreingestellte Sprache wird unterstützt', +'There are no translators, only default language is supported': 'Keine übersetzungen vorhanden, nur die voreingestellte Sprache wird Unterstützt', 'There are no views': 'Keine Views vorhanden', -'These files are served without processing, your images go here': 'These files are served without processing, your images go here', +'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app', +'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 is a copy of the scaffolding application': 'Dies ist eine Kopie einer Grundgerüst-Anwendung', 'This is the %(filename)s template': 'Dies ist das Template %(filename)s', 'Ticket': 'Ticket', -'Timestamp': 'Timestamp', +'Ticket ID': 'Ticket ID', +'Timestamp': 'Zeitstempel', 'TM': 'TM', 'to previous version.': 'zu einer früheren Version.', 'To create a plugin, name a file/folder plugin_[name]': 'Um ein Plugin zu erstellen benennen Sie eine(n) Datei/Ordner plugin_[Name]', -'translation strings for the application': 'Übersetzungs-Strings für die Anwendung', -'Translation strings for the application': 'Translation strings for the application', +'toggle breakpoint': 'toggle breakpoint', +'Toggle Fullscreen': 'Toggle Fullscreen', +'Traceback': 'Traceback', +'translation strings for the application': 'übersetzungs-Strings für die Anwendung', +'Translation strings for the application': 'übersetzungs-Strings für die Anwendung', 'try': 'versuche', 'try something like': 'versuche so etwas wie', +'Try the mobile interface': 'Try the mobile interface', +'try view': 'try view', 'Unable to check for upgrades': 'überprüfen von Upgrades nicht möglich', 'unable to create application "%s"': 'erzeugen von Anwendung "%s" nicht möglich', 'unable to delete file "%(filename)s"': 'löschen von Datein "%(filename)s" nicht möglich', @@ -311,37 +374,40 @@ 'Unable to download app': 'herunterladen der Anwendung nicht möglich', 'unable to parse csv file': 'analysieren der cvs Datei nicht möglich', 'unable to uninstall "%s"': 'deinstallieren von "%s" nicht möglich', -'uncheck all': 'alles demarkieren', +'uncheck all': 'Selektionen entfernen', 'Uninstall': 'deinstallieren', 'update': 'aktualisieren', 'update all languages': 'aktualisiere alle Sprachen', 'Update:': 'Aktualisiere:', 'upgrade web2py now': 'jetzt web2py upgraden', 'upload': 'upload', +'Upload': 'Upload', 'Upload & install packed application': 'Verpackte Anwendung hochladen und installieren', -'Upload a package:': 'Upload a package:', -'Upload and install packed application': 'Upload and install packed application', +'Upload a package:': 'Ein Packet hochladen:', +'Upload and install packed application': 'Verpackte Anwendung hochladen und installieren', 'upload application:': 'lade Anwendung hoch:', 'Upload existing application': 'lade existierende Anwendung hoch', 'upload file:': 'lade Datei hoch:', 'upload plugin file:': 'Plugin-Datei hochladen:', 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Benutze (...)&(...) für AND, (...)|(...) für OR, und ~(...) für NOT, um komplexe Abfragen zu erstellen.', -'Use an url:': 'Use an url:', -'user': 'user', +'Use an url:': 'Verwende URL:', +'user': 'Nutzer', 'User ID': 'Benutzer ID', -'variables': 'variables', +'variables': 'Variablen', 'Version': 'Version', 'Version %s.%s.%s (%s) %s': 'Version %s.%s.%s (%s) %s', 'versioning': 'Versionierung', -'View': 'View', -'view': 'View', -'Views': 'Views', -'views': 'Views', +'Versioning': 'Versionierung', +'View': 'Ansicht', +'view': 'Ansicht', +'Views': 'Ansichten', +'views': 'Ansichten', +'Web Framework': 'Web Framework', 'web2py is up to date': 'web2py ist auf dem neuesten Stand', 'web2py Recent Tweets': 'neuste Tweets von web2py', 'Welcome %s': 'Willkommen %s', 'Welcome to web2py': 'Willkommen zu web2py', -'Which called the function': 'Which called the function', +'Which called the function': 'welche die Funktion aufrief', 'Wrap with Abbreviation': 'mit Kürzel einhüllen', 'xml': 'xml', 'YES': 'JA', diff --git a/applications/admin/static/js/web2py.js b/applications/admin/static/js/web2py.js index d6a59c33..c7570263 100644 --- a/applications/admin/static/js/web2py.js +++ b/applications/admin/static/js/web2py.js @@ -1,242 +1,653 @@ -function popup(url) { - newwindow=window.open(url,'name','height=400,width=600'); - if (window.focus) newwindow.focus(); - return false; -} -function collapse(id) { jQuery('#'+id).slideToggle(); } -function fade(id,value) { if(value>0) jQuery('#'+id).hide().fadeIn('slow'); else jQuery('#'+id).show().fadeOut('slow'); } -function ajax(u,s,t) { - query = ''; - if (typeof s == "string") { - d = jQuery(s).serialize(); - if(d){ query = d; } - } else { +(function ($, undefined) { + /* + * Unobtrusive scripting adapter for jQuery, largely taken from + * the wonderful https://github.com/rails/jquery-ujs + * + * + * Released under the MIT license + * + */ + + String.prototype.reverse = function () { + return this.split('').reverse().join(''); + }; + var web2py; + + $.web2py = web2py = { + + popup: function (url) { + newwindow = window.open(url, 'name', 'height=400,width=600'); + if(window.focus) newwindow.focus(); + return false; + }, + collapse: function (id) { + $('#' + id).slideToggle(); + }, + fade: function (id, value) { + if(value > 0) $('#' + id).hide().fadeIn('slow'); + else $('#' + id).show().fadeOut('slow'); + }, + ajax: function (u, s, t) { + query = ''; + if(typeof s == "string") { + d = $(s).serialize(); + if(d) { + query = d; + } + } else { pcs = []; - if (s != null && s != undefined) for(i=0; i 0) { + query = pcs.join("&"); } - if (pcs.length>0){query = pcs.join("&");} - } - jQuery.ajax({type: "POST", url: u, data: query, success: function(msg) { if(t) { if(t==':eval') eval(msg); else if(typeof t=='string') jQuery("#"+t).html(msg); else t(msg); } } }); -} - -String.prototype.reverse = function () { return this.split('').reverse().join('');}; -function web2py_ajax_fields(target) { - var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; - var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; - jQuery("input.date",target).each(function() {Calendar.setup({inputField:this, ifFormat:date_format, showsTime:false });}); - jQuery("input.datetime",target).each(function() {Calendar.setup({inputField:this, ifFormat:datetime_format, showsTime: true, timeFormat: "24" });}); - jQuery("input.time",target).each(function(){jQuery(this).timeEntry();}); - -}; - -function web2py_ajax_init(target) { - jQuery('.hidden', target).hide(); - jQuery('.error', target).hide().slideDown('slow'); - web2py_ajax_fields(target); - web2py_show_if(target); -}; - -function web2py_event_handlers() { - var doc = jQuery(document) - doc.on('click', '.flash', function(e){var t=jQuery(this); if(t.css('top')=='0px') t.slideUp('slow'); else t.fadeOut(); e.preventDefault();}); - doc.on('keyup', 'input.integer', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();}); - doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();}); - var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?"; - doc.on('click', "input[type='checkbox'].delete", function(){if(this.checked) if(!confirm(confirm_message)) this.checked=false;}); - - doc.ajaxSuccess(function(e, xhr) { - var redirect=xhr.getResponseHeader('web2py-redirect-location'); - var command=xhr.getResponseHeader('web2py-component-command'); - var flash=xhr.getResponseHeader('web2py-component-flash'); - if (redirect !== null) { - window.location = redirect; - }; - if(command !== null){ - eval(decodeURIComponent(command)); - } - if(flash) { - jQuery('.flash') - .html(decodeURIComponent(flash)) - .append('×') - .slideDown(); - } - }); - - doc.ajaxError(function(e, xhr, settings, exception) { - doc.off('click', '.flash') - switch(xhr.status){ - case 500: - $('.flash').html(ajax_error_500).slideDown(); } - }); -}; - -jQuery(function() { - var flash = jQuery('.flash'); - flash.hide(); - if(flash.html()) flash.append('×').slideDown(); - web2py_ajax_init(document); - web2py_event_handlers(); -}); - -function web2py_trap_form(action,target) { - jQuery('#'+target+' form').each(function(i){ - var form=jQuery(this); - if(!form.hasClass('no_trap')) - form.submit(function(e){ - jQuery('.flash').hide().html(''); - web2py_ajax_page('post',action,form.serialize(),target); - e.preventDefault(); + $.ajax({ + type: "POST", + url: u, + data: query, + success: function (msg) { + if(t) { + if(t == ':eval') eval(msg); + else if(typeof t == 'string') $("#" + t).html(msg); + else t(msg); + } + } }); - }); -} - -function web2py_trap_link(target) { - jQuery('#'+target+' a.w2p_trap').each(function(i){ - var link=jQuery(this); - link.click(function(e) { - jQuery('.flash').hide().html(''); - web2py_ajax_page('get',link.attr('href'),[],target); - e.preventDefault(); - }); + }, + ajax_fields: function (target) { + /*this attaches something to a newly loaded fragment/page + * Ideally all events should be bound to the document, so we can avoid calling + * this over and over... all will be bound to the document + */ + var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; + var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; + $("input.date", target).each(function () { + Calendar.setup({ + inputField: this, + ifFormat: date_format, + showsTime: false }); -} + }); + $("input.datetime", target).each(function () { + Calendar.setup({ + inputField: this, + ifFormat: datetime_format, + showsTime: true, + timeFormat: "24" + }); + }); + $("input.time", target).each(function () { + $(this).timeEntry(); + }); + /*adds btn class to buttons*/ + $('button', target).addClass('btn'); + $('form input[type="submit"], form input[type="button"]', target).addClass('btn'); + /*no more inline javascript for PasswordWidget*/ + $('input[type=password][data-w2p_entropy]', target).each(function () { + web2py.validate_entropy($(this)); + }); + /*no more inline javascript for ListWidget*/ + $('ul.w2p_list', target).each(function () { + function pe(ul, e) { + var new_line = ml(ul); + rel(ul); + if($(e.target).parent().is(':visible')) { + //make sure we didn't delete the element before we insert after + new_line.insertAfter($(e.target).parent()); + } else { + //the line we clicked on was deleted, just add to end of list + new_line.appendTo(ul); + } + new_line.find(":text").focus(); + return false; + } -function web2py_ajax_page(method, action, data, target) { - jQuery.ajax({'type':method, 'url':action, 'data':data, - 'beforeSend':function(xhr) { - xhr.setRequestHeader('web2py-component-location', document.location); - xhr.setRequestHeader('web2py-component-element', target);}, - 'complete':function(xhr,text){ - var html=xhr.responseText; - var content=xhr.getResponseHeader('web2py-component-content'); - var t = jQuery('#'+target); - if(content=='prepend') t.prepend(html); - else if(content=='append') t.append(html); - else if(content!='hide') t.html(html); - web2py_trap_form(action,target); - web2py_trap_link(target); - web2py_ajax_init('#'+target); - } - }); -} + function rl(ul, e) { + if($(ul).children().length > 1) { + //only remove if we have more than 1 item so the list is never empty + $(e.target).parent().remove(); + } + } -function web2py_component(action, target, timeout, times){ - jQuery(function(){ - var jelement = jQuery("#" + target); - var element = jelement.get(0); - var statement = "jQuery('#" + target + "').get(0).reload();"; - jelement.reload = function (){ - // Continue if times is Infinity or - // the times limit is not reached - if (element.reload_check()){ - web2py_ajax_page('get', action, null, target);} }; // reload - // Method to check timing limit - element.reload_check = function (){ - if (jelement.hasClass('w2p_component_stop')) {clearInterval(this.timing);return false;} - if (this.reload_counter == Infinity){return true;} - else { - if (!isNaN(this.reload_counter)){ - this.reload_counter -= 1; - if (this.reload_counter < 0){ - if (!this.run_once){ - clearInterval(this.timing); - return false; - } + function ml(ul) { + var line = $(ul).find("li:first").clone(true); + line.find(':text').val(''); + return line; + } + + function rel(ul) { + $(ul).find("li").each(function () { + var trimmed = $.trim($(this.firstChild).val()); + if(trimmed == '') $(this).remove(); + else $(this.firstChild).val(trimmed); + }); + } + var ul = this; + $(ul).find(":text").after('+ -').keypress(function (e) { + return(e.which == 13) ? pe(ul, e) : true; + }).next().click(function (e) { + pe(ul, e); + e.preventDefault(); + }).next().click(function (e) { + rl(ul, e); + e.preventDefault(); + }); + }); + }, + ajax_init: function (target) { + $('.hidden', target).hide(); + web2py.manage_errors(target); + web2py.ajax_fields(target); + web2py.show_if_handler(target); + web2py.component_handler(target); + }, + //manage errors in forms + manage_errors: function(target) { + $('.error', target).hide().slideDown('slow'); + //$('.error', target).hide().fadeIn('slow'); + }, + event_handlers: function () { + /* This is called once for page + * Ideally it should bound all the things that are needed + */ + var doc = $(document); + doc.on('click', '.flash', function (e) { + var t = $(this); + if(t.css('top') == '0px') t.slideUp('slow'); + else t.fadeOut(); + //if I want to display a clickable something + //inside flash, I should not be prevented to follow it + //e.preventDefault(); + }); + doc.on('keyup', 'input.integer', function () { + this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse(); + }); + doc.on('keyup', 'input.double, input.decimal', function () { + this.value = this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g, '').reverse(); + }); + var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?"; + doc.on('click', "input[type='checkbox'].delete", function () { + if(this.checked) if(!web2py.confirm(confirm_message)) this.checked = false; + }); + + doc.ajaxSuccess(function (e, xhr) { + var redirect = xhr.getResponseHeader('web2py-redirect-location'); + var command = xhr.getResponseHeader('web2py-component-command'); + var flash = xhr.getResponseHeader('web2py-component-flash'); + if(redirect !== null) { + window.location = redirect; + }; + if(command !== null) { + eval(decodeURIComponent(command)); + } + if(flash) { + web2py.flash(decodeURIComponent(flash)) + } + }); + + doc.ajaxError(function (e, xhr, settings, exception) { + //personally I don't like it. + //if there's an error it it flashed and can be removed + //as any other message + //doc.off('click', '.flash') + switch(xhr.status) { + case 500: + web2py.flash(ajax_error_500); + } + }); + + }, + trap_form: function (action, target) { + $('#' + target + ' form').each(function (i) { + var form = $(this); + form.attr('data-w2p_target', target); + if(!form.hasClass('no_trap')) { + //should be there by default ? + form.find('input[type=submit]').attr('data-w2p_disable_with', 'Working...'); + form.submit(function (e) { + web2py.hide_flash(); + web2py.ajax_page('post', action, form.serialize(), target, form); + e.preventDefault(); + }); + } + }); + }, + trap_link: function (target) { + $('#' + target + ' a.w2p_trap').each(function (i) { + var link = $(this); + link.click(function (e) { + web2py.hide_flash(); + web2py.ajax_page('get', link.attr('href'), [], target); + e.preventDefault(); + }); + }); + }, + ajax_page: function (method, action, data, target, element) { + //element is a new parameter, but should be put be put in front + if(element == undefined) element = $(document); + if(web2py.fire(element, 'ajax:before')) { //test a usecase, should stop here if returns false + $.ajax({ + 'type': method, + 'url': action, + 'data': data, + 'beforeSend': function (xhr, settings) { + //added + xhr.setRequestHeader('web2py-component-location', document.location); + xhr.setRequestHeader('web2py-component-element', target); + return web2py.fire(element, 'ajax:beforeSend', [xhr, settings]); //test a usecase, should stop here if returns false + }, + //added + 'success': function (data, status, xhr) { + //bummer for form submissions....the element is not there after complete + //because it gets replaced by the new response.... + element.trigger('ajax:success', [data, status, xhr]); + }, + //added + 'error': function (xhr, status, error) { + //bummer for form submissions....in addition to the element being not there after + //complete because it gets replaced by the new response, standard form + //handling just returns the same status code for good and bad + //form submissions (i.e. that triggered a validator error) + element.trigger('ajax:error', [xhr, status, error]); + }, + 'complete': function (xhr, status) { + element.trigger('ajax:complete', [xhr, status]); + var html = xhr.responseText; + var content = xhr.getResponseHeader('web2py-component-content'); + var t = $('#' + target); + if(content == 'prepend') t.prepend(html); + else if(content == 'append') t.append(html); + else if(content != 'hide') t.html(html); + web2py.trap_form(action, target); + web2py.trap_link(target); + web2py.ajax_init('#' + target); + } + }); + } + }, + component: function (action, target, timeout, times, el) { + //element is a new parameter, but should be put in front + $(function () { + var jelement = $("#" + target); + var element = jelement.get(0); + var statement = "jQuery('#" + target + "').get(0).reload();"; + element.reload = function () { + // Continue if times is Infinity or + // the times limit is not reached + if(element.reload_check()) { + web2py.ajax_page('get', action, null, target, el); + } + }; // reload + // Method to check timing limit + element.reload_check = function () { + if(jelement.hasClass('w2p_component_stop')) { + clearInterval(this.timing); + return false; + } + if(this.reload_counter == Infinity) { + return true; + } else { + if(!isNaN(this.reload_counter)) { + this.reload_counter -= 1; + if(this.reload_counter < 0) { + if(!this.run_once) { + clearInterval(this.timing); + return false; } - else{return true;} - } } - return false;}; // reload check - if (!isNaN(timeout)){ - element.timeout = timeout; - element.reload_counter = times; - if (times > 1){ - // Multiple or infinite reload - // Run first iteration - web2py_ajax_page('get', action, null, target); - element.run_once = false; - element.timing = setInterval(statement, timeout); - element.reload_counter -= 1; + } else { + return true; + } + } + } + return false; + }; // reload check + if(!isNaN(timeout)) { + element.timeout = timeout; + element.reload_counter = times; + if(times > 1) { + // Multiple or infinite reload + // Run first iteration + web2py.ajax_page('get', action, null, target, el); + element.run_once = false; + element.timing = setInterval(statement, timeout); + element.reload_counter -= 1; + } else if(times == 1) { + // Run once with timeout + element.run_once = true; + element.setTimeout = setTimeout; + element.timing = setTimeout(statement, timeout); + } + } else { + // run once (no timeout specified) + element.reload_counter = Infinity; + web2py.ajax_page('get', action, null, target, el); } - else if (times == 1) { - // Run once with timeout - element.run_once = true; - element.setTimeout = setTimeout; - element.timing = setTimeout(statement, timeout); + }); + }, + calc_entropy: function (mystring) { + //calculate a simple entropy for a given string + var csets = new Array( + 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/', + '0123456789abcdefghijklmnopqrstuvwxyz'); + var score = 0, + other = {}, seen = {}, lastset = null, + mystringlist = mystring.split(''); + for(var i = 0; i < mystringlist.length; i++) { // classify this character + var c = mystringlist[i], + inset = 5; + for(var j = 0; j < csets.length; j++) + if(csets[j].indexOf(c) != -1) { + inset = j; + break; + } + //calculate effect of character on alphabet size + if(!(inset in seen)) { + seen[inset] = 1; + score += csets[inset].length; + } else if(!(c in other)) { + score += 1; + other[c] = 1; } - } else { - // run once (no timeout specified) - element.reload_counter = Infinity; - web2py_ajax_page('get', action, null, target); - } }); } + if(inset != lastset) { + score += 1; + lastset = inset; + } + } + var entropy = mystring.length * Math.log(score) / 0.6931471805599453; + return Math.round(entropy * 100) / 100 + }, + validate_entropy: function (myfield, req_entropy) { + //added + if(myfield.data('w2p_entropy') != undefined) req_entropy = myfield.data('w2p_entropy'); + var validator = function () { + var v = (web2py.calc_entropy(myfield.val()) || 0) / req_entropy; + var r = 0, + g = 0, + b = 0, + rs = function (x) { + return Math.round(x * 15).toString(16) + }; + if(v <= 0.5) { + r = 1.0; + g = 2.0 * v; + } else { + r = (1.0 - 2.0 * (Math.max(v, 0) - 0.5)); + g = 1.0; + } + var color = '#' + rs(r) + rs(g) + rs(b); + myfield.css('background-color', color); + entropy_callback = myfield.data('entropy_callback'); + if(entropy_callback) entropy_callback(v); + } + if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check'); + }, + web2py_websocket: function (url, onmessage, onopen, onclose) { + if("WebSocket" in window) { + var ws = new WebSocket(url); + ws.onopen = onopen ? onopen : (function () {}); + ws.onmessage = onmessage; + ws.onclose = onclose ? onclose : (function () {}); + return true; // supported + } else return false; // not supported + }, + /* new from here */ + // Form input elements bound by jquery-ujs + formInputClickSelector: 'input[type=submit], input[type=image], button[type=submit], button:not([type])', + // Form input elements disabled during form submission + disableSelector: 'input, button, textarea, select', + // Form input elements re-enabled after form submission + enableSelector: 'input:disabled, button:disabled, textarea:disabled, select:disabled', + // Triggers an event on an element and returns false if the event result is false + fire: function (obj, name, data) { + var event = $.Event(name); + obj.trigger(event, data); + return event.result !== false; + }, + // Helper function, needed to provide consistent behavior in IE + stopEverything: function (e) { + $(e.target).trigger('w2p:everythingStopped'); + e.stopImmediatePropagation(); + return false; + }, + confirm: function (message) { + return confirm(message); + }, + // replace element's html with the 'data-disable-with' after storing original html + // and prevent clicking on it + disableElement: function (el) { + el.addClass('disabled'); + var method = el.prop('type') == 'submit' ? 'val' : 'html'; + // store enabled state + el.data('w2p:enable-with', el[method]); + /* little addition by default*/ + if((el.data('w2p_disable_with') == 'default') || (el.data('w2p_disable_with') === undefined)) { + el.data('w2p_disable_with', 'Working...'); + } + // set to disabled state + el[method](el.data('w2p_disable_with')); -function web2py_websocket(url,onmessage,onopen,onclose) { - if ("WebSocket" in window) { - var ws = new WebSocket(url); - ws.onopen = onopen?onopen:(function(){}); - ws.onmessage = onmessage; - ws.onclose = onclose?onclose:(function(){}); - return true; // supported - } else return false; // not supported -} + el.bind('click.w2pDisable', function (e) { // prevent further clicking + return web2py.stopEverything(e); + }); + }, + // restore element to its original state which was disabled by 'disableElement' above + enableElement: function (el) { + var method = el.prop('type') == 'submit' ? 'val' : 'html'; + if(el.data('w2p:enable-with') !== undefined) { + // set to old enabled state + el[method](el.data('w2p:enable-with')); + el.removeData('w2p:enable-with'); // clean up cache + } + el.removeClass('disabled'); + el.unbind('click.w2pDisable'); // enable element + }, + //convenience wrapper, internal use only + simple_component: function (action, target, element) { + web2py.component(action, target, 0, 1, element); + }, + //helper for flash messages + flash: function(message, status) { + var flash = $('.flash'); + web2py.hide_flash(); + flash.html(message).addClass(status); + if(flash.html()) flash.append(' × ').slideDown(); + }, + hide_flash: function() { + $('.flash').hide().html(''); + }, + show_if_handler: function(target) { + var triggers = {}; + var show_if = function () { + var t = $(this); + var id = t.attr('id'); + t.attr('value', t.val()); + for(var k = 0; k < triggers[id].length; k++) { + var dep = $('#' + triggers[id][k], target); + var tr = $('#' + triggers[id][k] + '__row', target); + if(t.is(dep.attr('data-show-if'))) tr.slideDown(); + else tr.hide(); + } + }; + $('[data-show-trigger]', target).each(function () { + var name = $(this).attr('data-show-trigger'); + if(!triggers[name]) triggers[name] = []; + triggers[name].push($(this).attr('id')); + }); + for(var name in triggers) { + $('#' + name, target).change(show_if).keyup(show_if); + show_if.call($('#' + name, target)); + }; + }, + component_handler : function (target) { + $('div[data-w2p_remote]', target).each(function () { + var remote, times, timeout, target; + var el = $(this); + remote = el.data('w2p_remote'); + times = el.data('w2p_times'); + timeout = el.data('w2p_timeout'); + target = el.attr('id'); + web2py.component(remote, target, timeout, times, $(this)); + } + ) + }, + a_handler: function (el, e) { + e.preventDefault(); + var method = el.data('w2p_method'); + var action = el.attr('href'); + var target = el.data('w2p_target'); + var confirm_message = el.data('w2p_confirm'); -function web2py_calc_entropy(mystring) { - //calculate a simple entropy for a given string - var csets = new Array( - 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/', - '0123456789abcdefghijklmnopqrstuvwxyz'); - var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split(''); - for (var i=0;i0) jQuery('#'+id).hide().fadeIn('slow'); else jQuery('#'+id).show().fadeOut('slow'); } -function ajax(u,s,t) { - query = ''; - if (typeof s == "string") { - d = jQuery(s).serialize(); - if(d){ query = d; } - } else { +(function ($, undefined) { + /* + * Unobtrusive scripting adapter for jQuery, largely taken from + * the wonderful https://github.com/rails/jquery-ujs + * + * + * Released under the MIT license + * + */ + + String.prototype.reverse = function () { + return this.split('').reverse().join(''); + }; + var web2py; + + $.web2py = web2py = { + + popup: function (url) { + newwindow = window.open(url, 'name', 'height=400,width=600'); + if(window.focus) newwindow.focus(); + return false; + }, + collapse: function (id) { + $('#' + id).slideToggle(); + }, + fade: function (id, value) { + if(value > 0) $('#' + id).hide().fadeIn('slow'); + else $('#' + id).show().fadeOut('slow'); + }, + ajax: function (u, s, t) { + query = ''; + if(typeof s == "string") { + d = $(s).serialize(); + if(d) { + query = d; + } + } else { pcs = []; - if (s != null && s != undefined) for(i=0; i 0) { + query = pcs.join("&"); } - if (pcs.length>0){query = pcs.join("&");} - } - jQuery.ajax({type: "POST", url: u, data: query, success: function(msg) { if(t) { if(t==':eval') eval(msg); else if(typeof t=='string') jQuery("#"+t).html(msg); else t(msg); } } }); -} - -String.prototype.reverse = function () { return this.split('').reverse().join('');}; -function web2py_ajax_fields(target) { - var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; - var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; - jQuery("input.date",target).each(function() {Calendar.setup({inputField:this, ifFormat:date_format, showsTime:false });}); - jQuery("input.datetime",target).each(function() {Calendar.setup({inputField:this, ifFormat:datetime_format, showsTime: true, timeFormat: "24" });}); - jQuery("input.time",target).each(function(){jQuery(this).timeEntry();}); - -}; - -function web2py_ajax_init(target) { - jQuery('.hidden', target).hide(); - jQuery('.error', target).hide().slideDown('slow'); - web2py_ajax_fields(target); - web2py_show_if(target); -}; - -function web2py_event_handlers() { - var doc = jQuery(document) - doc.on('click', '.flash', function(e){var t=jQuery(this); if(t.css('top')=='0px') t.slideUp('slow'); else t.fadeOut(); e.preventDefault();}); - doc.on('keyup', 'input.integer', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();}); - doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();}); - var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?"; - doc.on('click', "input[type='checkbox'].delete", function(){if(this.checked) if(!confirm(confirm_message)) this.checked=false;}); - - doc.ajaxSuccess(function(e, xhr) { - var redirect=xhr.getResponseHeader('web2py-redirect-location'); - var command=xhr.getResponseHeader('web2py-component-command'); - var flash=xhr.getResponseHeader('web2py-component-flash'); - if (redirect !== null) { - window.location = redirect; - }; - if(command !== null){ - eval(decodeURIComponent(command)); - } - if(flash) { - jQuery('.flash') - .html(decodeURIComponent(flash)) - .append('×') - .slideDown(); - } - }); - - doc.ajaxError(function(e, xhr, settings, exception) { - doc.off('click', '.flash') - switch(xhr.status){ - case 500: - $('.flash').html(ajax_error_500).slideDown(); } - }); -}; - -jQuery(function() { - var flash = jQuery('.flash'); - flash.hide(); - if(flash.html()) flash.append('×').slideDown(); - web2py_ajax_init(document); - web2py_event_handlers(); -}); - -function web2py_trap_form(action,target) { - jQuery('#'+target+' form').each(function(i){ - var form=jQuery(this); - if(!form.hasClass('no_trap')) - form.submit(function(e){ - jQuery('.flash').hide().html(''); - web2py_ajax_page('post',action,form.serialize(),target); - e.preventDefault(); + $.ajax({ + type: "POST", + url: u, + data: query, + success: function (msg) { + if(t) { + if(t == ':eval') eval(msg); + else if(typeof t == 'string') $("#" + t).html(msg); + else t(msg); + } + } }); - }); -} - -function web2py_trap_link(target) { - jQuery('#'+target+' a.w2p_trap').each(function(i){ - var link=jQuery(this); - link.click(function(e) { - jQuery('.flash').hide().html(''); - web2py_ajax_page('get',link.attr('href'),[],target); - e.preventDefault(); - }); + }, + ajax_fields: function (target) { + /*this attaches something to a newly loaded fragment/page + * Ideally all events should be bound to the document, so we can avoid calling + * this over and over... all will be bound to the document + */ + var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; + var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; + $("input.date", target).each(function () { + Calendar.setup({ + inputField: this, + ifFormat: date_format, + showsTime: false }); -} + }); + $("input.datetime", target).each(function () { + Calendar.setup({ + inputField: this, + ifFormat: datetime_format, + showsTime: true, + timeFormat: "24" + }); + }); + $("input.time", target).each(function () { + $(this).timeEntry(); + }); + /*adds btn class to buttons*/ + $('button', target).addClass('btn'); + $('form input[type="submit"], form input[type="button"]', target).addClass('btn'); + /*no more inline javascript for PasswordWidget*/ + $('input[type=password][data-w2p_entropy]', target).each(function () { + web2py.validate_entropy($(this)); + }); + /*no more inline javascript for ListWidget*/ + $('ul.w2p_list', target).each(function () { + function pe(ul, e) { + var new_line = ml(ul); + rel(ul); + if($(e.target).parent().is(':visible')) { + //make sure we didn't delete the element before we insert after + new_line.insertAfter($(e.target).parent()); + } else { + //the line we clicked on was deleted, just add to end of list + new_line.appendTo(ul); + } + new_line.find(":text").focus(); + return false; + } -function web2py_ajax_page(method, action, data, target) { - jQuery.ajax({'type':method, 'url':action, 'data':data, - 'beforeSend':function(xhr) { - xhr.setRequestHeader('web2py-component-location', document.location); - xhr.setRequestHeader('web2py-component-element', target);}, - 'complete':function(xhr,text){ - var html=xhr.responseText; - var content=xhr.getResponseHeader('web2py-component-content'); - var t = jQuery('#'+target); - if(content=='prepend') t.prepend(html); - else if(content=='append') t.append(html); - else if(content!='hide') t.html(html); - web2py_trap_form(action,target); - web2py_trap_link(target); - web2py_ajax_init('#'+target); - } - }); -} + function rl(ul, e) { + if($(ul).children().length > 1) { + //only remove if we have more than 1 item so the list is never empty + $(e.target).parent().remove(); + } + } -function web2py_component(action, target, timeout, times){ - jQuery(function(){ - var jelement = jQuery("#" + target); - var element = jelement.get(0); - var statement = "jQuery('#" + target + "').get(0).reload();"; - jelement.reload = function (){ - // Continue if times is Infinity or - // the times limit is not reached - if (element.reload_check()){ - web2py_ajax_page('get', action, null, target);} }; // reload - // Method to check timing limit - element.reload_check = function (){ - if (jelement.hasClass('w2p_component_stop')) {clearInterval(this.timing);return false;} - if (this.reload_counter == Infinity){return true;} - else { - if (!isNaN(this.reload_counter)){ - this.reload_counter -= 1; - if (this.reload_counter < 0){ - if (!this.run_once){ - clearInterval(this.timing); - return false; - } + function ml(ul) { + var line = $(ul).find("li:first").clone(true); + line.find(':text').val(''); + return line; + } + + function rel(ul) { + $(ul).find("li").each(function () { + var trimmed = $.trim($(this.firstChild).val()); + if(trimmed == '') $(this).remove(); + else $(this.firstChild).val(trimmed); + }); + } + var ul = this; + $(ul).find(":text").after('+ -').keypress(function (e) { + return(e.which == 13) ? pe(ul, e) : true; + }).next().click(function (e) { + pe(ul, e); + e.preventDefault(); + }).next().click(function (e) { + rl(ul, e); + e.preventDefault(); + }); + }); + }, + ajax_init: function (target) { + $('.hidden', target).hide(); + web2py.manage_errors(target); + web2py.ajax_fields(target); + web2py.show_if_handler(target); + web2py.component_handler(target); + }, + //manage errors in forms + manage_errors: function(target) { + $('.error', target).hide().slideDown('slow'); + //$('.error', target).hide().fadeIn('slow'); + }, + event_handlers: function () { + /* This is called once for page + * Ideally it should bound all the things that are needed + */ + var doc = $(document); + doc.on('click', '.flash', function (e) { + var t = $(this); + if(t.css('top') == '0px') t.slideUp('slow'); + else t.fadeOut(); + //if I want to display a clickable something + //inside flash, I should not be prevented to follow it + //e.preventDefault(); + }); + doc.on('keyup', 'input.integer', function () { + this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse(); + }); + doc.on('keyup', 'input.double, input.decimal', function () { + this.value = this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g, '').reverse(); + }); + var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?"; + doc.on('click', "input[type='checkbox'].delete", function () { + if(this.checked) if(!web2py.confirm(confirm_message)) this.checked = false; + }); + + doc.ajaxSuccess(function (e, xhr) { + var redirect = xhr.getResponseHeader('web2py-redirect-location'); + var command = xhr.getResponseHeader('web2py-component-command'); + var flash = xhr.getResponseHeader('web2py-component-flash'); + if(redirect !== null) { + window.location = redirect; + }; + if(command !== null) { + eval(decodeURIComponent(command)); + } + if(flash) { + web2py.flash(decodeURIComponent(flash)) + } + }); + + doc.ajaxError(function (e, xhr, settings, exception) { + //personally I don't like it. + //if there's an error it it flashed and can be removed + //as any other message + //doc.off('click', '.flash') + switch(xhr.status) { + case 500: + web2py.flash(ajax_error_500); + } + }); + + }, + trap_form: function (action, target) { + $('#' + target + ' form').each(function (i) { + var form = $(this); + form.attr('data-w2p_target', target); + if(!form.hasClass('no_trap')) { + //should be there by default ? + form.find('input[type=submit]').attr('data-w2p_disable_with', 'Working...'); + form.submit(function (e) { + web2py.hide_flash(); + web2py.ajax_page('post', action, form.serialize(), target, form); + e.preventDefault(); + }); + } + }); + }, + trap_link: function (target) { + $('#' + target + ' a.w2p_trap').each(function (i) { + var link = $(this); + link.click(function (e) { + web2py.hide_flash(); + web2py.ajax_page('get', link.attr('href'), [], target); + e.preventDefault(); + }); + }); + }, + ajax_page: function (method, action, data, target, element) { + //element is a new parameter, but should be put be put in front + if(element == undefined) element = $(document); + if(web2py.fire(element, 'ajax:before')) { //test a usecase, should stop here if returns false + $.ajax({ + 'type': method, + 'url': action, + 'data': data, + 'beforeSend': function (xhr, settings) { + //added + xhr.setRequestHeader('web2py-component-location', document.location); + xhr.setRequestHeader('web2py-component-element', target); + return web2py.fire(element, 'ajax:beforeSend', [xhr, settings]); //test a usecase, should stop here if returns false + }, + //added + 'success': function (data, status, xhr) { + //bummer for form submissions....the element is not there after complete + //because it gets replaced by the new response.... + element.trigger('ajax:success', [data, status, xhr]); + }, + //added + 'error': function (xhr, status, error) { + //bummer for form submissions....in addition to the element being not there after + //complete because it gets replaced by the new response, standard form + //handling just returns the same status code for good and bad + //form submissions (i.e. that triggered a validator error) + element.trigger('ajax:error', [xhr, status, error]); + }, + 'complete': function (xhr, status) { + element.trigger('ajax:complete', [xhr, status]); + var html = xhr.responseText; + var content = xhr.getResponseHeader('web2py-component-content'); + var t = $('#' + target); + if(content == 'prepend') t.prepend(html); + else if(content == 'append') t.append(html); + else if(content != 'hide') t.html(html); + web2py.trap_form(action, target); + web2py.trap_link(target); + web2py.ajax_init('#' + target); + } + }); + } + }, + component: function (action, target, timeout, times, el) { + //element is a new parameter, but should be put in front + $(function () { + var jelement = $("#" + target); + var element = jelement.get(0); + var statement = "jQuery('#" + target + "').get(0).reload();"; + element.reload = function () { + // Continue if times is Infinity or + // the times limit is not reached + if(element.reload_check()) { + web2py.ajax_page('get', action, null, target, el); + } + }; // reload + // Method to check timing limit + element.reload_check = function () { + if(jelement.hasClass('w2p_component_stop')) { + clearInterval(this.timing); + return false; + } + if(this.reload_counter == Infinity) { + return true; + } else { + if(!isNaN(this.reload_counter)) { + this.reload_counter -= 1; + if(this.reload_counter < 0) { + if(!this.run_once) { + clearInterval(this.timing); + return false; } - else{return true;} - } } - return false;}; // reload check - if (!isNaN(timeout)){ - element.timeout = timeout; - element.reload_counter = times; - if (times > 1){ - // Multiple or infinite reload - // Run first iteration - web2py_ajax_page('get', action, null, target); - element.run_once = false; - element.timing = setInterval(statement, timeout); - element.reload_counter -= 1; + } else { + return true; + } + } + } + return false; + }; // reload check + if(!isNaN(timeout)) { + element.timeout = timeout; + element.reload_counter = times; + if(times > 1) { + // Multiple or infinite reload + // Run first iteration + web2py.ajax_page('get', action, null, target, el); + element.run_once = false; + element.timing = setInterval(statement, timeout); + element.reload_counter -= 1; + } else if(times == 1) { + // Run once with timeout + element.run_once = true; + element.setTimeout = setTimeout; + element.timing = setTimeout(statement, timeout); + } + } else { + // run once (no timeout specified) + element.reload_counter = Infinity; + web2py.ajax_page('get', action, null, target, el); } - else if (times == 1) { - // Run once with timeout - element.run_once = true; - element.setTimeout = setTimeout; - element.timing = setTimeout(statement, timeout); + }); + }, + calc_entropy: function (mystring) { + //calculate a simple entropy for a given string + var csets = new Array( + 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/', + '0123456789abcdefghijklmnopqrstuvwxyz'); + var score = 0, + other = {}, seen = {}, lastset = null, + mystringlist = mystring.split(''); + for(var i = 0; i < mystringlist.length; i++) { // classify this character + var c = mystringlist[i], + inset = 5; + for(var j = 0; j < csets.length; j++) + if(csets[j].indexOf(c) != -1) { + inset = j; + break; + } + //calculate effect of character on alphabet size + if(!(inset in seen)) { + seen[inset] = 1; + score += csets[inset].length; + } else if(!(c in other)) { + score += 1; + other[c] = 1; } - } else { - // run once (no timeout specified) - element.reload_counter = Infinity; - web2py_ajax_page('get', action, null, target); - } }); } + if(inset != lastset) { + score += 1; + lastset = inset; + } + } + var entropy = mystring.length * Math.log(score) / 0.6931471805599453; + return Math.round(entropy * 100) / 100 + }, + validate_entropy: function (myfield, req_entropy) { + //added + if(myfield.data('w2p_entropy') != undefined) req_entropy = myfield.data('w2p_entropy'); + var validator = function () { + var v = (web2py.calc_entropy(myfield.val()) || 0) / req_entropy; + var r = 0, + g = 0, + b = 0, + rs = function (x) { + return Math.round(x * 15).toString(16) + }; + if(v <= 0.5) { + r = 1.0; + g = 2.0 * v; + } else { + r = (1.0 - 2.0 * (Math.max(v, 0) - 0.5)); + g = 1.0; + } + var color = '#' + rs(r) + rs(g) + rs(b); + myfield.css('background-color', color); + entropy_callback = myfield.data('entropy_callback'); + if(entropy_callback) entropy_callback(v); + } + if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check'); + }, + web2py_websocket: function (url, onmessage, onopen, onclose) { + if("WebSocket" in window) { + var ws = new WebSocket(url); + ws.onopen = onopen ? onopen : (function () {}); + ws.onmessage = onmessage; + ws.onclose = onclose ? onclose : (function () {}); + return true; // supported + } else return false; // not supported + }, + /* new from here */ + // Form input elements bound by jquery-ujs + formInputClickSelector: 'input[type=submit], input[type=image], button[type=submit], button:not([type])', + // Form input elements disabled during form submission + disableSelector: 'input, button, textarea, select', + // Form input elements re-enabled after form submission + enableSelector: 'input:disabled, button:disabled, textarea:disabled, select:disabled', + // Triggers an event on an element and returns false if the event result is false + fire: function (obj, name, data) { + var event = $.Event(name); + obj.trigger(event, data); + return event.result !== false; + }, + // Helper function, needed to provide consistent behavior in IE + stopEverything: function (e) { + $(e.target).trigger('w2p:everythingStopped'); + e.stopImmediatePropagation(); + return false; + }, + confirm: function (message) { + return confirm(message); + }, + // replace element's html with the 'data-disable-with' after storing original html + // and prevent clicking on it + disableElement: function (el) { + el.addClass('disabled'); + var method = el.prop('type') == 'submit' ? 'val' : 'html'; + // store enabled state + el.data('w2p:enable-with', el[method]); + /* little addition by default*/ + if((el.data('w2p_disable_with') == 'default') || (el.data('w2p_disable_with') === undefined)) { + el.data('w2p_disable_with', 'Working...'); + } + // set to disabled state + el[method](el.data('w2p_disable_with')); -function web2py_websocket(url,onmessage,onopen,onclose) { - if ("WebSocket" in window) { - var ws = new WebSocket(url); - ws.onopen = onopen?onopen:(function(){}); - ws.onmessage = onmessage; - ws.onclose = onclose?onclose:(function(){}); - return true; // supported - } else return false; // not supported -} + el.bind('click.w2pDisable', function (e) { // prevent further clicking + return web2py.stopEverything(e); + }); + }, + // restore element to its original state which was disabled by 'disableElement' above + enableElement: function (el) { + var method = el.prop('type') == 'submit' ? 'val' : 'html'; + if(el.data('w2p:enable-with') !== undefined) { + // set to old enabled state + el[method](el.data('w2p:enable-with')); + el.removeData('w2p:enable-with'); // clean up cache + } + el.removeClass('disabled'); + el.unbind('click.w2pDisable'); // enable element + }, + //convenience wrapper, internal use only + simple_component: function (action, target, element) { + web2py.component(action, target, 0, 1, element); + }, + //helper for flash messages + flash: function(message, status) { + var flash = $('.flash'); + web2py.hide_flash(); + flash.html(message).addClass(status); + if(flash.html()) flash.append(' × ').slideDown(); + }, + hide_flash: function() { + $('.flash').hide().html(''); + }, + show_if_handler: function(target) { + var triggers = {}; + var show_if = function () { + var t = $(this); + var id = t.attr('id'); + t.attr('value', t.val()); + for(var k = 0; k < triggers[id].length; k++) { + var dep = $('#' + triggers[id][k], target); + var tr = $('#' + triggers[id][k] + '__row', target); + if(t.is(dep.attr('data-show-if'))) tr.slideDown(); + else tr.hide(); + } + }; + $('[data-show-trigger]', target).each(function () { + var name = $(this).attr('data-show-trigger'); + if(!triggers[name]) triggers[name] = []; + triggers[name].push($(this).attr('id')); + }); + for(var name in triggers) { + $('#' + name, target).change(show_if).keyup(show_if); + show_if.call($('#' + name, target)); + }; + }, + component_handler : function (target) { + $('div[data-w2p_remote]', target).each(function () { + var remote, times, timeout, target; + var el = $(this); + remote = el.data('w2p_remote'); + times = el.data('w2p_times'); + timeout = el.data('w2p_timeout'); + target = el.attr('id'); + web2py.component(remote, target, timeout, times, $(this)); + } + ) + }, + a_handler: function (el, e) { + e.preventDefault(); + var method = el.data('w2p_method'); + var action = el.attr('href'); + var target = el.data('w2p_target'); + var confirm_message = el.data('w2p_confirm'); -function web2py_calc_entropy(mystring) { - //calculate a simple entropy for a given string - var csets = new Array( - 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/', - '0123456789abcdefghijklmnopqrstuvwxyz'); - var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split(''); - for (var i=0;i0) jQuery('#'+id).hide().fadeIn('slow'); else jQuery('#'+id).show().fadeOut('slow'); } -function ajax(u,s,t) { - query = ''; - if (typeof s == "string") { - d = jQuery(s).serialize(); - if(d){ query = d; } - } else { +(function ($, undefined) { + /* + * Unobtrusive scripting adapter for jQuery, largely taken from + * the wonderful https://github.com/rails/jquery-ujs + * + * + * Released under the MIT license + * + */ + + String.prototype.reverse = function () { + return this.split('').reverse().join(''); + }; + var web2py; + + $.web2py = web2py = { + + popup: function (url) { + newwindow = window.open(url, 'name', 'height=400,width=600'); + if(window.focus) newwindow.focus(); + return false; + }, + collapse: function (id) { + $('#' + id).slideToggle(); + }, + fade: function (id, value) { + if(value > 0) $('#' + id).hide().fadeIn('slow'); + else $('#' + id).show().fadeOut('slow'); + }, + ajax: function (u, s, t) { + query = ''; + if(typeof s == "string") { + d = $(s).serialize(); + if(d) { + query = d; + } + } else { pcs = []; - if (s != null && s != undefined) for(i=0; i 0) { + query = pcs.join("&"); } - if (pcs.length>0){query = pcs.join("&");} - } - jQuery.ajax({type: "POST", url: u, data: query, success: function(msg) { if(t) { if(t==':eval') eval(msg); else if(typeof t=='string') jQuery("#"+t).html(msg); else t(msg); } } }); -} - -String.prototype.reverse = function () { return this.split('').reverse().join('');}; -function web2py_ajax_fields(target) { - var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; - var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; - jQuery("input.date",target).each(function() {Calendar.setup({inputField:this, ifFormat:date_format, showsTime:false });}); - jQuery("input.datetime",target).each(function() {Calendar.setup({inputField:this, ifFormat:datetime_format, showsTime: true, timeFormat: "24" });}); - jQuery("input.time",target).each(function(){jQuery(this).timeEntry();}); - -}; - -function web2py_ajax_init(target) { - jQuery('.hidden', target).hide(); - jQuery('.error', target).hide().slideDown('slow'); - web2py_ajax_fields(target); - web2py_show_if(target); -}; - -function web2py_event_handlers() { - var doc = jQuery(document) - doc.on('click', '.flash', function(e){var t=jQuery(this); if(t.css('top')=='0px') t.slideUp('slow'); else t.fadeOut(); e.preventDefault();}); - doc.on('keyup', 'input.integer', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();}); - doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();}); - var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?"; - doc.on('click', "input[type='checkbox'].delete", function(){if(this.checked) if(!confirm(confirm_message)) this.checked=false;}); - - doc.ajaxSuccess(function(e, xhr) { - var redirect=xhr.getResponseHeader('web2py-redirect-location'); - var command=xhr.getResponseHeader('web2py-component-command'); - var flash=xhr.getResponseHeader('web2py-component-flash'); - if (redirect !== null) { - window.location = redirect; - }; - if(command !== null){ - eval(decodeURIComponent(command)); - } - if(flash) { - jQuery('.flash') - .html(decodeURIComponent(flash)) - .append('×') - .slideDown(); - } - }); - - doc.ajaxError(function(e, xhr, settings, exception) { - doc.off('click', '.flash') - switch(xhr.status){ - case 500: - $('.flash').html(ajax_error_500).slideDown(); } - }); -}; - -jQuery(function() { - var flash = jQuery('.flash'); - flash.hide(); - if(flash.html()) flash.append('×').slideDown(); - web2py_ajax_init(document); - web2py_event_handlers(); -}); - -function web2py_trap_form(action,target) { - jQuery('#'+target+' form').each(function(i){ - var form=jQuery(this); - if(!form.hasClass('no_trap')) - form.submit(function(e){ - jQuery('.flash').hide().html(''); - web2py_ajax_page('post',action,form.serialize(),target); - e.preventDefault(); + $.ajax({ + type: "POST", + url: u, + data: query, + success: function (msg) { + if(t) { + if(t == ':eval') eval(msg); + else if(typeof t == 'string') $("#" + t).html(msg); + else t(msg); + } + } }); - }); -} - -function web2py_trap_link(target) { - jQuery('#'+target+' a.w2p_trap').each(function(i){ - var link=jQuery(this); - link.click(function(e) { - jQuery('.flash').hide().html(''); - web2py_ajax_page('get',link.attr('href'),[],target); - e.preventDefault(); - }); + }, + ajax_fields: function (target) { + /*this attaches something to a newly loaded fragment/page + * Ideally all events should be bound to the document, so we can avoid calling + * this over and over... all will be bound to the document + */ + var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; + var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; + $("input.date", target).each(function () { + Calendar.setup({ + inputField: this, + ifFormat: date_format, + showsTime: false }); -} + }); + $("input.datetime", target).each(function () { + Calendar.setup({ + inputField: this, + ifFormat: datetime_format, + showsTime: true, + timeFormat: "24" + }); + }); + $("input.time", target).each(function () { + $(this).timeEntry(); + }); + /*adds btn class to buttons*/ + $('button', target).addClass('btn'); + $('form input[type="submit"], form input[type="button"]', target).addClass('btn'); + /*no more inline javascript for PasswordWidget*/ + $('input[type=password][data-w2p_entropy]', target).each(function () { + web2py.validate_entropy($(this)); + }); + /*no more inline javascript for ListWidget*/ + $('ul.w2p_list', target).each(function () { + function pe(ul, e) { + var new_line = ml(ul); + rel(ul); + if($(e.target).parent().is(':visible')) { + //make sure we didn't delete the element before we insert after + new_line.insertAfter($(e.target).parent()); + } else { + //the line we clicked on was deleted, just add to end of list + new_line.appendTo(ul); + } + new_line.find(":text").focus(); + return false; + } -function web2py_ajax_page(method, action, data, target) { - jQuery.ajax({'type':method, 'url':action, 'data':data, - 'beforeSend':function(xhr) { - xhr.setRequestHeader('web2py-component-location', document.location); - xhr.setRequestHeader('web2py-component-element', target);}, - 'complete':function(xhr,text){ - var html=xhr.responseText; - var content=xhr.getResponseHeader('web2py-component-content'); - var t = jQuery('#'+target); - if(content=='prepend') t.prepend(html); - else if(content=='append') t.append(html); - else if(content!='hide') t.html(html); - web2py_trap_form(action,target); - web2py_trap_link(target); - web2py_ajax_init('#'+target); - } - }); -} + function rl(ul, e) { + if($(ul).children().length > 1) { + //only remove if we have more than 1 item so the list is never empty + $(e.target).parent().remove(); + } + } -function web2py_component(action, target, timeout, times){ - jQuery(function(){ - var jelement = jQuery("#" + target); - var element = jelement.get(0); - var statement = "jQuery('#" + target + "').get(0).reload();"; - jelement.reload = function (){ - // Continue if times is Infinity or - // the times limit is not reached - if (element.reload_check()){ - web2py_ajax_page('get', action, null, target);} }; // reload - // Method to check timing limit - element.reload_check = function (){ - if (jelement.hasClass('w2p_component_stop')) {clearInterval(this.timing);return false;} - if (this.reload_counter == Infinity){return true;} - else { - if (!isNaN(this.reload_counter)){ - this.reload_counter -= 1; - if (this.reload_counter < 0){ - if (!this.run_once){ - clearInterval(this.timing); - return false; - } + function ml(ul) { + var line = $(ul).find("li:first").clone(true); + line.find(':text').val(''); + return line; + } + + function rel(ul) { + $(ul).find("li").each(function () { + var trimmed = $.trim($(this.firstChild).val()); + if(trimmed == '') $(this).remove(); + else $(this.firstChild).val(trimmed); + }); + } + var ul = this; + $(ul).find(":text").after('+ -').keypress(function (e) { + return(e.which == 13) ? pe(ul, e) : true; + }).next().click(function (e) { + pe(ul, e); + e.preventDefault(); + }).next().click(function (e) { + rl(ul, e); + e.preventDefault(); + }); + }); + }, + ajax_init: function (target) { + $('.hidden', target).hide(); + web2py.manage_errors(target); + web2py.ajax_fields(target); + web2py.show_if_handler(target); + web2py.component_handler(target); + }, + //manage errors in forms + manage_errors: function(target) { + $('.error', target).hide().slideDown('slow'); + //$('.error', target).hide().fadeIn('slow'); + }, + event_handlers: function () { + /* This is called once for page + * Ideally it should bound all the things that are needed + */ + var doc = $(document); + doc.on('click', '.flash', function (e) { + var t = $(this); + if(t.css('top') == '0px') t.slideUp('slow'); + else t.fadeOut(); + //if I want to display a clickable something + //inside flash, I should not be prevented to follow it + //e.preventDefault(); + }); + doc.on('keyup', 'input.integer', function () { + this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse(); + }); + doc.on('keyup', 'input.double, input.decimal', function () { + this.value = this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g, '').reverse(); + }); + var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?"; + doc.on('click', "input[type='checkbox'].delete", function () { + if(this.checked) if(!web2py.confirm(confirm_message)) this.checked = false; + }); + + doc.ajaxSuccess(function (e, xhr) { + var redirect = xhr.getResponseHeader('web2py-redirect-location'); + var command = xhr.getResponseHeader('web2py-component-command'); + var flash = xhr.getResponseHeader('web2py-component-flash'); + if(redirect !== null) { + window.location = redirect; + }; + if(command !== null) { + eval(decodeURIComponent(command)); + } + if(flash) { + web2py.flash(decodeURIComponent(flash)) + } + }); + + doc.ajaxError(function (e, xhr, settings, exception) { + //personally I don't like it. + //if there's an error it it flashed and can be removed + //as any other message + //doc.off('click', '.flash') + switch(xhr.status) { + case 500: + web2py.flash(ajax_error_500); + } + }); + + }, + trap_form: function (action, target) { + $('#' + target + ' form').each(function (i) { + var form = $(this); + form.attr('data-w2p_target', target); + if(!form.hasClass('no_trap')) { + //should be there by default ? + form.find('input[type=submit]').attr('data-w2p_disable_with', 'Working...'); + form.submit(function (e) { + web2py.hide_flash(); + web2py.ajax_page('post', action, form.serialize(), target, form); + e.preventDefault(); + }); + } + }); + }, + trap_link: function (target) { + $('#' + target + ' a.w2p_trap').each(function (i) { + var link = $(this); + link.click(function (e) { + web2py.hide_flash(); + web2py.ajax_page('get', link.attr('href'), [], target); + e.preventDefault(); + }); + }); + }, + ajax_page: function (method, action, data, target, element) { + //element is a new parameter, but should be put be put in front + if(element == undefined) element = $(document); + if(web2py.fire(element, 'ajax:before')) { //test a usecase, should stop here if returns false + $.ajax({ + 'type': method, + 'url': action, + 'data': data, + 'beforeSend': function (xhr, settings) { + //added + xhr.setRequestHeader('web2py-component-location', document.location); + xhr.setRequestHeader('web2py-component-element', target); + return web2py.fire(element, 'ajax:beforeSend', [xhr, settings]); //test a usecase, should stop here if returns false + }, + //added + 'success': function (data, status, xhr) { + //bummer for form submissions....the element is not there after complete + //because it gets replaced by the new response.... + element.trigger('ajax:success', [data, status, xhr]); + }, + //added + 'error': function (xhr, status, error) { + //bummer for form submissions....in addition to the element being not there after + //complete because it gets replaced by the new response, standard form + //handling just returns the same status code for good and bad + //form submissions (i.e. that triggered a validator error) + element.trigger('ajax:error', [xhr, status, error]); + }, + 'complete': function (xhr, status) { + element.trigger('ajax:complete', [xhr, status]); + var html = xhr.responseText; + var content = xhr.getResponseHeader('web2py-component-content'); + var t = $('#' + target); + if(content == 'prepend') t.prepend(html); + else if(content == 'append') t.append(html); + else if(content != 'hide') t.html(html); + web2py.trap_form(action, target); + web2py.trap_link(target); + web2py.ajax_init('#' + target); + } + }); + } + }, + component: function (action, target, timeout, times, el) { + //element is a new parameter, but should be put in front + $(function () { + var jelement = $("#" + target); + var element = jelement.get(0); + var statement = "jQuery('#" + target + "').get(0).reload();"; + element.reload = function () { + // Continue if times is Infinity or + // the times limit is not reached + if(element.reload_check()) { + web2py.ajax_page('get', action, null, target, el); + } + }; // reload + // Method to check timing limit + element.reload_check = function () { + if(jelement.hasClass('w2p_component_stop')) { + clearInterval(this.timing); + return false; + } + if(this.reload_counter == Infinity) { + return true; + } else { + if(!isNaN(this.reload_counter)) { + this.reload_counter -= 1; + if(this.reload_counter < 0) { + if(!this.run_once) { + clearInterval(this.timing); + return false; } - else{return true;} - } } - return false;}; // reload check - if (!isNaN(timeout)){ - element.timeout = timeout; - element.reload_counter = times; - if (times > 1){ - // Multiple or infinite reload - // Run first iteration - web2py_ajax_page('get', action, null, target); - element.run_once = false; - element.timing = setInterval(statement, timeout); - element.reload_counter -= 1; + } else { + return true; + } + } + } + return false; + }; // reload check + if(!isNaN(timeout)) { + element.timeout = timeout; + element.reload_counter = times; + if(times > 1) { + // Multiple or infinite reload + // Run first iteration + web2py.ajax_page('get', action, null, target, el); + element.run_once = false; + element.timing = setInterval(statement, timeout); + element.reload_counter -= 1; + } else if(times == 1) { + // Run once with timeout + element.run_once = true; + element.setTimeout = setTimeout; + element.timing = setTimeout(statement, timeout); + } + } else { + // run once (no timeout specified) + element.reload_counter = Infinity; + web2py.ajax_page('get', action, null, target, el); } - else if (times == 1) { - // Run once with timeout - element.run_once = true; - element.setTimeout = setTimeout; - element.timing = setTimeout(statement, timeout); + }); + }, + calc_entropy: function (mystring) { + //calculate a simple entropy for a given string + var csets = new Array( + 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + '0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/', + '0123456789abcdefghijklmnopqrstuvwxyz'); + var score = 0, + other = {}, seen = {}, lastset = null, + mystringlist = mystring.split(''); + for(var i = 0; i < mystringlist.length; i++) { // classify this character + var c = mystringlist[i], + inset = 5; + for(var j = 0; j < csets.length; j++) + if(csets[j].indexOf(c) != -1) { + inset = j; + break; + } + //calculate effect of character on alphabet size + if(!(inset in seen)) { + seen[inset] = 1; + score += csets[inset].length; + } else if(!(c in other)) { + score += 1; + other[c] = 1; } - } else { - // run once (no timeout specified) - element.reload_counter = Infinity; - web2py_ajax_page('get', action, null, target); - } }); } + if(inset != lastset) { + score += 1; + lastset = inset; + } + } + var entropy = mystring.length * Math.log(score) / 0.6931471805599453; + return Math.round(entropy * 100) / 100 + }, + validate_entropy: function (myfield, req_entropy) { + //added + if(myfield.data('w2p_entropy') != undefined) req_entropy = myfield.data('w2p_entropy'); + var validator = function () { + var v = (web2py.calc_entropy(myfield.val()) || 0) / req_entropy; + var r = 0, + g = 0, + b = 0, + rs = function (x) { + return Math.round(x * 15).toString(16) + }; + if(v <= 0.5) { + r = 1.0; + g = 2.0 * v; + } else { + r = (1.0 - 2.0 * (Math.max(v, 0) - 0.5)); + g = 1.0; + } + var color = '#' + rs(r) + rs(g) + rs(b); + myfield.css('background-color', color); + entropy_callback = myfield.data('entropy_callback'); + if(entropy_callback) entropy_callback(v); + } + if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check'); + }, + web2py_websocket: function (url, onmessage, onopen, onclose) { + if("WebSocket" in window) { + var ws = new WebSocket(url); + ws.onopen = onopen ? onopen : (function () {}); + ws.onmessage = onmessage; + ws.onclose = onclose ? onclose : (function () {}); + return true; // supported + } else return false; // not supported + }, + /* new from here */ + // Form input elements bound by jquery-ujs + formInputClickSelector: 'input[type=submit], input[type=image], button[type=submit], button:not([type])', + // Form input elements disabled during form submission + disableSelector: 'input, button, textarea, select', + // Form input elements re-enabled after form submission + enableSelector: 'input:disabled, button:disabled, textarea:disabled, select:disabled', + // Triggers an event on an element and returns false if the event result is false + fire: function (obj, name, data) { + var event = $.Event(name); + obj.trigger(event, data); + return event.result !== false; + }, + // Helper function, needed to provide consistent behavior in IE + stopEverything: function (e) { + $(e.target).trigger('w2p:everythingStopped'); + e.stopImmediatePropagation(); + return false; + }, + confirm: function (message) { + return confirm(message); + }, + // replace element's html with the 'data-disable-with' after storing original html + // and prevent clicking on it + disableElement: function (el) { + el.addClass('disabled'); + var method = el.prop('type') == 'submit' ? 'val' : 'html'; + // store enabled state + el.data('w2p:enable-with', el[method]); + /* little addition by default*/ + if((el.data('w2p_disable_with') == 'default') || (el.data('w2p_disable_with') === undefined)) { + el.data('w2p_disable_with', 'Working...'); + } + // set to disabled state + el[method](el.data('w2p_disable_with')); -function web2py_websocket(url,onmessage,onopen,onclose) { - if ("WebSocket" in window) { - var ws = new WebSocket(url); - ws.onopen = onopen?onopen:(function(){}); - ws.onmessage = onmessage; - ws.onclose = onclose?onclose:(function(){}); - return true; // supported - } else return false; // not supported -} + el.bind('click.w2pDisable', function (e) { // prevent further clicking + return web2py.stopEverything(e); + }); + }, + // restore element to its original state which was disabled by 'disableElement' above + enableElement: function (el) { + var method = el.prop('type') == 'submit' ? 'val' : 'html'; + if(el.data('w2p:enable-with') !== undefined) { + // set to old enabled state + el[method](el.data('w2p:enable-with')); + el.removeData('w2p:enable-with'); // clean up cache + } + el.removeClass('disabled'); + el.unbind('click.w2pDisable'); // enable element + }, + //convenience wrapper, internal use only + simple_component: function (action, target, element) { + web2py.component(action, target, 0, 1, element); + }, + //helper for flash messages + flash: function(message, status) { + var flash = $('.flash'); + web2py.hide_flash(); + flash.html(message).addClass(status); + if(flash.html()) flash.append(' × ').slideDown(); + }, + hide_flash: function() { + $('.flash').hide().html(''); + }, + show_if_handler: function(target) { + var triggers = {}; + var show_if = function () { + var t = $(this); + var id = t.attr('id'); + t.attr('value', t.val()); + for(var k = 0; k < triggers[id].length; k++) { + var dep = $('#' + triggers[id][k], target); + var tr = $('#' + triggers[id][k] + '__row', target); + if(t.is(dep.attr('data-show-if'))) tr.slideDown(); + else tr.hide(); + } + }; + $('[data-show-trigger]', target).each(function () { + var name = $(this).attr('data-show-trigger'); + if(!triggers[name]) triggers[name] = []; + triggers[name].push($(this).attr('id')); + }); + for(var name in triggers) { + $('#' + name, target).change(show_if).keyup(show_if); + show_if.call($('#' + name, target)); + }; + }, + component_handler : function (target) { + $('div[data-w2p_remote]', target).each(function () { + var remote, times, timeout, target; + var el = $(this); + remote = el.data('w2p_remote'); + times = el.data('w2p_times'); + timeout = el.data('w2p_timeout'); + target = el.attr('id'); + web2py.component(remote, target, timeout, times, $(this)); + } + ) + }, + a_handler: function (el, e) { + e.preventDefault(); + var method = el.data('w2p_method'); + var action = el.attr('href'); + var target = el.data('w2p_target'); + var confirm_message = el.data('w2p_confirm'); -function web2py_calc_entropy(mystring) { - //calculate a simple entropy for a given string - var csets = new Array( - 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - '0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/', - '0123456789abcdefghijklmnopqrstuvwxyz'); - var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split(''); - for (var i=0;i0 + __nonzero__ = lambda self: len(self.__dict__)>0 - def update(self, *args, **kwargs): - self.__dict__.update(*args, **kwargs) + update = lambda self, *args, **kwargs: self.__dict__.update(*args, **kwargs) - def keys(self): - return self.__dict__.keys() + keys = lambda self: self.__dict__.keys() - def items(self): - return self.__dict__.items() + items = lambda self: self.__dict__.items() - def values(self): - return self.__dict__.values() + values = lambda self: self.__dict__.values() - def __iter__(self): - return self.__dict__.__iter__() + __iter__ = lambda self: self.__dict__.__iter__() - def iteritems(self): - return self.__dict__.iteritems() + iteritems = lambda self: self.__dict__.iteritems() - def __str__(self): - ### this could be made smarter - return '' % self.as_dict() + __str__ = __repr__ = lambda self: '' % self.as_dict() - def __repr__(self): - return '' % self.as_dict() + __int__ = lambda self: object.__getattribute__(self,'id') - def __int__(self): - return object.__getattribute__(self,'id') + __long__ = lambda self: long(object.__getattribute__(self,'id')) - def __long__(self): - return long(object.__getattribute__(self,'id')) def __eq__(self,other): try: @@ -8492,9 +8485,10 @@ class Table(object): def __str__(self): if self._ot is not None: - if 'Oracle' in str(type(self._db._adapter)): # <<< patch - return '%s %s' % (self._ot, self._tablename) # <<< patch - return '%s AS %s' % (self._ot, self._tablename) + return self._db._adapter.QUOTE_TEMPLATE % self._ot + #if 'Oracle' in str(type(self._db._adapter)): # <<< patch + # return '%s %s' % (self._ot, self._tablename) # <<< patch + #return '%s AS %s' % (self._ot, self._tablename) return self._tablename def _drop(self, mode = ''): @@ -9959,7 +9953,7 @@ class Set(object): fields = table._listify(update_fields,update=True) if not fields: raise SyntaxError("No fields to update") - ret = db._adapter.update(tablename,self.query,fields) + ret = db._adapter.update("%s" % table,self.query,fields) ret and [f(self,update_fields) for f in table._after_update] return ret @@ -9971,7 +9965,8 @@ class Set(object): table = self.db[tablename] fields = table._listify(update_fields,update=True) if not fields: raise SyntaxError("No fields to update") - ret = self.db._adapter.update(tablename,self.query,fields) + + ret = self.db._adapter.update("%s" % table,self.query,fields) return ret def validate_and_update(self, **update_fields): @@ -10301,10 +10296,10 @@ class Rows(object): """ Takes an index and returns a copy of the indexed row with values transformed via the "represent" attributes of the associated fields. - + If no index is specified, a generator is returned for iteration over all the rows. - + fields -- a list of fields to transform (if None, all fields with "represent" attributes will be transformed). """ diff --git a/gluon/globals.py b/gluon/globals.py index 940d7f3e..3aafea1f 100644 --- a/gluon/globals.py +++ b/gluon/globals.py @@ -83,7 +83,6 @@ class Request(Storage): def __init__(self): Storage.__init__(self) - self.wsgi = Storage() # hooks to environ and start_response self.env = Storage() self.cookies = Cookie.SimpleCookie() self.get_vars = Storage() diff --git a/gluon/html.py b/gluon/html.py index 54326155..91bff05e 100644 --- a/gluon/html.py +++ b/gluon/html.py @@ -1479,30 +1479,30 @@ class A(DIV): def xml(self): if not self.components and self['_href']: self.append(self['_href']) + if not self['_disable_with']: + self['_data-w2p_disable_with'] = 'default' if self['delete']: - d = "jQuery(this).closest('%s').remove();" % self['delete'] - else: - d = '' + self['_data-w2p_remove'] = self['delete'] + if self['target']: + self['_data-w2p_target'] = self['target'] if self['component']: - self['_onclick'] = "web2py_component('%s','%s');%sreturn false;" % \ - (self['component'], self['target'] or '', d) - self['_href'] = self['_href'] or '#null' + self['_data-w2p_method'] = 'GET' + self['_href'] = self['component'] elif self['callback']: - returnfalse = "var e = arguments[0] || window.event; e.cancelBubble=true; if (e.stopPropagation) {e.stopPropagation(); e.stopImmediatePropagation(); e.preventDefault();}" - if d and not self['noconfirm']: - self['_onclick'] = "if(confirm(w2p_ajax_confirm_message||'Are you sure you want to delete this object?')){ajax('%s',[],'%s');%s};%s" % \ - (self['callback'], self['target'] or '', d, returnfalse) - else: - self['_onclick'] = "ajax('%s',[],'%s');%sreturn false" % \ - (self['callback'], self['target'] or '', d) - self['_href'] = self['_href'] or '#null' + self['_data-w2p_method'] = 'POST' + self['_href'] = self['callback'] + if self['delete'] and not self['noconfirm']: + if not self['confirm']: + self['_data-w2p_confirm'] = 'default' + else: + self['_data-w2p_confirm'] = self['confirm'] elif self['cid']: - pre = self['pre_call'] + ';' if self['pre_call'] else '' - self['_onclick'] = '%sweb2py_component("%s","%s");%sreturn false;' % \ - (pre,self['_href'], self['cid'], d) + self['_data-w2p_method'] = 'GET' + self['_data-w2p_target'] = self['cid'] + if self['pre_call']: + self['_data-w2p_pre_call'] = self['pre_call'] return DIV.xml(self) - class BUTTON(DIV): tag = 'button' @@ -2481,7 +2481,7 @@ def test(): >>> from validators import * >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml() - + >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml()
  • doc
  • cat
  • mouse
>>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml() diff --git a/gluon/main.py b/gluon/main.py index 1a9c268f..0ebeade1 100644 --- a/gluon/main.py +++ b/gluon/main.py @@ -257,51 +257,55 @@ def serve_controller(request, response, session): raise HTTP(response.status, page, **response.headers) -def start_response_aux(status, headers, exc_info, response=None): - """ - in controller you can use:: - - - request.wsgi.environ - - request.wsgi.start_response - - to call third party WSGI applications - """ - response.status = str(status).split(' ', 1)[0] - response.headers = dict(headers) - return lambda *args, **kargs: response.write(escape=False, *args, **kargs) - - -def middleware_aux(request, response, *middleware_apps): - """ - In you controller use:: - +class LazyWSGI(object): + def __init__(self, environ, request, response): + self.wsgi_environ = environ + self.request = request + self.response = response + @property + def environ(self): + if not hasattr(self,'_environ'): + new_environ = self.wsgi_environ + new_environ['wsgi.input'] = self.request.body + new_environ['wsgi.version'] = 1 + self._environ = new_environ + return self._environ + def start_response(self,status='200', headers=[], exec_info=None): + """ + in controller you can use:: + + - request.wsgi.environ + - request.wsgi.start_response + + to call third party WSGI applications + """ + self.response.status = str(status).split(' ', 1)[0] + self.response.headers = dict(headers) + return lambda *args, **kargs: \ + self.response.write(escape=False, *args, **kargs) + def middleware(self,*a): + """ + In you controller use:: + @request.wsgi.middleware(middleware1, middleware2, ...) - - to decorate actions with WSGI middleware. actions must return strings. - uses a simulated environment so it may have weird behavior in some cases - """ - def middleware(f): - def app(environ, start_response): - data = f() - start_response(response.status, response.headers.items()) - if isinstance(data, list): - return data - return [data] - for item in middleware_apps: - app = item(app) - - def caller(app): - wsgi = request.wsgi - return app(wsgi.environ, wsgi.start_response) - return lambda caller=caller, app=app: caller(app) - return middleware - - -def environ_aux(environ, request): - new_environ = copy.copy(environ) - new_environ['wsgi.input'] = request.body - new_environ['wsgi.version'] = 1 - return new_environ + + to decorate actions with WSGI middleware. actions must return strings. + uses a simulated environment so it may have weird behavior in some cases + """ + def middleware(f): + def app(environ, start_response): + data = f() + start_response(self.response.status, + self.response.headers.items()) + if isinstance(data, list): + return data + return [data] + for item in middleware_apps: + app = item(app) + def caller(app): + return app(self.environ, self.start_response) + return lambda caller=caller, app=app: caller(app) + return middleware ISLE25 = sys.version_info[1] <= 5 @@ -339,8 +343,9 @@ def parse_get_post_vars(request, environ): json_vars = {} pass # update vars and get_vars with what was posted as json - request.get_vars.update(json_vars) - request.vars.update(json_vars) + if isinstance(json_vars,dict): + request.get_vars.update(json_vars) + request.vars.update(json_vars) # parse POST variables on POST, PUT, BOTH only in post_vars @@ -386,7 +391,7 @@ def parse_get_post_vars(request, environ): if len(pvalue): request.post_vars[key] = (len(pvalue) > 1 and pvalue) or pvalue[0] - if is_json: + if is_json and isinstance(json_vars,dict): # update post_vars with what was posted as json request.post_vars.update(json_vars) @@ -536,13 +541,7 @@ def wsgibase(environ, responder): # expose wsgi hooks for convenience # ################################################## - request.wsgi.environ = environ_aux(environ, request) - request.wsgi.start_response = \ - lambda status='200', headers=[], \ - exec_info=None, response=response: \ - start_response_aux(status, headers, exec_info, response) - request.wsgi.middleware = \ - lambda *a: middleware_aux(request, response, *a) + request.wsgi = LazyWSGI(environ, request, response) # ################################################## # load cookies diff --git a/gluon/rewrite.py b/gluon/rewrite.py index baebe8c7..a96c4302 100644 --- a/gluon/rewrite.py +++ b/gluon/rewrite.py @@ -638,6 +638,8 @@ def regex_url_in(request, environ): if match.group('c') == 'static': application = match.group('a') version, filename = None, match.group('z') + if not filename: + raise HTTP(404) items = filename.split('/', 1) if regex_version.match(items[0]): version, filename = items diff --git a/gluon/scheduler.py b/gluon/scheduler.py index 2dea4efe..95820ffa 100644 --- a/gluon/scheduler.py +++ b/gluon/scheduler.py @@ -480,7 +480,8 @@ class Scheduler(MetaScheduler): Field('function_name', requires=IS_IN_SET(sorted(self.tasks.keys())) if self.tasks else DEFAULT), - Field('uuid', requires=IS_NOT_IN_DB(db, 'scheduler_task.uuid'), + Field('uuid', length=255, + requires=IS_NOT_IN_DB(db, 'scheduler_task.uuid'), unique=True, default=web2py_uuid), Field('args', 'text', default='[]', requires=TYPE(list)), Field('vars', 'text', default='{}', requires=TYPE(dict)), @@ -521,7 +522,7 @@ class Scheduler(MetaScheduler): db.define_table( 'scheduler_worker', - Field('worker_name', unique=True), + Field('worker_name', length=255, unique=True), Field('first_heartbeat', 'datetime'), Field('last_heartbeat', 'datetime'), Field('status', requires=IS_IN_SET(WORKER_STATUS)), diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index cb1c1f3b..26720803 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -86,8 +86,8 @@ def show_if(cond): if not cond: return None base = "%s_%s" % (cond.first.tablename, cond.first.name) - if ((cond.op.__name__ == 'EQ' and cond.second == True) or - (cond.op.__name__ == 'NE' and cond.second == False)): + if ((cond.op.__name__ == 'EQ' and cond.second == True) or + (cond.op.__name__ == 'NE' and cond.second == False)): return base,":checked" if ((cond.op.__name__ == 'EQ' and cond.second == False) or (cond.op.__name__ == 'NE' and cond.second == True)): @@ -97,7 +97,7 @@ def show_if(cond): if cond.op.__name__ == 'NE': return base,"[value!='%s']" % cond.second if cond.op.__name__ == 'CONTAINS': - return base,"[value~='%s']" % cond.second + return base,"[value~='%s']" % cond.second if cond.op.__name__ == 'BELONGS' and isinstance(cond.second,(list,tuple)): return base,','.join("[value='%s']" % (v) for v in cond.second) raise RuntimeError("Not Implemented Error") @@ -126,7 +126,7 @@ class FormWidget(object): _id='%s_%s' % (field.tablename, field.name), _class=cls._class or widget_class.match(str(field.type)).group(), - _name=field.name, + _name=field.name, requires=field.requires, ) if getattr(field,'show_if',None): @@ -295,57 +295,15 @@ class ListWidget(StringWidget): _class = 'string' requires = field.requires if isinstance( field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None - attributes['_style'] = 'list-style:none' nvalue = value or [''] items = [LI(INPUT(_id=_id, _class=_class, _name=_name, value=v, hideerror=k < len(nvalue) - 1, requires=requires), **attributes) for (k, v) in enumerate(nvalue)] - script = SCRIPT(""" -// from http://refactormycode.com/codes/694-expanding-input-list-using-jquery -(function(){ -jQuery.fn.grow_input = function() { - return this.each(function() { - var ul = this; - jQuery(ul).find(":text").after('+ -').keypress(function (e) { return (e.which == 13) ? pe(ul, e) : true; }).next().click(function(e){ pe(ul, e) }).next().click(function(e){ rl(ul, e)}); - }); -}; -function pe(ul, e) { - var new_line = ml(ul); - rel(ul); - if (jQuery(e.target).parent().is(':visible')) { - //make sure we didn't delete the element before we insert after - new_line.insertAfter(jQuery(e.target).parent()); - } else { - //the line we clicked on was deleted, just add to end of list - new_line.appendTo(ul); - } - new_line.find(":text").focus(); - return false; -} -function rl(ul, e) { - if (jQuery(ul).children().length > 1) { - //only remove if we have more than 1 item so the list is never empty - jQuery(e.target).parent().remove(); - } -} -function ml(ul) { - var line = jQuery(ul).find("li:first").clone(true); - line.find(':text').val(''); - return line; -} -function rel(ul) { - jQuery(ul).find("li").each(function() { - var trimmed = jQuery.trim(jQuery(this.firstChild).val()); - if (trimmed=='') jQuery(this).remove(); else jQuery(this.firstChild).val(trimmed); - }); -} -})(); -jQuery(document).ready(function(){jQuery('#%s_grow_input').grow_input();}); -""" % _id) attributes['_id'] = _id + '_grow_input' attributes['_style'] = 'list-style:none' - return TAG[''](UL(*items, **attributes), script) + attributes['_class'] = 'w2p_list' + return TAG[''](UL(*items, **attributes)) class MultipleOptionsWidget(OptionsWidget): @@ -521,7 +479,6 @@ class PasswordWidget(FormWidget): _value=(value and cls.DEFAULT_PASSWORD_DISPLAY) or '', ) attr = cls._attributes(field, default, **attributes) - output = CAT(INPUT(**attr)) # deal with entropy check! requires = field.requires @@ -529,10 +486,9 @@ class PasswordWidget(FormWidget): requires = [requires] is_strong = [r for r in requires if isinstance(r, IS_STRONG)] if is_strong: - output.append(SCRIPT("web2py_validate_entropy(jQuery('#%s'),%s);" % ( - attr['_id'], is_strong[0].entropy - if is_strong[0].entropy else "null"))) + attr['_data-w2p_entropy'] = is_strong[0].entropy if is_strong[0].entropy else "null" # end entropy check + output = INPUT(**attr) return output @@ -651,7 +607,7 @@ class AutocompleteWidget(object): self.help_fields = help_fields or [] self.help_string = help_string if self.help_fields and not self.help_string: - self.help_string = ' '.join('%%(%s)s'%f.name + self.help_string = ' '.join('%%(%s)s'%f.name for f in self.help_fields) self.request = request @@ -1484,7 +1440,7 @@ class SQLFORM(FORM): continue else: f = os.path.join( - current.request.folder, + current.request.folder, os.path.normpath(f)) source_file = open(f, 'rb') original_filename = os.path.split(f)[1] @@ -2141,7 +2097,7 @@ class SQLFORM(FORM): else: rows = dbset.select(left=left, orderby=orderby, cacheable=True, *expcolumns) - + value = exportManager[export_type] clazz = value[0] if hasattr(value, '__getitem__') else value oExp = clazz(rows) @@ -2615,7 +2571,7 @@ class SQLFORM(FORM): except (KeyError, ValueError, TypeError): redirect(URL(args=table._tablename)) if nargs == len(args) + 1: - query = table._id != None + query = table._db._adapter.id_query(table) # filter out data info for displayed table if table._tablename in constraints: @@ -2694,7 +2650,7 @@ class SQLFORM(FORM): elif grid.view_form: header = T('View %(entity)s') % dict( entity=format(grid.view_form.table, - grid.view_form.record)) + grid.view_form.record)) if next: breadcrumbs.append(LI( A(T(header), _class=trap_class(),_href=url()), @@ -3136,4 +3092,3 @@ class ExporterJSON(ExportClass): return self.rows.as_json() else: return 'null' - diff --git a/gluon/tests/test_html.py b/gluon/tests/test_html.py index 23b4c83c..51a75526 100644 --- a/gluon/tests/test_html.py +++ b/gluon/tests/test_html.py @@ -46,7 +46,7 @@ class TestBareHelpers(unittest.TestCase): def testA(self): self.assertEqual(A('<>', _a='1', _b='2').xml(), - '<>') + '<>') def testB(self): self.assertEqual(B('<>', _a='1', _b='2').xml(), @@ -209,8 +209,7 @@ class TestBareHelpers(unittest.TestCase): class TestData(unittest.TestCase): def testAdata(self): - self.assertEqual(A('<>', data=dict(abc='', cde='standard'), _a='1', _b='2').xml(), - '<>') + self.assertEqual(A('<>', data=dict(abc='', cde='standard'), _a='1', _b='2').xml(),'<>') if __name__ == '__main__': diff --git a/gluon/tools.py b/gluon/tools.py index 37069c32..e2e2bd09 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -1545,7 +1545,7 @@ class Auth(object): settings.table_user_name, []) + signature_list if username or settings.cas_provider: is_unique_username = \ - [IS_MATCH('[\w\.\-]+'), + [IS_MATCH('[\w\.\-]+', strict=True), IS_NOT_IN_DB(db, '%s.username' % settings.table_user_name)] if not settings.username_case_sensitive: is_unique_username.insert(1, IS_LOWER()) @@ -1824,7 +1824,7 @@ class Auth(object): basic = current.request.env.http_authorization if basic_auth_realm: if callable(basic_auth_realm): - basic_auth_realm = basic_auth_auth() + basic_auth_realm = basic_auth_realm() elif isinstance(basic_auth_realm, (unicode, str)): basic_realm = unicode(basic_auth_realm) elif basic_auth_realm is True: @@ -5201,12 +5201,13 @@ class Wiki(object): return True def can_see_menu(self): - if self.settings.menu_groups is None: - return True if self.auth.user: - groups = self.auth.user_groups.values() - if any(t in self.settings.menu_groups for t in groups): + if self.settings.menu_groups is None: return True + else: + groups = self.auth.user_groups.values() + if any(t in self.settings.menu_groups for t in groups): + return True return False ### END POLICY