diff --git a/CHANGELOG b/CHANGELOG
index 993ebe19..bd6fb9cd 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,9 @@
### DAL Improvements
- Support for DAL(lazy_tables=True) and db.define_table(on_define=lambda table:), thanks Jonathan
+- db(...).select(cacheable=True) make select 30% faster
+- db(...).select(cache=(cache.ram,3600)) now caches parsed data 100x faster
+- db(...).count(cache=(cache.ram,3600)) now supported
- MongoDB support in DAL (experimental), thanks Mark Breedveld
- geodal and spatialite, thanks Denes and Fran (experimental)
- db.mytable._before_insert, _after_insert, _before_update, _after_update, _before_delete. _after_delete (list of callbacks)
diff --git a/Makefile b/Makefile
index 864d37ec..4eace126 100644
--- a/Makefile
+++ b/Makefile
@@ -29,7 +29,7 @@ update:
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
echo "remember that pymysql was tweaked"
src:
- echo 'Version 2.00.0 ('`date +%Y-%m-%d\ %H:%M:%S`') dev' > VERSION
+ echo 'Version 2.00.1 ('`date +%Y-%m-%d\ %H:%M:%S`') rc4' > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
diff --git a/VERSION b/VERSION
index 3fd1bc7d..7b48a4bd 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-26 00:00:11) dev
+Version 2.00.1 (2012-08-28 22:14:54) rc4
diff --git a/applications/admin/controllers/appadmin.py b/applications/admin/controllers/appadmin.py
index 126200b9..fa264bae 100644
--- a/applications/admin/controllers/appadmin.py
+++ b/applications/admin/controllers/appadmin.py
@@ -11,6 +11,8 @@ import copy
import gluon.contenttype
import gluon.fileutils
+response.subtitle = 'Database Administration (appadmin)'
+
# ## critical --- make a copy of the environment
global_env = copy.copy(globals())
diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py
index 4f685ba0..1fb8cf9e 100644
--- a/applications/admin/controllers/default.py
+++ b/applications/admin/controllers/default.py
@@ -23,9 +23,9 @@ except ImportError:
GIT_MISSING = 'requires python-git module, but not installed or incompatible version'
from gluon.languages import (regex_language, read_possible_languages,
- read_possible_plurals, lang_sampling,
+ lang_sampling,
read_dict, write_dict, read_plural_dict,
- write_plural_dict)
+ write_plural_dict, PLURAL_RULES)
if DEMO_MODE and request.function in ['change_password','pack','pack_plugin','upgrade_web2py','uninstall','cleanup','compile_app','remove_compiled_app','delete','delete_plugin','create_file','upload_file','update_languages','reload_routes','git_push','git_pull']:
@@ -949,8 +949,8 @@ def design():
# get only existed files
languages = sorted(all_languages)
- plural_rules={}
- all_plurals=read_possible_plurals()
+ plural_rules = {}
+ all_plurals = PLURAL_RULES
for langfile,lang in all_languages.iteritems():
lang=lang.strip()
match_language = regex_language.match(lang)
@@ -961,7 +961,7 @@ def design():
plang = lang_sampling(match_language, all_plurals.keys())
if plang:
plural=all_plurals[plang]
- plural_rules[langfile]=(plural[0],plang,plural[1],plural[3])
+ plural_rules[langfile]=(plural[0],plang,plural[4],plural[3])
else:
plural_rules[langfile]=(0,lang,'plural_rules-%s.py'%lang,'')
diff --git a/applications/admin/languages/default.py b/applications/admin/languages/default.py
index 94b0828c..a9bd0be9 100644
--- a/applications/admin/languages/default.py
+++ b/applications/admin/languages/default.py
@@ -4,5 +4,127 @@
'!langname!': 'English (US)',
'%Y-%m-%d': '%m-%d-%Y',
'%Y-%m-%d %H:%M:%S': '%m-%d-%Y %H:%M:%S',
+'(requires internet access)': '(requires internet access)',
+'(something like "it-it")': '(something like "it-it")',
+'About': 'About',
+'Additional code for your application': 'Additional code for your application',
+'Admin language': 'Admin language',
+'administrative interface': 'administrative interface',
+'Administrator Password:': 'Administrator Password:',
+'and rename it:': 'and rename it:',
+'Application name:': 'Application name:',
+'are not used yet': 'are not used yet',
+'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
+'arguments': 'arguments',
'back': '< -1;
- },
-
- /* Reconfigure the settings for a time entry field.
- @param input (element) input field to change
- @param options (object) new settings to add or
- (string) an individual setting name
- @param value (any) the individual setting's value
- */
- _changeTimeEntry: function(input, options, value) {
- var inst = jQuery.data(input, PROP_NAME);
- if (inst) {
- if (typeof options == 'string') {
- var name = options;
- options = {};
- options[name] = value;
- }
- var currentTime = this._extractTime(inst);
- extendRemove(inst.options, options || {});
- if (currentTime)
- this._setTime(inst, new Date(0, 0, 0,
- currentTime[0], currentTime[1], currentTime[2]));
- }
- jQuery.data(input, PROP_NAME, inst);
- },
-
- /* Remove the time entry functionality from an input.
- @param input (element) input field to affect
- */
- _destroyTimeEntry: function(input) {
- jQueryinput = jQuery(input);
- if (!jQueryinput.hasClass(this.markerClassName)) return;
- jQueryinput.removeClass(this.markerClassName).unbind('.timeEntry');
- this._disabledInputs = jQuery.map(this._disabledInputs, function(value) { return (value == input ? null : value); }); // Delete entry
- jQueryinput.parent().replaceWith(jQueryinput);
- jQuery.removeData(input, PROP_NAME);
- },
-
- /* Initialise the current time for a time entry input field.
- @param input (element) input field to update
- @param time (Date) the new time (year/month/day ignored) or null for now
- */
- _setTimeTimeEntry: function(input, time) {
- var inst = jQuery.data(input, PROP_NAME);
- if (inst) this._setTime(inst, time ? (typeof time == 'object' ? new Date(time.getTime()) : time) : null);
- },
-
- /* Retrieve the current time for a time entry input field.
- @param input (element) input field to examine
- @return (Date) current time (year/month/day zero) or null if none
- */
- _getTimeTimeEntry: function(input) {
- var inst = jQuery.data(input, PROP_NAME);
- var currentTime = (inst ? this._extractTime(inst) : null);
- return (!currentTime ? null : new Date(0, 0, 0, currentTime[0], currentTime[1], currentTime[2]));
- },
-
- /* Retrieve the millisecond offset for the current time.
- @param input (element) input field to examine
- @return (number) the time as milliseconds offset or zero if none
- */
- _getOffsetTimeEntry: function(input) {
- var inst = jQuery.data(input, PROP_NAME);
- var currentTime = (inst ? this._extractTime(inst) : null);
- return (!currentTime ? 0 : (currentTime[0] * 3600 + currentTime[1] * 60 + currentTime[2]) * 1000);
- },
-
- /* Initialise time entry.
- @param target (element) the input field or (event) the focus event
- */
- _doFocus: function(target) {
- var input = (target.nodeName && target.nodeName.toLowerCase() == 'input' ? target : this);
- if (jQuery.timeEntry._lastInput == input || jQuery.timeEntry._isDisabledTimeEntry(input)) {
- jQuery.timeEntry._focussed = false;
- return;
- }
- var inst = jQuery.data(input, PROP_NAME);
- jQuery.timeEntry._focussed = true;
- jQuery.timeEntry._lastInput = input;
- jQuery.timeEntry._blurredInput = null;
- jQuery.data(input, PROP_NAME, inst);
- jQuery.timeEntry._parseTime(inst);
- setTimeout(function() { jQuery.timeEntry._showField(inst); }, 10);
- },
-
- /* Note that the field has been exited.
- @param event (event) the blur event
- */
- _doBlur: function(event) {
- jQuery.timeEntry._blurredInput = jQuery.timeEntry._lastInput;
- jQuery.timeEntry._lastInput = null;
- },
-
- /* Select appropriate field portion on click, if already in the field.
- @param event (event) the click event
- */
- _doClick: function(event) {
- var input = event.target;
- var inst = jQuery.data(input, PROP_NAME);
- if (!jQuery.timeEntry._focussed) {
- var fieldSize = 3;
- inst._field = 0;
- if (input.selectionStart != null) { // Use input select range
- for (var field = 0; field <= Math.max(1, inst._secondField, inst._ampmField); field++) {
- var end = (field != inst._ampmField ? (field * fieldSize) + 2 : (inst._ampmField * fieldSize) + 2);
- inst._field = field;
- if (input.selectionStart < end) break;
- }
- } else if (input.createTextRange) { // Check against bounding boxes
- var src = jQuery(event.srcElement);
- var range = input.createTextRange();
- var convert = function(value) {
- return {thin: 2, medium: 4, thick: 6}[value] || value;
- };
- var offsetX = event.clientX + document.documentElement.scrollLeft -
- (src.offset().left + parseInt(convert(src.css('border-left-width')), 10)) -
- range.offsetLeft; // Position - left edge - alignment
- for (var field = 0; field <= Math.max(1, inst._secondField, inst._ampmField); field++) {
- var end = (field != inst._ampmField ? (field * fieldSize) + 2 : (inst._ampmField * fieldSize) + 2);
- range.collapse();
- range.moveEnd('character', end);
- inst._field = field;
- if (offsetX < range.boundingWidth) break; // And compare
- }
- }
- }
- jQuery.data(input, PROP_NAME, inst);
- jQuery.timeEntry._showField(inst);
- jQuery.timeEntry._focussed = false;
- },
-
- /* Handle keystrokes in the field.
- @param event (event) the keydown event
- @return (boolean) true to continue, false to stop processing
- */
- _doKeyDown: function(event) {
- if (event.keyCode >= 48) return true;
- var inst = jQuery.data(event.target, PROP_NAME);
-
- switch (event.keyCode) {
- case 9:
- var its = jQuery(':input');
- its.eq(its.index(this)+(event.shiftKey?-1:+1)).focus();
- break;
- case 37: jQuery.timeEntry._changeField(inst, -1, false); break; // Previous field on left
- case 38: jQuery.timeEntry._adjustField(inst, -1); break; // Increment time field on down
- case 16: if(!event.shiftKey) jQuery.timeEntry._changeField(inst, +1, false); break; // Next field on right
- case 39: jQuery.timeEntry._changeField(inst, +1, false); break; // Next field on right
- case 40: jQuery.timeEntry._adjustField(inst, +1); break; // Decrement time field on up
- case 32: case 46: jQuery.timeEntry._setValue(inst, ''); break; // Clear time on delete
- }
- return false;
- },
-
- /* Disallow unwanted characters.
- @param event (event) the keypress event
- @return (boolean) true to continue, false to stop processing
- */
- _doKeyPress: function(event) {
- var chr = String.fromCharCode(event.charCode == undefined ? event.keyCode : event.charCode);
- if (chr < ' ') return true;
- var inst = jQuery.data(event.target, PROP_NAME);
- jQuery.timeEntry._handleKeyPress(inst, chr);
- return false;
- },
-
- /* Get a setting value, defaulting if necessary.
- @param inst (object) the instance settings
- @param name (string) the setting name
- @return (any) the setting value
- */
- _get: function(inst, name) {
- return (inst.options[name] != null ? inst.options[name] : jQuery.timeEntry._defaults[name]);
- },
-
- /* Extract the time value from the input field, or default to now.
- @param inst (object) the instance settings
- */
- _parseTime: function(inst) {
- var currentTime = this._extractTime(inst);
- var showSeconds = this._get(inst, 'showSeconds');
- if (currentTime) {
- inst._selectedHour = currentTime[0];
- inst._selectedMinute = currentTime[1];
- inst._selectedSecond = currentTime[2];
- }
- else {
- var now = this._constrainTime(inst);
- inst._selectedHour = now[0];
- inst._selectedMinute = now[1];
- inst._selectedSecond = (showSeconds ? now[2] : 0);
- }
- inst._secondField = (showSeconds ? 2 : -1);
- inst._ampmField = (this._get(inst, 'show24Hours') ? -1 : (showSeconds ? 3 : 2));
- inst._lastChr = '';
- inst._field = Math.max(0, Math.min(Math.max(1, inst._secondField, inst._ampmField), 0));
- if (inst.input.val() != '') this._showTime(inst);
- },
-
- /* Extract the time value from a string as an array of values, or default to null.
- @param inst (object) the instance settings
- @param value (string) the time value to parse
- @return (number[3]) the time components (hours, minutes, seconds)
- or null if no value
- */
- _extractTime: function(inst, value) {
- value = value || inst.input.val();
- var currentTime = value.split(':');
- var ampmNames = this._get(inst, 'ampmNames');
- var show24Hours = this._get(inst, 'show24Hours');
- if (currentTime.length >= 2) {
- var isAM = !show24Hours && (value.indexOf(ampmNames[0]) > -1);
- var isPM = !show24Hours && (value.indexOf(ampmNames[1]) > -1);
- var hour = parseInt(currentTime[0], 10);
- hour = (isNaN(hour) ? 0 : hour);
- hour = ((isAM || isPM) && hour == 12 ? 0 : hour) + (isPM ? 12 : 0);
- var minute = parseInt(currentTime[1], 10);
- minute = (isNaN(minute) ? 0 : minute);
- var second = (currentTime.length >= 3 ?
- parseInt(currentTime[2], 10) : 0);
- second = (isNaN(second) || !this._get(inst, 'showSeconds') ? 0 : second);
- return this._constrainTime(inst, [hour, minute, second]);
- }
- return null;
- },
-
- /* Constrain the given/current time to the time steps.
- @param inst (object) the instance settings
- @param fields (number[3]) the current time components (hours, minutes, seconds)
- @return (number[3]) the constrained time components (hours, minutes, seconds)
- */
- _constrainTime: function(inst, fields) {
- var specified = (fields != null);
- if (!specified) {
- var now = this._determineTime(inst, this._get(inst, 'defaultTime')) || new Date();
- fields = [now.getHours(), now.getMinutes(), now.getSeconds()];
- }
- return fields;
- },
-
- /* Set the selected time into the input field.
- @param inst (object) the instance settings
- */
- _showTime: function(inst) {
- var show24Hours = this._get(inst, 'show24Hours');
- var currentTime = (this._formatNumber(show24Hours ? inst._selectedHour :
- ((inst._selectedHour + 11) % 12) + 1) + ':' +
- this._formatNumber(inst._selectedMinute) +
- (this._get(inst, 'showSeconds') ? ':' +
- this._formatNumber(inst._selectedSecond) : '') +
- (show24Hours ? '' : this._get(inst, 'ampmNames')[(inst._selectedHour < 12 ? 0 : 1)]));
- this._setValue(inst, currentTime);
- this._showField(inst);
- },
-
- /* Highlight the current time field.
- @param inst (object) the instance settings
- */
- _showField: function(inst) {
- var input = inst.input[0];
- if (inst.input.is(':hidden') || jQuery.timeEntry._lastInput != input) return;
- var fieldSize = 3;
- var start = (inst._field == inst._ampmField ? (inst._ampmField * fieldSize) - 1 : (inst._field * fieldSize));
- var end = start + (inst._field == inst._ampmField ? 2 : 2);
- if (input.setSelectionRange) { // Mozilla
- input.setSelectionRange(start, end);
- }
- else if (input.createTextRange) { // IE
- var range = input.createTextRange();
- range.moveStart('character', start);
- range.moveEnd('character', end - inst.input.val().length);
- range.select();
- }
- if (!input.disabled) input.focus();
- },
-
- /* Ensure displayed single number has a leading zero.
- @param value (number) current value
- @return (string) number with at least two digits
- */
- _formatNumber: function(value) {
- return (value < 10 ? '0' : '') + value;
- },
-
- /* Update the input field and notify listeners.
- @param inst (object) the instance settings
- @param value (string) the new value
- */
- _setValue: function(inst, value) {
- if (value != inst.input.val()) inst.input.val(value).trigger('change');
- },
-
- /* Move to previous/next field, or out of field altogether if appropriate.
- @param inst (object) the instance settings
- @param offset (number) the direction of change (-1, +1)
- @param moveOut (boolean) true if can move out of the field
- @return (boolean) true if exitting the field, false if not
- */
- _changeField: function(inst, offset, moveOut) {
- var atFirstLast = (inst.input.val() == '' || inst._field == (offset == -1 ? 0 : Math.max(1, inst._secondField, inst._ampmField)));
- if (!atFirstLast) inst._field += offset;
- this._showField(inst);
- inst._lastChr = '';
- jQuery.data(inst.input[0], PROP_NAME, inst);
- return (atFirstLast && moveOut);
- },
-
- /* Update the current field in the direction indicated.
- @param inst (object) the instance settings
- @param offset (number) the amount to change by
- */
- _adjustField: function(inst, offset) {
- if (inst.input.val() == '') offset = 0;
- this._setTime(inst, new Date(0, 0, 0,
- inst._selectedHour + (inst._field == 0 ? offset : 0) +
- (inst._field == inst._ampmField ? offset * 12 : 0),
- inst._selectedMinute + (inst._field == 1 ? offset : 0),
- inst._selectedSecond + (inst._field == inst._secondField ? offset : 0)));
- },
-
- /* Check against minimum/maximum and display time.
- @param inst (object) the instance settings
- @param time (Date) an actual time or
- (number) offset in seconds from now or
- (string) units and periods of offsets from now
- */
- _setTime: function(inst, time) {
- time = this._determineTime(inst, time);
- var fields = this._constrainTime(inst, time ?
- [time.getHours(), time.getMinutes(), time.getSeconds()] : null);
- time = new Date(0, 0, 0, fields[0], fields[1], fields[2]);
- // Normalise to base date
- var time = this._normaliseTime(time);
- var minTime = this._normaliseTime(this._determineTime(inst, this._get(inst, 'minTime')));
- var maxTime = this._normaliseTime(this._determineTime(inst, this._get(inst, 'maxTime')));
- // Ensure it is within the bounds set
- time = (minTime && time < minTime ? minTime :
- (maxTime && time > maxTime ? maxTime : time));
- inst._selectedHour = time.getHours();
- inst._selectedMinute = time.getMinutes();
- inst._selectedSecond = time.getSeconds();
- this._showTime(inst);
- jQuery.data(inst.input[0], PROP_NAME, inst);
- },
-
- /* Normalise time object to a common date.
- @param time (Date) the original time
- @return (Date) the normalised time
- */
- _normaliseTime: function(time) {
- if (!time) return null;
- time.setFullYear(1900);
- time.setMonth(0);
- time.setDate(0);
- return time;
- },
-
- /* A time may be specified as an exact value or a relative one.
- @param inst (object) the instance settings
- @param setting (Date) an actual time or
- (number) offset in seconds from now or
- (string) units and periods of offsets from now
- @return (Date) the calculated time
- */
- _determineTime: function(inst, setting) {
- var offsetNumeric = function(offset) { // E.g. +300, -2
- var time = new Date();
- time.setTime(time.getTime() + offset * 1000);
- return time;
- };
- var offsetString = function(offset) { // E.g. '+2m', '-4h', '+3h +30m' or '12:34:56PM'
- var fields = jQuery.timeEntry._extractTime(inst, offset); // Actual time?
- var time = new Date();
- var hour = (fields ? fields[0] : time.getHours());
- var minute = (fields ? fields[1] : time.getMinutes());
- var second = (fields ? fields[2] : time.getSeconds());
- if (!fields) {
- var pattern = /([+-]?[0-9]+)\s*(s|S|m|M|h|H)?/g;
- var matches = pattern.exec(offset);
- while (matches) {
- switch (matches[2] || 's') {
- case 's' : case 'S' : second += parseInt(matches[1], 10); break;
- case 'm' : case 'M' : minute += parseInt(matches[1], 10); break;
- case 'h' : case 'H' : hour += parseInt(matches[1], 10); break;
- }
- matches = pattern.exec(offset);
- }
- }
- time = new Date(0, 0, 10, hour, minute, second, 0);
- if (/^!/.test(offset)) { // No wrapping
- if (time.getDate() > 10)
- time = new Date(0, 0, 10, 23, 59, 59);
- else if (time.getDate() < 10)
- time = new Date(0, 0, 10, 0, 0, 0);
- }
- return time;
- };
- return (setting ? (typeof setting == 'string' ? offsetString(setting) :
- (typeof setting == 'number' ? offsetNumeric(setting) : setting)) : null);
- },
-
- /* Update time based on keystroke entered.
- @param inst (object) the instance settings
- @param chr (ch) the new character
- */
- _handleKeyPress: function(inst, chr) {
- if (chr == ':') this._changeField(inst, +1, false);
- else if (chr >= '0' && chr <= '9') { // Allow direct entry of time
- var key = parseInt(chr, 10);
- var value = parseInt(inst._lastChr + chr, 10);
- var show24Hours = this._get(inst, 'show24Hours');
- var hour = (inst._field != 0 ? inst._selectedHour :
- (show24Hours ? (value < 24 ? value : key) :
- (value >= 1 && value <= 12 ? value :
- (key > 0 ? key : inst._selectedHour)) % 12 +
- (inst._selectedHour >= 12 ? 12 : 0)));
- var minute = (inst._field != 1 ? inst._selectedMinute :
- (value < 60 ? value : key));
- var second = (inst._field != inst._secondField ? inst._selectedSecond :
- (value < 60 ? value : key));
- var fields = this._constrainTime(inst, [hour, minute, second]);
- this._setTime(inst, new Date(0, 0, 0, fields[0], fields[1], fields[2]));
- inst._lastChr = chr;
- }
- else if (!this._get(inst, 'show24Hours')) { // Set am/pm based on first char of names
- chr = chr.toLowerCase();
- var ampmNames = this._get(inst, 'ampmNames');
- if ((chr == ampmNames[0].substring(0, 1).toLowerCase() && inst._selectedHour >= 12) ||
- (chr == ampmNames[1].substring(0, 1).toLowerCase() && inst._selectedHour < 12)) {
- var saveField = inst._field;
- inst._field = inst._ampmField;
- this._adjustField(inst, +1);
- inst._field = saveField;
- this._showField(inst);
- }
- }
- }
- });
-
- /* jQuery extend now ignores nulls!
- @param target (object) the object to update
- @param props (object) the new settings
- @return (object) the updated object
- */
- function extendRemove(target, props) {
- jQuery.extend(target, props);
- for (var name in props) if (props[name] == null) target[name] = null;
- return target;
- }
-
- // Commands that don't return a jQuery object
- var getters = ['getOffset', 'getTime', 'isDisabled'];
-
- /* Attach the time entry functionality to a jQuery selection.
- @param command (string) the command to run (optional, default 'attach')
- @param options (object) the new settings to use for these countdown instances (optional)
- @return (jQuery) for chaining further calls
- */
- jQuery.fn.timeEntry = function(options) {
- var otherArgs = Array.prototype.slice.call(arguments, 1);
- if (typeof options == 'string' && jQuery.inArray(options, getters) > -1) {
- return jQuery.timeEntry['_' + options + 'TimeEntry'].apply(jQuery.timeEntry, [this[0]].concat(otherArgs));
- }
- return this.each(function() {
- var nodeName = this.nodeName.toLowerCase();
- if (nodeName == 'input') {
- if (typeof options == 'string')
- jQuery.timeEntry['_' + options + 'TimeEntry'].apply(jQuery.timeEntry, [this].concat(otherArgs));
- else {
- // Check for settings on the control itself
- var inlineSettings = (jQuery.fn.metadata ? jQuery(this).metadata() : {});
- jQuery.timeEntry._connectTimeEntry(this, jQuery.extend(inlineSettings, options));
- }
- }
- });
- };
-
- /* Initialise the time entry functionality. */
- jQuery.timeEntry = new TimeEntry(); // Singleton instance
-
-})(jQuery);
-
diff --git a/applications/admin/views/appadmin.html b/applications/admin/views/appadmin.html
index 1d967c2f..541a18bb 100644
--- a/applications/admin/views/appadmin.html
+++ b/applications/admin/views/appadmin.html
@@ -10,8 +10,9 @@
//-->
{{if request.function=='index':}}
-{{=T("Available databases and tables")}}
+{{=T("Available Databases and Tables")}}
{{if not databases:}}{{=T("No databases in this application")}}{{pass}}
+
{{for db in sorted(databases):}}
{{for table in databases[db].tables:}}
{{qry='%s.%s.id>0'%(db,table)}}
@@ -28,20 +29,24 @@
{{qry=''}}
{{pass}}
{{pass}}
-
- {{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
-
- [ {{=A(str(T('insert new'))+' '+table,_href=URL('insert',args=[db,table]))}} ]
-
+
+ |
+ {{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
+ |
+
+ {{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
+ |
+
{{pass}}
+
{{pass}}
{{elif request.function=='select':}}
- {{=XML(str(T("database %s select"))%A(request.args[0],_href=URL('index'))) }}
+ {{=XML(str(T("Database %s select"))%A(request.args[0],_href=URL('index'))) }}
- {{if table:}}
- [ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]
- {{=T("Rows in table")}}
+ {{if table:}}
+ {{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}
+ {{=T("Rows in Table")}}
{{else:}}
{{=T("Rows selected")}}
{{pass}}
@@ -51,8 +56,8 @@
{{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}
{{=T("%s selected", nrows)}}
- {{if start>0:}}[ {{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)))}} ]{{pass}}
- {{if stop0:}}{{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)),_class="btn")}}{{pass}}
+ {{if stop
{{linkto=URL('update',args=request.args[0])}}
@@ -61,35 +66,35 @@
{{pass}}
{{=T("Import/Export")}}
- [ {{=T("export as csv file")}} ]
+ {{=T("export as csv file")}}
{{=formcsv or ''}}
{{elif request.function=='insert':}}
- {{=T("database")}} {{=A(request.args[0],_href=URL('index'))}}
+ {{=T("Database")}} {{=A(request.args[0],_href=URL('index'))}}
{{if hasattr(table,'_primarykey'):}}
{{fieldname=table._primarykey[0]}}
{{dbname=request.args[0]}}
{{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}}
- {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
+ {{=T("Table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{else:}}
- {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
+ {{=T("Table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{pass}}
{{=T("New Record")}}
{{=form}}
{{elif request.function=='update':}}
- {{=T("database")}} {{=A(request.args[0],_href=URL('index'))}}
+ {{=T("Database")}} {{=A(request.args[0],_href=URL('index'))}}
{{if hasattr(table,'_primarykey'):}}
{{fieldname=request.vars.keys()[0]}}
{{dbname=request.args[0]}}
{{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}}
- {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
- {{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
+ {{=T("Table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
+ {{=T("Record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
{{else:}}
- {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
- {{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
+ {{=T("Table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
+ {{=T("Record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
{{pass}}
{{=T("Edit current record")}}
{{=form}}
diff --git a/applications/admin/views/default/ticket.html b/applications/admin/views/default/ticket.html
index 026b00bd..233305e6 100644
--- a/applications/admin/views/default/ticket.html
+++ b/applications/admin/views/default/ticket.html
@@ -13,12 +13,16 @@
web2py™ |
{{=myversion}} |
+ {{if snapshot:}}
| Python |
{{=snapshot.get('pyver','')}} |
+ {{pass}}
+
+{{if traceback or code or layer:}}
{{=T('Traceback')}}
{{=traceback}}
@@ -130,4 +134,6 @@
In file: {{=layer}}
{{=CODE(code.replace('\r',''),language='python',link='/examples/global/vars/')}}
-
+{{else:}}
+{{=T('Ticket Missing')}}
+{{pass}}
diff --git a/applications/examples/controllers/appadmin.py b/applications/examples/controllers/appadmin.py
index 126200b9..fa264bae 100644
--- a/applications/examples/controllers/appadmin.py
+++ b/applications/examples/controllers/appadmin.py
@@ -11,6 +11,8 @@ import copy
import gluon.contenttype
import gluon.fileutils
+response.subtitle = 'Database Administration (appadmin)'
+
# ## critical --- make a copy of the environment
global_env = copy.copy(globals())
diff --git a/applications/examples/static/images/gluon.png b/applications/examples/static/images/gluon.png
deleted file mode 100644
index 923fd182..00000000
Binary files a/applications/examples/static/images/gluon.png and /dev/null differ
diff --git a/applications/examples/static/images/logo3Tones.png b/applications/examples/static/images/logo3Tones.png
deleted file mode 100644
index 7bd5b3c5..00000000
Binary files a/applications/examples/static/images/logo3Tones.png and /dev/null differ
diff --git a/applications/examples/static/images/netdow1.png b/applications/examples/static/images/netdow1.png
deleted file mode 100644
index 6e9710ee..00000000
Binary files a/applications/examples/static/images/netdow1.png and /dev/null differ
diff --git a/applications/examples/static/images/netdow2.png b/applications/examples/static/images/netdow2.png
deleted file mode 100644
index de078ebf..00000000
Binary files a/applications/examples/static/images/netdow2.png and /dev/null differ
diff --git a/applications/examples/static/images/netdow3.png b/applications/examples/static/images/netdow3.png
deleted file mode 100644
index 268ee469..00000000
Binary files a/applications/examples/static/images/netdow3.png and /dev/null differ
diff --git a/applications/examples/static/images/tablet.png b/applications/examples/static/images/tablet.png
deleted file mode 100644
index 0917d0ad..00000000
Binary files a/applications/examples/static/images/tablet.png and /dev/null differ
diff --git a/applications/examples/static/images/tipDownloads.png b/applications/examples/static/images/tipDownloads.png
deleted file mode 100644
index 38d12e1e..00000000
Binary files a/applications/examples/static/images/tipDownloads.png and /dev/null differ
diff --git a/applications/examples/static/images/tipDownloads2.png b/applications/examples/static/images/tipDownloads2.png
deleted file mode 100644
index 2d21d43b..00000000
Binary files a/applications/examples/static/images/tipDownloads2.png and /dev/null differ
diff --git a/applications/examples/static/images/web2py_logo_light.png b/applications/examples/static/images/web2py_logo_light.png
deleted file mode 100644
index cf41af38..00000000
Binary files a/applications/examples/static/images/web2py_logo_light.png and /dev/null differ
diff --git a/applications/examples/static/js/common.js b/applications/examples/static/js/common.js
deleted file mode 100644
index 3be320c6..00000000
--- a/applications/examples/static/js/common.js
+++ /dev/null
@@ -1,116 +0,0 @@
-$(function(){
- // hide/show header
- $('#close-open-top a').bind('click', function() {
- if($('header:visible').length) {
- $('img', this).attr('src', 'images/open.png');
- } else {
- $('img', this).attr('src', 'images/close.png');
- }
- $('header').slideToggle('slow');
-
- return false;
- });
-
- // tabs
- $('.tab_content').hide();
- $('ul.tabs li:first').addClass('active').show();
- $('.tab_content:first').show();
-
- $('ul.tabs li').click(function() {
- $('ul.tabs li').removeClass('active');
- $(this).addClass('active');
- $('.tab_content').hide();
- var activeTab = $(this).find('a').attr('href');
- $(activeTab).fadeIn();
- return false;
- });
-
- // hide/show default text when user focuses on newsletter subscribe field
- var defaultEmailTxt = $('#email-address').val();
- $('#email-address').focus(function() {
- if ($('#email-address').val() == defaultEmailTxt) {
- $('#email-address').val('');
- }
- });
- $('#email-address').blur(function() {
- if ($('#email-address').val() == '') {
- $('#email-address').val(defaultEmailTxt);
- }
- });
-
- // Lightbox
- $(".gallery a[rel^='prettyPhoto']").prettyPhoto({animationSpeed:'slow',theme:'dark_rounded',slideshow:4000, autoplay_slideshow: false});
-
- // Tipsy
- $('#social li a img').tipsy({delayIn: 1200, delayOut: 1200, gravity: 's'});
-
- // init newsletter subscription AJAX handling
- $('#newslettersubmit').click(function() { $('#newsletterform').submit(); return false; });
- $('#newsletterform').ajaxForm({dataType: 'json',
- timeout: 2000,
- success: newsletterResponse});
-
- // Twitter script config
- if ($('#tweet').length) {
- getTwitters('tweet', {
- id: 'envatowebdesign',
- count: 3,
- enableLinks: true,
- ignoreReplies: true,
- template: '"%text%" %time%'});
- }
-
-
- // init contact form validation and AJAX handling
- if ($("#contactform").length > 0) {
- $("#contactform").validate({ rules: { name: "required",
- email: { required: true, email: true },
- message: "required"},
- messages: { name: "This field is required.",
- email: { required: "This field is required.",
- email: "Please enter a valied email address."},
- message: "This field is required."},
- submitHandler: function(form) { $(form).ajaxSubmit({dataType: 'json', success: contactFormResponse}); }
- });
- }
-});
-
-// handle newsletter subscribe AJAX response
-function newsletterResponse(response) {
- if (response.responseStatus == 'err') {
- if (response.responseMsg == 'ajax') {
- alert('Error - this script can only be invoked via an AJAX call.');
- } else if (response.responseMsg == 'fileopen') {
- alert('Error opening $emailsFile. Please refer to documentation for help.');
- } else if (response.responseMsg == 'email') {
- alert('Please enter a valid email address.');
- } else if (response.responseMsg == 'duplicate') {
- alert('You are already subscribed to our newsletter.');
- } else if (response.responseMsg == 'filewrite') {
- alert('Error writing to $emailsFile. Please refer to documentation for help.');
- } else {
- alert('Undocumented error. Please refresh the page and try again.');
- }
- } else if (response.responseStatus == 'ok') {
- alert('Thank you for subscribing to our newsletter! We will not abuse your address.');
- } else {
- alert('Undocumented error. Please refresh the page and try again.');
- }
-} // newsletterResponse
-
-// handle contact form AJAX response
-function contactFormResponse(response) {
- if (response.responseStatus == 'err') {
- if (response.responseMsg == 'ajax') {
- alert('Error - this script can only be invoked via an AJAX call.');
- } else if (response.responseMsg == 'notsent') {
- alert('We are having some mail server issues. Please refresh the page or try again later.');
- } else {
- alert('Undocumented error. Please refresh the page and try again.');
- }
- } else if (response.responseStatus == 'ok') {
- alert('Thank you for contacting us! We\'ll get back to you ASAP.');
- } else {
- alert('Undocumented error. Please refresh the page and try again.');
- }
-} // contactFormResponse
diff --git a/applications/examples/static/js/jquery-1.5-min.js b/applications/examples/static/js/jquery-1.5-min.js
deleted file mode 100644
index 761a1e95..00000000
--- a/applications/examples/static/js/jquery-1.5-min.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/*!
- * jQuery JavaScript Library v1.5.1
- * http://jquery.com/
- *
- * Copyright 2011, John Resig
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * Includes Sizzle.js
- * http://sizzlejs.com/
- * Copyright 2011, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- *
- * Date: Wed Feb 23 13:55:29 2011 -0500
- */
-(function(a,b){function cg(a){return d.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cd(a){if(!bZ[a]){var b=d("<"+a+">").appendTo("body"),c=b.css("display");b.remove();if(c==="none"||c==="")c="block";bZ[a]=c}return bZ[a]}function cc(a,b){var c={};d.each(cb.concat.apply([],cb.slice(0,b)),function(){c[this]=a});return c}function bY(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function bX(){try{return new a.XMLHttpRequest}catch(b){}}function bW(){d(a).unload(function(){for(var a in bU)bU[a](0,1)})}function bQ(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var e=a.dataTypes,f={},g,h,i=e.length,j,k=e[0],l,m,n,o,p;for(g=1;g=0===c})}function N(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function F(a,b){return(a&&a!=="*"?a+".":"")+b.replace(r,"`").replace(s,"&")}function E(a){var b,c,e,f,g,h,i,j,k,l,m,n,o,q=[],r=[],s=d._data(this,"events");if(a.liveFired!==this&&s&&s.live&&!a.target.disabled&&(!a.button||a.type!=="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var t=s.live.slice(0);for(i=0;ic)break;a.currentTarget=f.elem,a.data=f.handleObj.data,a.handleObj=f.handleObj,o=f.handleObj.origHandler.apply(f.elem,arguments);if(o===!1||a.isPropagationStopped()){c=f.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function C(a,c,e){var f=d.extend({},e[0]);f.type=a,f.originalEvent={},f.liveFired=b,d.event.handle.call(c,f),f.isDefaultPrevented()&&e[0].preventDefault()}function w(){return!0}function v(){return!1}function g(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function f(a,c,f){if(f===b&&a.nodeType===1){f=a.getAttribute("data-"+c);if(typeof f==="string"){try{f=f==="true"?!0:f==="false"?!1:f==="null"?null:d.isNaN(f)?e.test(f)?d.parseJSON(f):f:parseFloat(f)}catch(g){}d.data(a,c,f)}else f=b}return f}var c=a.document,d=function(){function I(){if(!d.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(I,1);return}d.ready()}}var d=function(a,b){return new d.fn.init(a,b,g)},e=a.jQuery,f=a.$,g,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,i=/\S/,j=/^\s+/,k=/\s+$/,l=/\d/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=navigator.userAgent,w,x=!1,y,z="then done fail isResolved isRejected promise".split(" "),A,B=Object.prototype.toString,C=Object.prototype.hasOwnProperty,D=Array.prototype.push,E=Array.prototype.slice,F=String.prototype.trim,G=Array.prototype.indexOf,H={};d.fn=d.prototype={constructor:d,init:function(a,e,f){var g,i,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!e&&c.body){this.context=c,this[0]=c.body,this.selector="body",this.length=1;return this}if(typeof a==="string"){g=h.exec(a);if(!g||!g[1]&&e)return!e||e.jquery?(e||f).find(a):this.constructor(e).find(a);if(g[1]){e=e instanceof d?e[0]:e,k=e?e.ownerDocument||e:c,j=m.exec(a),j?d.isPlainObject(e)?(a=[c.createElement(j[1])],d.fn.attr.call(a,e,!0)):a=[k.createElement(j[1])]:(j=d.buildFragment([g[1]],[k]),a=(j.cacheable?d.clone(j.fragment):j.fragment).childNodes);return d.merge(this,a)}i=c.getElementById(g[2]);if(i&&i.parentNode){if(i.id!==g[2])return f.find(a);this.length=1,this[0]=i}this.context=c,this.selector=a;return this}if(d.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return d.makeArray(a,this)},selector:"",jquery:"1.5.1",length:0,size:function(){return this.length},toArray:function(){return E.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var e=this.constructor();d.isArray(a)?D.apply(e,a):d.merge(e,a),e.prevObject=this,e.context=this.context,b==="find"?e.selector=this.selector+(this.selector?" ":"")+c:b&&(e.selector=this.selector+"."+b+"("+c+")");return e},each:function(a,b){return d.each(this,a,b)},ready:function(a){d.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(E.apply(this,arguments),"slice",E.call(arguments).join(","))},map:function(a){return this.pushStack(d.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:D,sort:[].sort,splice:[].splice},d.fn.init.prototype=d.fn,d.extend=d.fn.extend=function(){var a,c,e,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i==="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!=="object"&&!d.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;y.resolveWith(c,[d]),d.fn.trigger&&d(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!x){x=!0;if(c.readyState==="complete")return setTimeout(d.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",A,!1),a.addEventListener("load",d.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",A),a.attachEvent("onload",d.ready);var b=!1;try{b=a.frameElement==null}catch(e){}c.documentElement.doScroll&&b&&I()}}},isFunction:function(a){return d.type(a)==="function"},isArray:Array.isArray||function(a){return d.type(a)==="array"},isWindow:function(a){return a&&typeof a==="object"&&"setInterval"in a},isNaN:function(a){return a==null||!l.test(a)||isNaN(a)},type:function(a){return a==null?String(a):H[B.call(a)]||"object"},isPlainObject:function(a){if(!a||d.type(a)!=="object"||a.nodeType||d.isWindow(a))return!1;if(a.constructor&&!C.call(a,"constructor")&&!C.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a){}return c===b||C.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!=="string"||!b)return null;b=d.trim(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return a.JSON&&a.JSON.parse?a.JSON.parse(b):(new Function("return "+b))();d.error("Invalid JSON: "+b)},parseXML:function(b,c,e){a.DOMParser?(e=new DOMParser,c=e.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),e=c.documentElement,(!e||!e.nodeName||e.nodeName==="parsererror")&&d.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(a){if(a&&i.test(a)){var b=c.head||c.getElementsByTagName("head")[0]||c.documentElement,e=c.createElement("script");d.support.scriptEval()?e.appendChild(c.createTextNode(a)):e.text=a,b.insertBefore(e,b.firstChild),b.removeChild(e)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,e){var f,g=0,h=a.length,i=h===b||d.isFunction(a);if(e){if(i){for(f in a)if(c.apply(a[f],e)===!1)break}else for(;g1){var f=E.call(arguments,0),g=b,h=function(a){return function(b){f[a]=arguments.length>1?E.call(arguments,0):b,--g||c.resolveWith(e,f)}};while(b--)a=f[b],a&&d.isFunction(a.promise)?a.promise().then(h(b),c.reject):--g;g||c.resolveWith(e,f)}else c!==a&&c.resolve(a);return e},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}d.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.subclass=this.subclass,a.fn.init=function b(b,c){c&&c instanceof d&&!(c instanceof a)&&(c=a(c));return d.fn.init.call(this,b,c,e)},a.fn.init.prototype=a.fn;var e=a(c);return a},browser:{}}),y=d._Deferred(),d.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){H["[object "+b+"]"]=b.toLowerCase()}),w=d.uaMatch(v),w.browser&&(d.browser[w.browser]=!0,d.browser.version=w.version),d.browser.webkit&&(d.browser.safari=!0),G&&(d.inArray=function(a,b){return G.call(b,a)}),i.test(" ")&&(j=/^[\s\xA0]+/,k=/[\s\xA0]+$/),g=d(c),c.addEventListener?A=function(){c.removeEventListener("DOMContentLoaded",A,!1),d.ready()}:c.attachEvent&&(A=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",A),d.ready())});return d}();(function(){d.support={};var b=c.createElement("div");b.style.display="none",b.innerHTML=" a";var e=b.getElementsByTagName("*"),f=b.getElementsByTagName("a")[0],g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=b.getElementsByTagName("input")[0];if(e&&e.length&&f){d.support={leadingWhitespace:b.firstChild.nodeType===3,tbody:!b.getElementsByTagName("tbody").length,htmlSerialize:!!b.getElementsByTagName("link").length,style:/red/.test(f.getAttribute("style")),hrefNormalized:f.getAttribute("href")==="/a",opacity:/^0.55$/.test(f.style.opacity),cssFloat:!!f.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,deleteExpando:!0,optDisabled:!1,checkClone:!1,noCloneEvent:!0,noCloneChecked:!0,boxModel:null,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableHiddenOffsets:!0},i.checked=!0,d.support.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,d.support.optDisabled=!h.disabled;var j=null;d.support.scriptEval=function(){if(j===null){var b=c.documentElement,e=c.createElement("script"),f="script"+d.now();try{e.appendChild(c.createTextNode("window."+f+"=1;"))}catch(g){}b.insertBefore(e,b.firstChild),a[f]?(j=!0,delete a[f]):j=!1,b.removeChild(e),b=e=f=null}return j};try{delete b.test}catch(k){d.support.deleteExpando=!1}!b.addEventListener&&b.attachEvent&&b.fireEvent&&(b.attachEvent("onclick",function l(){d.support.noCloneEvent=!1,b.detachEvent("onclick",l)}),b.cloneNode(!0).fireEvent("onclick")),b=c.createElement("div"),b.innerHTML="";var m=c.createDocumentFragment();m.appendChild(b.firstChild),d.support.checkClone=m.cloneNode(!0).cloneNode(!0).lastChild.checked,d(function(){var a=c.createElement("div"),b=c.getElementsByTagName("body")[0];if(b){a.style.width=a.style.paddingLeft="1px",b.appendChild(a),d.boxModel=d.support.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,d.support.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="",d.support.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="";var e=a.getElementsByTagName("td");d.support.reliableHiddenOffsets=e[0].offsetHeight===0,e[0].style.display="",e[1].style.display="none",d.support.reliableHiddenOffsets=d.support.reliableHiddenOffsets&&e[0].offsetHeight===0,a.innerHTML="",b.removeChild(a).style.display="none",a=e=null}});var n=function(a){var b=c.createElement("div");a="on"+a;if(!b.attachEvent)return!0;var d=a in b;d||(b.setAttribute(a,"return;"),d=typeof b[a]==="function"),b=null;return d};d.support.submitBubbles=n("submit"),d.support.changeBubbles=n("change"),b=e=f=null}})();var e=/^(?:\{.*\}|\[.*\])$/;d.extend({cache:{},uuid:0,expando:"jQuery"+(d.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?d.cache[a[d.expando]]:a[d.expando];return!!a&&!g(a)},data:function(a,c,e,f){if(d.acceptData(a)){var g=d.expando,h=typeof c==="string",i,j=a.nodeType,k=j?d.cache:a,l=j?a[d.expando]:a[d.expando]&&d.expando;if((!l||f&&l&&!k[l][g])&&h&&e===b)return;l||(j?a[d.expando]=l=++d.uuid:l=d.expando),k[l]||(k[l]={},j||(k[l].toJSON=d.noop));if(typeof c==="object"||typeof c==="function")f?k[l][g]=d.extend(k[l][g],c):k[l]=d.extend(k[l],c);i=k[l],f&&(i[g]||(i[g]={}),i=i[g]),e!==b&&(i[c]=e);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[c]:i}},removeData:function(b,c,e){if(d.acceptData(b)){var f=d.expando,h=b.nodeType,i=h?d.cache:b,j=h?b[d.expando]:d.expando;if(!i[j])return;if(c){var k=e?i[j][f]:i[j];if(k){delete k[c];if(!g(k))return}}if(e){delete i[j][f];if(!g(i[j]))return}var l=i[j][f];d.support.deleteExpando||i!=a?delete i[j]:i[j]=null,l?(i[j]={},h||(i[j].toJSON=d.noop),i[j][f]=l):h&&(d.support.deleteExpando?delete b[d.expando]:b.removeAttribute?b.removeAttribute(d.expando):b[d.expando]=null)}},_data:function(a,b,c){return d.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=d.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),d.fn.extend({data:function(a,c){var e=null;if(typeof a==="undefined"){if(this.length){e=d.data(this[0]);if(this[0].nodeType===1){var g=this[0].attributes,h;for(var i=0,j=g.length;i-1)return!0;return!1},val:function(a){if(!arguments.length){var c=this[0];if(c){if(d.nodeName(c,"option")){var e=c.attributes.value;return!e||e.specified?c.value:c.text}if(d.nodeName(c,"select")){var f=c.selectedIndex,g=[],h=c.options,i=c.type==="select-one";if(f<0)return null;for(var k=i?f:0,l=i?f+1:h.length;k=0;else if(d.nodeName(this,"select")){var f=d.makeArray(e);d("option",this).each(function(){this.selected=d.inArray(d(this).val(),f)>=0}),f.length||(this.selectedIndex=-1)}else this.value=e}})}}),d.extend({attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,e,f){if(!a||a.nodeType===3||a.nodeType===8||a.nodeType===2)return b;if(f&&c in d.attrFn)return d(a)[c](e);var g=a.nodeType!==1||!d.isXMLDoc(a),h=e!==b;c=g&&d.props[c]||c;if(a.nodeType===1){var i=k.test(c);if(c==="selected"&&!d.support.optSelected){var j=a.parentNode;j&&(j.selectedIndex,j.parentNode&&j.parentNode.selectedIndex)}if((c in a||a[c]!==b)&&g&&!i){h&&(c==="type"&&l.test(a.nodeName)&&a.parentNode&&d.error("type property can't be changed"),e===null?a.nodeType===1&&a.removeAttribute(c):a[c]=e);if(d.nodeName(a,"form")&&a.getAttributeNode(c))return a.getAttributeNode(c).nodeValue;if(c==="tabIndex"){var o=a.getAttributeNode("tabIndex");return o&&o.specified?o.value:m.test(a.nodeName)||n.test(a.nodeName)&&a.href?0:b}return a[c]}if(!d.support.style&&g&&c==="style"){h&&(a.style.cssText=""+e);return a.style.cssText}h&&a.setAttribute(c,""+e);if(!a.attributes[c]&&(a.hasAttribute&&!a.hasAttribute(c)))return b;var p=!d.support.hrefNormalized&&g&&i?a.getAttribute(c,2):a.getAttribute(c);return p===null?b:p}h&&(a[c]=e);return a[c]}});var p=/\.(.*)$/,q=/^(?:textarea|input|select)$/i,r=/\./g,s=/ /g,t=/[^\w\s.|`]/g,u=function(a){return a.replace(t,"\\$&")};d.event={add:function(c,e,f,g){if(c.nodeType!==3&&c.nodeType!==8){try{d.isWindow(c)&&(c!==a&&!c.frameElement)&&(c=a)}catch(h){}if(f===!1)f=v;else if(!f)return;var i,j;f.handler&&(i=f,f=i.handler),f.guid||(f.guid=d.guid++);var k=d._data(c);if(!k)return;var l=k.events,m=k.handle;l||(k.events=l={}),m||(k.handle=m=function(){return typeof d!=="undefined"&&!d.event.triggered?d.event.handle.apply(m.elem,arguments):b}),m.elem=c,e=e.split(" ");var n,o=0,p;while(n=e[o++]){j=i?d.extend({},i):{handler:f,data:g},n.indexOf(".")>-1?(p=n.split("."),n=p.shift(),j.namespace=p.slice(0).sort().join(".")):(p=[],j.namespace=""),j.type=n,j.guid||(j.guid=f.guid);var q=l[n],r=d.event.special[n]||{};if(!q){q=l[n]=[];if(!r.setup||r.setup.call(c,g,p,m)===!1)c.addEventListener?c.addEventListener(n,m,!1):c.attachEvent&&c.attachEvent("on"+n,m)}r.add&&(r.add.call(c,j),j.handler.guid||(j.handler.guid=f.guid)),q.push(j),d.event.global[n]=!0}c=null}},global:{},remove:function(a,c,e,f){if(a.nodeType!==3&&a.nodeType!==8){e===!1&&(e=v);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=d.hasData(a)&&d._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(e=c.handler,c=c.type);if(!c||typeof c==="string"&&c.charAt(0)==="."){c=c||"";for(h in t)d.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+d.map(m.slice(0).sort(),u).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!e){for(j=0;j=0&&(a.type=f=f.slice(0,-1),a.exclusive=!0),e||(a.stopPropagation(),d.event.global[f]&&d.each(d.cache,function(){var b=d.expando,e=this[b];e&&e.events&&e.events[f]&&d.event.trigger(a,c,e.handle.elem)}));if(!e||e.nodeType===3||e.nodeType===8)return b;a.result=b,a.target=e,c=d.makeArray(c),c.unshift(a)}a.currentTarget=e;var h=d._data(e,"handle");h&&h.apply(e,c);var i=e.parentNode||e.ownerDocument;try{e&&e.nodeName&&d.noData[e.nodeName.toLowerCase()]||e["on"+f]&&e["on"+f].apply(e,c)===!1&&(a.result=!1,a.preventDefault())}catch(j){}if(!a.isPropagationStopped()&&i)d.event.trigger(a,c,i,!0);else if(!a.isDefaultPrevented()){var k,l=a.target,m=f.replace(p,""),n=d.nodeName(l,"a")&&m==="click",o=d.event.special[m]||{};if((!o._default||o._default.call(e,a)===!1)&&!n&&!(l&&l.nodeName&&d.noData[l.nodeName.toLowerCase()])){try{l[m]&&(k=l["on"+m],k&&(l["on"+m]=null),d.event.triggered=!0,l[m]())}catch(q){}k&&(l["on"+m]=k),d.event.triggered=!1}}},handle:function(c){var e,f,g,h,i,j=[],k=d.makeArray(arguments);c=k[0]=d.event.fix(c||a.event),c.currentTarget=this,e=c.type.indexOf(".")<0&&!c.exclusive,e||(g=c.type.split("."),c.type=g.shift(),j=g.slice(0).sort(),h=new RegExp("(^|\\.)"+j.join("\\.(?:.*\\.)?")+"(\\.|$)")),c.namespace=c.namespace||j.join("."),i=d._data(this,"events"),f=(i||{})[c.type];if(i&&f){f=f.slice(0);for(var l=0,m=f.length;l-1?d.map(a.options,function(a){return a.selected}).join("-"):"":a.nodeName.toLowerCase()==="select"&&(c=a.selectedIndex);return c},B=function B(a){var c=a.target,e,f;if(q.test(c.nodeName)&&!c.readOnly){e=d._data(c,"_change_data"),f=A(c),(a.type!=="focusout"||c.type!=="radio")&&d._data(c,"_change_data",f);if(e===b||f===e)return;if(e!=null||f)a.type="change",a.liveFired=b,d.event.trigger(a,arguments[1],c)}};d.event.special.change={filters:{focusout:B,beforedeactivate:B,click:function(a){var b=a.target,c=b.type;(c==="radio"||c==="checkbox"||b.nodeName.toLowerCase()==="select")&&B.call(this,a)},keydown:function(a){var b=a.target,c=b.type;(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&B.call(this,a)},beforeactivate:function(a){var b=a.target;d._data(b,"_change_data",A(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in z)d.event.add(this,c+".specialChange",z[c]);return q.test(this.nodeName)},teardown:function(a){d.event.remove(this,".specialChange");return q.test(this.nodeName)}},z=d.event.special.change.filters,z.focus=z.beforeactivate}c.addEventListener&&d.each({focus:"focusin",blur:"focusout"},function(a,b){function c(a){a=d.event.fix(a),a.type=b;return d.event.handle.call(this,a)}d.event.special[b]={setup:function(){this.addEventListener(a,c,!0)},teardown:function(){this.removeEventListener(a,c,!0)}}}),d.each(["bind","one"],function(a,c){d.fn[c]=function(a,e,f){if(typeof a==="object"){for(var g in a)this[c](g,e,a[g],f);return this}if(d.isFunction(e)||e===!1)f=e,e=b;var h=c==="one"?d.proxy(f,function(a){d(this).unbind(a,h);return f.apply(this,arguments)}):f;if(a==="unload"&&c!=="one")this.one(a,e,f);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},d.attrFn&&(d.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,e,g){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!=="string")return e;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(f.call(n)==="[object Array]")if(u)if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&e.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&e.push(j[t]);else e.push.apply(e,n);else p(n,e);o&&(k(o,h,e,g),k.uniqueSort(e));return e};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b==="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){return"text"===a.getAttribute("type")},radio:function(a){return"radio"===a.type},checkbox:function(a){return"checkbox"===a.type},file:function(a){return"file"===a.type},password:function(a){return"password"===a.type},submit:function(a){return"submit"===a.type},image:function(a){return"image"===a.type},reset:function(a){return"reset"===a.type},button:function(a){return"button"===a.type||a.nodeName.toLowerCase()==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(f.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length==="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!=="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!=="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!=="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!=="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector,d=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(e){d=!0}b&&(k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(d||!l.match.PSEUDO.test(c)&&!/!=/.test(c))return b.call(a,c)}catch(e){}return k(c,null,null,[a]).length>0})}(),function(){var a=c.createElement("div");a.innerHTML="";if(a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!=="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(var g=c;g0},closest:function(a,b){var c=[],e,f,g=this[0];if(d.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(e=0,f=a.length;e-1:d(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=L.test(a)?d(a,b||this.context):null;for(e=0,f=this.length;e-1:d.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b)break}}c=c.length>1?d.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a==="string")return d.inArray(this[0],a?d(a):this.parent().children());return d.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a==="string"?d(a,b):d.makeArray(a),e=d.merge(this.get(),c);return this.pushStack(N(c[0])||N(e[0])?e:d.unique(e))},andSelf:function(){return this.add(this.prevObject)}}),d.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return d.dir(a,"parentNode")},parentsUntil:function(a,b,c){return d.dir(a,"parentNode",c)},next:function(a){return d.nth(a,2,"nextSibling")},prev:function(a){return d.nth(a,2,"previousSibling")},nextAll:function(a){return d.dir(a,"nextSibling")},prevAll:function(a){return d.dir(a,"previousSibling")},nextUntil:function(a,b,c){return d.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return d.dir(a,"previousSibling",c)},siblings:function(a){return d.sibling(a.parentNode.firstChild,a)},children:function(a){return d.sibling(a.firstChild)},contents:function(a){return d.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:d.makeArray(a.childNodes)}},function(a,b){d.fn[a]=function(c,e){var f=d.map(this,b,c),g=K.call(arguments);G.test(a)||(e=c),e&&typeof e==="string"&&(f=d.filter(e,f)),f=this.length>1&&!M[a]?d.unique(f):f,(this.length>1||I.test(e))&&H.test(a)&&(f=f.reverse());return this.pushStack(f,a,g.join(","))}}),d.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?d.find.matchesSelector(b[0],a)?[b[0]]:[]:d.find.matches(a,b)},dir:function(a,c,e){var f=[],g=a[c];while(g&&g.nodeType!==9&&(e===b||g.nodeType!==1||!d(g).is(e)))g.nodeType===1&&f.push(g),g=g[c];return f},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var P=/ jQuery\d+="(?:\d+|null)"/g,Q=/^\s+/,R=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,S=/<([\w:]+)/,T=/",""],legend:[1,""],thead:[1,""],tr:[2,""],td:[3,""],col:[2,""],area:[1,""],_default:[0,"",""]};X.optgroup=X.option,X.tbody=X.tfoot=X.colgroup=X.caption=X.thead,X.th=X.td,d.support.htmlSerialize||(X._default=[1,"div","
"]),d.fn.extend({text:function(a){if(d.isFunction(a))return this.each(function(b){var c=d(this);c.text(a.call(this,b,c.text()))});if(typeof a!=="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return d.text(this)},wrapAll:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapAll(a.call(this,b))});if(this[0]){var b=d(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapInner(a.call(this,b))});return this.each(function(){var b=d(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){d(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){d.nodeName(this,"body")||d(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=d(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,d(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,e;(e=this[c])!=null;c++)if(!a||d.filter(a,[e]).length)!b&&e.nodeType===1&&(d.cleanData(e.getElementsByTagName("*")),d.cleanData([e])),e.parentNode&&e.parentNode.removeChild(e);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&d.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return d.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(P,""):null;if(typeof a!=="string"||V.test(a)||!d.support.leadingWhitespace&&Q.test(a)||X[(S.exec(a)||["",""])[1].toLowerCase()])d.isFunction(a)?this.each(function(b){var c=d(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);else{a=a.replace(R,"<$1>$2>");try{for(var c=0,e=this.length;c1&&l0?this.clone(!0):this).get();d(f[h])[b](j),e=e.concat(j)}return this.pushStack(e,a,f.selector)}}),d.extend({clone:function(a,b,c){var e=a.cloneNode(!0),f,g,h;if((!d.support.noCloneEvent||!d.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!d.isXMLDoc(a)){$(a,e),f=_(a),g=_(e);for(h=0;f[h];++h)$(f[h],g[h])}if(b){Z(a,e);if(c){f=_(a),g=_(e);for(h=0;f[h];++h)Z(f[h],g[h])}}return e},clean:function(a,b,e,f){b=b||c,typeof b.createElement==="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var g=[];for(var h=0,i;(i=a[h])!=null;h++){typeof i==="number"&&(i+="");if(!i)continue;if(typeof i!=="string"||U.test(i)){if(typeof i==="string"){i=i.replace(R,"<$1>$2>");var j=(S.exec(i)||["",""])[1].toLowerCase(),k=X[j]||X._default,l=k[0],m=b.createElement("div");m.innerHTML=k[1]+i+k[2];while(l--)m=m.lastChild;if(!d.support.tbody){var n=T.test(i),o=j==="table"&&!n?m.firstChild&&m.firstChild.childNodes:k[1]===""&&!n?m.childNodes:[];for(var p=o.length-1;p>=0;--p)d.nodeName(o[p],"tbody")&&!o[p].childNodes.length&&o[p].parentNode.removeChild(o[p])}!d.support.leadingWhitespace&&Q.test(i)&&m.insertBefore(b.createTextNode(Q.exec(i)[0]),m.firstChild),i=m.childNodes}}else i=b.createTextNode(i);i.nodeType?g.push(i):g=d.merge(g,i)}if(e)for(h=0;g[h];h++)!f||!d.nodeName(g[h],"script")||g[h].type&&g[h].type.toLowerCase()!=="text/javascript"?(g[h].nodeType===1&&g.splice.apply(g,[h+1,0].concat(d.makeArray(g[h].getElementsByTagName("script")))),e.appendChild(g[h])):f.push(g[h].parentNode?g[h].parentNode.removeChild(g[h]):g[h]);return g},cleanData:function(a){var b,c,e=d.cache,f=d.expando,g=d.event.special,h=d.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&d.noData[j.nodeName.toLowerCase()])continue;c=j[d.expando];if(c){b=e[c]&&e[c][f];if(b&&b.events){for(var k in b.events)g[k]?d.event.remove(j,k):d.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[d.expando]:j.removeAttribute&&j.removeAttribute(d.expando),delete e[c]}}}});var bb=/alpha\([^)]*\)/i,bc=/opacity=([^)]*)/,bd=/-([a-z])/ig,be=/([A-Z])/g,bf=/^-?\d+(?:px)?$/i,bg=/^-?\d/,bh={position:"absolute",visibility:"hidden",display:"block"},bi=["Left","Right"],bj=["Top","Bottom"],bk,bl,bm,bn=function(a,b){return b.toUpperCase()};d.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return d.access(this,a,c,!0,function(a,c,e){return e!==b?d.style(a,c,e):d.css(a,c)})},d.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bk(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0},cssProps:{"float":d.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,e,f){if(a&&a.nodeType!==3&&a.nodeType!==8&&a.style){var g,h=d.camelCase(c),i=a.style,j=d.cssHooks[h];c=d.cssProps[h]||h;if(e===b){if(j&&"get"in j&&(g=j.get(a,!1,f))!==b)return g;return i[c]}if(typeof e==="number"&&isNaN(e)||e==null)return;typeof e==="number"&&!d.cssNumber[h]&&(e+="px");if(!j||!("set"in j)||(e=j.set(a,e))!==b)try{i[c]=e}catch(k){}}},css:function(a,c,e){var f,g=d.camelCase(c),h=d.cssHooks[g];c=d.cssProps[g]||g;if(h&&"get"in h&&(f=h.get(a,!0,e))!==b)return f;if(bk)return bk(a,c,g)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bd,bn)}}),d.curCSS=d.css,d.each(["height","width"],function(a,b){d.cssHooks[b]={get:function(a,c,e){var f;if(c){a.offsetWidth!==0?f=bo(a,b,e):d.swap(a,bh,function(){f=bo(a,b,e)});if(f<=0){f=bk(a,b,b),f==="0px"&&bm&&(f=bm(a,b,b));if(f!=null)return f===""||f==="auto"?"0px":f}if(f<0||f==null){f=a.style[b];return f===""||f==="auto"?"0px":f}return typeof f==="string"?f:f+"px"}},set:function(a,b){if(!bf.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),d.support.opacity||(d.cssHooks.opacity={get:function(a,b){return bc.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style;c.zoom=1;var e=d.isNaN(b)?"":"alpha(opacity="+b*100+")",f=c.filter||"";c.filter=bb.test(f)?f.replace(bb,e):c.filter+" "+e}}),c.defaultView&&c.defaultView.getComputedStyle&&(bl=function(a,c,e){var f,g,h;e=e.replace(be,"-$1").toLowerCase();if(!(g=a.ownerDocument.defaultView))return b;if(h=g.getComputedStyle(a,null))f=h.getPropertyValue(e),f===""&&!d.contains(a.ownerDocument.documentElement,a)&&(f=d.style(a,e));return f}),c.documentElement.currentStyle&&(bm=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bf.test(d)&&bg.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bk=bl||bm,d.expr&&d.expr.filters&&(d.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!d.support.reliableHiddenOffsets&&(a.style.display||d.css(a,"display"))==="none"},d.expr.filters.visible=function(a){return!d.expr.filters.hidden(a)});var bp=/%20/g,bq=/\[\]$/,br=/\r?\n/g,bs=/#.*$/,bt=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bu=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bv=/(?:^file|^widget|\-extension):$/,bw=/^(?:GET|HEAD)$/,bx=/^\/\//,by=/\?/,bz=/
{{if request.function=='index':}}
-{{=T("Available databases and tables")}}
+{{=T("Available Databases and Tables")}}
{{if not databases:}}{{=T("No databases in this application")}}{{pass}}
+
{{for db in sorted(databases):}}
{{for table in databases[db].tables:}}
{{qry='%s.%s.id>0'%(db,table)}}
@@ -28,20 +29,24 @@
{{qry=''}}
{{pass}}
{{pass}}
-
- {{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
-
- [ {{=A(str(T('insert new'))+' '+table,_href=URL('insert',args=[db,table]))}} ]
-
+
+ |
+ {{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
+ |
+
+ {{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
+ |
+
{{pass}}
+
{{pass}}
{{elif request.function=='select':}}
- {{=XML(str(T("database %s select"))%A(request.args[0],_href=URL('index'))) }}
+ {{=XML(str(T("Database %s select"))%A(request.args[0],_href=URL('index'))) }}
- {{if table:}}
- [ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]
- {{=T("Rows in table")}}
+ {{if table:}}
+ {{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}
+ {{=T("Rows in Table")}}
{{else:}}
{{=T("Rows selected")}}
{{pass}}
@@ -51,8 +56,8 @@
{{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}
{{=T("%s selected", nrows)}}
- {{if start>0:}}[ {{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)))}} ]{{pass}}
- {{if stop0:}}{{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)),_class="btn")}}{{pass}}
+ {{if stop
{{linkto=URL('update',args=request.args[0])}}
@@ -61,35 +66,35 @@
{{pass}}
{{=T("Import/Export")}}
- [ {{=T("export as csv file")}} ]
+ {{=T("export as csv file")}}
{{=formcsv or ''}}
{{elif request.function=='insert':}}
- {{=T("database")}} {{=A(request.args[0],_href=URL('index'))}}
+ {{=T("Database")}} {{=A(request.args[0],_href=URL('index'))}}
{{if hasattr(table,'_primarykey'):}}
{{fieldname=table._primarykey[0]}}
{{dbname=request.args[0]}}
{{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}}
- {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
+ {{=T("Table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{else:}}
- {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
+ {{=T("Table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{pass}}
{{=T("New Record")}}
{{=form}}
{{elif request.function=='update':}}
- {{=T("database")}} {{=A(request.args[0],_href=URL('index'))}}
+ {{=T("Database")}} {{=A(request.args[0],_href=URL('index'))}}
{{if hasattr(table,'_primarykey'):}}
{{fieldname=request.vars.keys()[0]}}
{{dbname=request.args[0]}}
{{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}}
- {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
- {{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
+ {{=T("Table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
+ {{=T("Record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
{{else:}}
- {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
- {{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
+ {{=T("Table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
+ {{=T("Record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
{{pass}}
{{=T("Edit current record")}}
{{=form}}
diff --git a/applications/welcome/controllers/appadmin.py b/applications/welcome/controllers/appadmin.py
index 126200b9..fa264bae 100644
--- a/applications/welcome/controllers/appadmin.py
+++ b/applications/welcome/controllers/appadmin.py
@@ -11,6 +11,8 @@ import copy
import gluon.contenttype
import gluon.fileutils
+response.subtitle = 'Database Administration (appadmin)'
+
# ## critical --- make a copy of the environment
global_env = copy.copy(globals())
diff --git a/applications/welcome/languages/cs.py b/applications/welcome/languages/cs.py
index ba362a3f..85c72dce 100644
--- a/applications/welcome/languages/cs.py
+++ b/applications/welcome/languages/cs.py
@@ -10,7 +10,7 @@
'%s selected': '%s označených',
'Administrative interface': 'pro administrátorské rozhranie kliknite sem',
'Are you sure you want to delete this object?': 'Opravdu chceš odstranit tento objekt?',
-'Available databases and tables': 'Dostupné databáze a tabuľky',
+'Available Databases and Tables': 'Dostupné databáze a tabuľky',
'Cannot be empty': 'Nemůže být prázdné',
'Change password': 'Změna hesla',
'Check to delete': 'Označit ke smazání',
@@ -68,7 +68,7 @@
'Reset Password key': 'Nastavit registrační kľíč',
'Retrieve username': 'Retrieve username',
'Role': 'Role',
-'Rows in table': 'řádků v tabulce',
+'Rows in Table': 'řádků v tabulce',
'Rows selected': 'označených řádků',
'Stylesheet': 'CSS',
'Submit': 'Odeslat',
@@ -100,8 +100,8 @@
'cache': 'cache',
'customize me!': 'uprav mě!',
'data uploaded': 'data nahrána',
-'database': 'databáze',
-'database %s select': 'databáze %s výber',
+'Database': 'databáze',
+'Database %s select': 'databáze %s výber',
'db': 'db',
'design': 'návrh',
'done!': 'hotovo!',
@@ -121,11 +121,11 @@
'password': 'heslo',
'previous 100 rows': 'předchádzajících 100 řádků',
'profile': 'profil',
-'record': 'záznam',
+'Record': 'záznam',
'record does not exist': 'záznam neexistuje',
-'record id': 'id záznamu',
+'Record id': 'id záznamu',
'register': 'registrovat',
'state': 'stav',
-'table': 'tabulka',
+'Table': 'tabulka',
'unable to parse csv file': 'nedá sa zpracovat csv soubor',
}
diff --git a/applications/welcome/languages/default.py b/applications/welcome/languages/default.py
index 17cc65dc..7fddedd2 100644
--- a/applications/welcome/languages/default.py
+++ b/applications/welcome/languages/default.py
@@ -2,4 +2,15 @@
{
'!langcode!': 'en-us',
'!langname!': 'English (US)',
+'%s %%(shop)': '%s %%(shop)',
+'%s %%(shop[0])': '%s %%(shop[0])',
+'%s %%{shop[0]}': '%s %%{shop[0]}',
+'%s %%{shop}': '%s %%{shop}',
+'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
+'@markmin\x01**Hello World**': '**Hello World**',
+'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g',
+'enter date and time as %(format)s': 'enter date and time as %(format)s',
+'Hello World': 'Hello World',
+'Hello World ## comment': 'Hello World ',
+'Hello World## comment': 'Hello World',
}
diff --git a/applications/welcome/languages/es.py b/applications/welcome/languages/es.py
index cd54bced..1780400c 100644
--- a/applications/welcome/languages/es.py
+++ b/applications/welcome/languages/es.py
@@ -24,7 +24,7 @@
'Are you sure you want to uninstall application "%s"': '¿Está seguro que desea desinstalar la aplicación "%s"',
'Are you sure you want to uninstall application "%s"?': '¿Está seguro que desea desinstalar la aplicación "%s"?',
'Authentication': 'Autenticación',
-'Available databases and tables': 'Bases de datos y tablas disponibles',
+'Available Databases and Tables': 'Bases de datos y tablas disponibles',
'Cannot be empty': 'No puede estar vacío',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'No se puede compilar: hay errores en su aplicación. Depure, corrija errores y vuelva a intentarlo.',
'Change Password': 'Cambie Contraseña',
@@ -99,7 +99,7 @@
'Reset Password key': 'Reset Password key',
'Resolve Conflict file': 'archivo Resolución de Conflicto',
'Role': 'Rol',
-'Rows in table': 'Filas en la tabla',
+'Rows in Table': 'Filas en la tabla',
'Rows selected': 'Filas seleccionadas',
'Saved file hash:': 'Hash del archivo guardado:',
'Static files': 'Archivos estáticos',
@@ -165,8 +165,8 @@
'currently saved or': 'actualmente guardado o',
'customize me!': 'Adaptame!',
'data uploaded': 'datos subidos',
-'database': 'base de datos',
-'database %s select': 'selección en base de datos %s',
+'Database': 'base de datos',
+'Database %s select': 'selección en base de datos %s',
'database administration': 'administración base de datos',
'db': 'db',
'defines tables': 'define tablas',
@@ -218,9 +218,9 @@
'pack all': 'empaquetar todo',
'pack compiled': 'empaquete compiladas',
'previous 100 rows': '100 filas anteriores',
-'record': 'registro',
+'Record': 'registro',
'record does not exist': 'el registro no existe',
-'record id': 'id de registro',
+'Record id': 'id de registro',
'register': 'registrese',
'remove compiled': 'eliminar compiladas',
'restore': 'restaurar',
@@ -232,7 +232,7 @@
'some files could not be removed': 'algunos archivos no pudieron ser removidos',
'state': 'estado',
'static': 'estáticos',
-'table': 'tabla',
+'Table': 'tabla',
'test': 'probar',
'the application logic, each URL path is mapped in one exposed function in the controller': 'la lógica de la aplicación, cada ruta URL se mapea en una función expuesta en el controlador',
'the data representation, define database tables and sets': 'la representación de datos, define tablas y conjuntos de base de datos',
diff --git a/applications/welcome/languages/fr-ca.py b/applications/welcome/languages/fr-ca.py
index 599cddbf..2491a9ea 100644
--- a/applications/welcome/languages/fr-ca.py
+++ b/applications/welcome/languages/fr-ca.py
@@ -14,7 +14,7 @@
'Ajax Recipes': 'Recettes Ajax',
'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
'Authentication': 'Authentification',
-'Available databases and tables': 'Bases de données et tables disponibles',
+'Available Databases and Tables': 'Bases de données et tables disponibles',
'Buy this book': 'Acheter ce livre',
'Cannot be empty': 'Ne peut pas être vide',
'Check to delete': 'Cliquez pour supprimer',
@@ -95,7 +95,7 @@
'Reset Password key': 'Réinitialiser le mot clé',
'Resources': 'Ressources',
'Role': 'Rôle',
-'Rows in table': 'Lignes du tableau',
+'Rows in Table': 'Lignes du tableau',
'Rows selected': 'Lignes sélectionnées',
'Semantic': 'Sémantique',
'Services': 'Services',
@@ -135,8 +135,8 @@
'change password': 'changer le mot de passe',
'customize me!': 'personnalisez-moi!',
'data uploaded': 'données téléchargées',
-'database': 'base de données',
-'database %s select': 'base de données %s select',
+'Database': 'base de données',
+'Database %s select': 'base de données %s select',
'db': 'db',
'design': 'design',
'done!': 'fait!',
@@ -157,12 +157,12 @@
'please input your password again': "S'il vous plaît entrer votre mot de passe",
'previous 100 rows': '100 lignes précédentes',
'profile': 'profile',
-'record': 'enregistrement',
+'Record': 'enregistrement',
'record does not exist': "l'archive n'existe pas",
-'record id': "id d'enregistrement",
+'Record id': "id d'enregistrement",
'register': "s'inscrire",
'state': 'état',
-'table': 'tableau',
+'Table': 'tableau',
'unable to parse csv file': "incapable d'analyser le fichier cvs",
'value already in database or empty': 'valeur déjà dans la base ou vide',
}
diff --git a/applications/welcome/languages/fr.py b/applications/welcome/languages/fr.py
index 8db13409..f7893576 100644
--- a/applications/welcome/languages/fr.py
+++ b/applications/welcome/languages/fr.py
@@ -16,7 +16,7 @@
'appadmin is disabled because insecure channel': "appadmin est désactivée parce que le canal n'est pas sécurisé",
'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
'Authentication': 'Authentification',
-'Available databases and tables': 'Bases de données et tables disponibles',
+'Available Databases and Tables': 'Bases de données et tables disponibles',
'Buy this book': 'Acheter ce livre',
'cache': 'cache',
'Cannot be empty': 'Ne peut pas être vide',
@@ -34,8 +34,8 @@
'customize me!': 'personnalisez-moi!',
'data uploaded': 'données téléchargées',
'Database': 'Base de données',
-'database': 'base de données',
-'database %s select': 'base de données %s select',
+'Database': 'base de données',
+'Database %s select': 'base de données %s select',
'db': 'db',
'DB Model': 'Modèle DB',
'Delete:': 'Supprimer:',
@@ -114,9 +114,9 @@
'Quick Examples': 'Examples Rapides',
'Readme': 'Lisez-moi',
'Recipes': 'Recettes',
-'record': 'enregistrement',
+'Record': 'enregistrement',
'record does not exist': "l'archive n'existe pas",
-'record id': "id d'enregistrement",
+'Record id': "id d'enregistrement",
'Record ID': "ID d'enregistrement",
'Register': "S'inscrire",
'register': "s'inscrire",
@@ -127,7 +127,7 @@
'Reset Password key': 'Réinitialiser le mot clé',
'Resources': 'Ressources',
'Role': 'Rôle',
-'Rows in table': 'Lignes du tableau',
+'Rows in Table': 'Lignes du tableau',
'Rows selected': 'Lignes sélectionnées',
'Semantic': 'Sémantique',
'Services': 'Services',
@@ -136,7 +136,7 @@
'Submit': 'Soumettre',
'Support': 'Support',
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
-'table': 'tableau',
+'Table': 'tableau',
'Table name': 'Nom du tableau',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "query" est une condition comme "db.table1.champ1==\'valeur\'". Quelque chose comme "db.table1.champ1==db.table2.champ2" résulte en un JOIN SQL.',
'The Core': 'Le noyau',
diff --git a/applications/welcome/languages/hi.py b/applications/welcome/languages/hi.py
index e2bbc7d1..8dc9f19f 100644
--- a/applications/welcome/languages/hi.py
+++ b/applications/welcome/languages/hi.py
@@ -9,7 +9,7 @@
'%s %%{row} updated': '%s पंक्तियाँ अद्यतन',
'%s selected': '%s चुना हुआ',
'Administrative interface': 'प्रशासनिक इंटरफेस के लिए यहाँ क्लिक करें',
-'Available databases and tables': 'उपलब्ध डेटाबेस और तालिका',
+'Available Databases and Tables': 'उपलब्ध डेटाबेस और तालिका',
'Cannot be empty': 'खाली नहीं हो सकता',
'Change Password': 'पासवर्ड बदलें',
'Check to delete': 'हटाने के लिए चुनें',
@@ -43,7 +43,7 @@
'Powered by': 'Powered by',
'Query:': 'प्रश्न:',
'Register': 'पंजीकृत (रजिस्टर) करना ',
-'Rows in table': 'तालिका में पंक्तियाँ ',
+'Rows in Table': 'तालिका में पंक्तियाँ ',
'Rows selected': 'चयनित (चुने गये) पंक्तियाँ ',
'Stylesheet': 'Stylesheet',
'Sure you want to delete this object?': 'सुनिश्चित हैं कि आप इस वस्तु को हटाना चाहते हैं?',
@@ -61,8 +61,8 @@
'change password': 'change password',
'customize me!': 'मुझे अनुकूलित (कस्टमाइज़) करें!',
'data uploaded': 'डाटा अपलोड सम्पन्न ',
-'database': 'डेटाबेस',
-'database %s select': 'डेटाबेस %s चुनी हुई',
+'Database': 'डेटाबेस',
+'Database %s select': 'डेटाबेस %s चुनी हुई',
'db': 'db',
'design': 'रचना करें',
'done!': 'हो गया!',
@@ -77,11 +77,11 @@
'next 100 rows': 'अगले 100 पंक्तियाँ',
'or import from csv file': 'या csv फ़ाइल से आयात',
'previous 100 rows': 'पिछले 100 पंक्तियाँ',
-'record': 'record',
+'Record': 'Record',
'record does not exist': 'रिकॉर्ड मौजूद नहीं है',
-'record id': 'रिकॉर्ड पहचानकर्ता (आईडी)',
+'Record id': 'रिकॉर्ड पहचानकर्ता (आईडी)',
'register': 'register',
'state': 'स्थिति',
-'table': 'तालिका',
+'Table': 'तालिका',
'unable to parse csv file': 'csv फ़ाइल पार्स करने में असमर्थ',
}
diff --git a/applications/welcome/languages/hu.py b/applications/welcome/languages/hu.py
index a5133a63..bd996f66 100644
--- a/applications/welcome/languages/hu.py
+++ b/applications/welcome/languages/hu.py
@@ -9,7 +9,7 @@
'%s %%{row} updated': '%s sorok frissítődtek',
'%s selected': '%s kiválasztott',
'Administrative interface': 'az adminisztrációs felületért kattints ide',
-'Available databases and tables': 'Elérhető adatbázisok és táblák',
+'Available Databases and Tables': 'Elérhető adatbázisok és táblák',
'Cannot be empty': 'Nem lehet üres',
'Check to delete': 'Törléshez válaszd ki',
'Client IP': 'Client IP',
@@ -50,7 +50,7 @@
'Registration key': 'Registration key',
'Reset Password key': 'Reset Password key',
'Role': 'Role',
-'Rows in table': 'Sorok a táblában',
+'Rows in Table': 'Sorok a táblában',
'Rows selected': 'Kiválasztott sorok',
'Stylesheet': 'Stylesheet',
'Sure you want to delete this object?': 'Biztos törli ezt az objektumot?',
@@ -71,8 +71,8 @@
'change password': 'jelszó megváltoztatása',
'customize me!': 'változtass meg!',
'data uploaded': 'adat feltöltve',
-'database': 'adatbázis',
-'database %s select': 'adatbázis %s kiválasztás',
+'Database': 'adatbázis',
+'Database %s select': 'adatbázis %s kiválasztás',
'db': 'db',
'design': 'design',
'done!': 'kész!',
@@ -88,11 +88,11 @@
'next 100 rows': 'következő 100 sor',
'or import from csv file': 'vagy betöltés csv fájlból',
'previous 100 rows': 'előző 100 sor',
-'record': 'bejegyzés',
+'Record': 'bejegyzés',
'record does not exist': 'bejegyzés nem létezik',
-'record id': 'bejegyzés id',
+'Record id': 'bejegyzés id',
'register': 'regisztráció',
'state': 'állapot',
-'table': 'tábla',
+'Table': 'tábla',
'unable to parse csv file': 'nem lehet a csv fájlt beolvasni',
}
diff --git a/applications/welcome/languages/it.py b/applications/welcome/languages/it.py
index afa3109a..a73486c8 100644
--- a/applications/welcome/languages/it.py
+++ b/applications/welcome/languages/it.py
@@ -9,99 +9,178 @@
'%s selected': '%s selezionato',
'%Y-%m-%d': '%d/%m/%Y',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
+'@markmin\x01Number of entries: **%s**': 'Number of entries: **%s**',
+'About': 'About',
+'Access Control': 'Access Control',
+'Administrative Interface': 'Administrative Interface',
'Administrative interface': 'Interfaccia amministrativa',
+'Ajax Recipes': 'Ajax Recipes',
'appadmin is disabled because insecure channel': 'Amministrazione (appadmin) disabilitata: comunicazione non sicura',
-'Available databases and tables': 'Database e tabelle disponibili',
+'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
+'Available Databases and Tables': 'Database e tabelle disponibili',
+'Buy this book': 'Buy this book',
'cache': 'cache',
'Cannot be empty': 'Non può essere vuoto',
'change password': 'Cambia password',
'Check to delete': 'Seleziona per cancellare',
+'Clear CACHE?': 'Clear CACHE?',
+'Clear DISK': 'Clear DISK',
+'Clear RAM': 'Clear RAM',
'Client IP': 'Client IP',
+'Community': 'Community',
+'Components and Plugins': 'Components and Plugins',
'Controller': 'Controller',
'Copyright': 'Copyright',
+'Created By': 'Created By',
+'Created On': 'Created On',
'Current request': 'Richiesta (request) corrente',
'Current response': 'Risposta (response) corrente',
'Current session': 'Sessione (session) corrente',
'customize me!': 'Personalizzami!',
'data uploaded': 'dati caricati',
'Database': 'Database',
-'database': 'database',
-'database %s select': 'database %s select',
+'Database %s select': 'Database %s select',
'db': 'db',
'DB Model': 'Modello di DB',
'Delete': 'Delete',
'Delete:': 'Cancella:',
+'Demo': 'Demo',
+'Deployment Recipes': 'Deployment Recipes',
'Description': 'Descrizione',
'design': 'progetta',
+'DISK': 'DISK',
+'Disk Cleared': 'Disk Cleared',
'Documentation': 'Documentazione',
+"Don't know what to do?": "Don't know what to do?",
'done!': 'fatto!',
+'Download': 'Download',
'E-mail': 'E-mail',
'Edit': 'Modifica',
'Edit current record': 'Modifica record corrente',
'edit profile': 'modifica profilo',
'Edit This App': 'Modifica questa applicazione',
+'Email and SMS': 'Email and SMS',
+'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g',
+'Errors': 'Errors',
'export as csv file': 'esporta come file CSV',
+'FAQ': 'FAQ',
'First name': 'Nome',
+'Forms and Validators': 'Forms and Validators',
+'Free Applications': 'Free Applications',
+'Group %(group_id)s created': 'Group %(group_id)s created',
'Group ID': 'ID Gruppo',
+'Group uniquely assigned to user %(id)s': 'Group uniquely assigned to user %(id)s',
+'Groups': 'Groups',
'hello': 'hello',
'hello world': 'salve mondo',
'Hello World': 'Salve Mondo',
'Hello World in a flash!': 'Salve Mondo in un flash!',
+'Home': 'Home',
+'How did you get here?': 'How did you get here?',
+'import': 'import',
'Import/Export': 'Importa/Esporta',
'Index': 'Indice',
'insert new': 'inserisci nuovo',
'insert new %s': 'inserisci nuovo %s',
'Internal State': 'Stato interno',
+'Introduction': 'Introduction',
'Invalid email': 'Email non valida',
+'Invalid login': 'Invalid login',
'Invalid Query': 'Richiesta (query) non valida',
'invalid request': 'richiesta non valida',
+'Is Active': 'Is Active',
'Last name': 'Cognome',
'Layout': 'Layout',
+'Layout Plugins': 'Layout Plugins',
+'Layouts': 'Layouts',
+'Live Chat': 'Live Chat',
+'Logged in': 'Logged in',
'login': 'accesso',
+'Login': 'Login',
'logout': 'uscita',
+'Logout': 'Logout',
+'Lost Password': 'Lost Password',
+'Lost password?': 'Lost password?',
'lost password?': 'dimenticato la password?',
'Main Menu': 'Menu principale',
+'Manage Cache': 'Manage Cache',
'Menu Model': 'Menu Modelli',
+'Modified By': 'Modified By',
+'Modified On': 'Modified On',
+'My Sites': 'My Sites',
'Name': 'Nome',
'New Record': 'Nuovo elemento (record)',
'new record inserted': 'nuovo record inserito',
'next 100 rows': 'prossime 100 righe',
'No databases in this application': 'Nessun database presente in questa applicazione',
'not authorized': 'non autorizzato',
+'Object or table name': 'Object or table name',
'Online examples': 'Vedere gli esempi',
'or import from csv file': 'oppure importa da file CSV',
'Origin': 'Origine',
+'Other Plugins': 'Other Plugins',
+'Other Recipes': 'Other Recipes',
+'Overview': 'Overview',
'Password': 'Password',
+"Password fields don't match": "Password fields don't match",
+'please input your password again': 'please input your password again',
+'Plugins': 'Plugins',
'Powered by': 'Powered by',
+'Preface': 'Preface',
'previous 100 rows': '100 righe precedenti',
+'Profile': 'Profile',
+'Python': 'Python',
'Query:': 'Richiesta (query):',
-'record': 'record',
+'Quick Examples': 'Quick Examples',
+'RAM': 'RAM',
+'Ram Cleared': 'Ram Cleared',
+'Recipes': 'Recipes',
+'Record': 'Record',
'record does not exist': 'il record non esiste',
-'record id': 'record id',
'Record ID': 'Record ID',
+'Record id': 'Record id',
+'Register': 'Register',
'register': 'registrazione',
+'Registration identifier': 'Registration identifier',
'Registration key': 'Chiave di Registazione',
+'Registration successful': 'Registration successful',
+'Remember me (for 30 days)': 'Remember me (for 30 days)',
'Reset Password key': 'Resetta chiave Password ',
'Role': 'Ruolo',
-'Rows in table': 'Righe nella tabella',
+'Rows in Table': 'Righe nella tabella',
'Rows selected': 'Righe selezionate',
+'Semantic': 'Semantic',
+'Services': 'Services',
'state': 'stato',
'Stylesheet': 'Foglio di stile (stylesheet)',
+'submit': 'submit',
+'Submit': 'Submit',
+'Support': 'Support',
'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?',
-'table': 'tabella',
+'Table': 'tabella',
'Table name': 'Nome tabella',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La richiesta (query) è una condizione come ad esempio "db.tabella1.campo1==\'valore\'". Una condizione come "db.tabella1.campo1==db.tabella2.campo2" produce un "JOIN" SQL.',
+'The Core': 'The Core',
'The output of the file is a dictionary that was rendered by the view %s': 'L\'output del file è un "dictionary" che è stato visualizzato dalla vista %s',
+'The Views': 'The Views',
+'This App': 'This App',
'This is a copy of the scaffolding application': "Questa è una copia dell'applicazione di base (scaffold)",
'Timestamp': 'Ora (timestamp)',
+'Twitter': 'Twitter',
'unable to parse csv file': 'non riesco a decodificare questo file CSV',
'Update': 'Update',
'Update:': 'Aggiorna:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).',
+'User %(id)s Logged-in': 'User %(id)s Logged-in',
+'User %(id)s Registered': 'User %(id)s Registered',
'User ID': 'ID Utente',
+'Verify Password': 'Verify Password',
+'Videos': 'Videos',
'View': 'Vista',
+'Welcome': 'Welcome',
'Welcome %s': 'Benvenuto %s',
'Welcome to web2py': 'Benvenuto su web2py',
+'Welcome to web2py!': 'Welcome to web2py!',
'Which called the function %s located in the file %s': 'che ha chiamato la funzione %s presente nel file %s',
'You are successfully running web2py': 'Stai eseguendo web2py con successo',
'You can modify this application and adapt it to your needs': 'Puoi modificare questa applicazione adattandola alle tue necessità',
diff --git a/applications/welcome/languages/nl.py b/applications/welcome/languages/nl.py
index 8efd85e4..33a06002 100644
--- a/applications/welcome/languages/nl.py
+++ b/applications/welcome/languages/nl.py
@@ -50,7 +50,7 @@
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'LET OP: TESTEN IS NIET THREAD SAFE, PROBEER NIET GELIJKTIJDIG MEERDERE TESTS TE DOEN.',
'ATTENTION: you cannot edit the running application!': 'LET OP: je kan de applicatie die nu draait niet editen!',
'Authentication': 'Authenticatie',
-'Available databases and tables': 'Beschikbare databases en tabellen',
+'Available Databases and Tables': 'Beschikbare databases en tabellen',
'Back': 'Terug',
'Buy this book': 'Koop dit boek',
'Cache': 'Cache',
@@ -95,8 +95,8 @@
'customize me!': 'pas me aan!',
'data uploaded': 'data geupload',
'Database': 'Database',
-'database': 'database',
-'database %s select': 'database %s select',
+'Database': 'Database',
+'Database %s select': 'Database %s select',
'database administration': 'database administratie',
'Date and Time': 'Datum en Tijd',
'db': 'db',
@@ -260,9 +260,9 @@
'RAM Cache Keys': 'RAM Cache Keys',
'Ram Cleared': 'Ram Geleegd',
'Recipes': 'Recepten',
-'record': 'record',
+'Record': 'Record',
'record does not exist': 'record bestaat niet',
-'record id': 'record id',
+'Record id': 'Record id',
'Record ID': 'Record ID',
'register': 'registreer',
'Register': 'Registreer',
@@ -277,7 +277,7 @@
'restore': 'herstel',
'revert': 'herstel',
'Role': 'Rol',
-'Rows in table': 'Rijen in tabel',
+'Rows in Table': 'Rijen in tabel',
'Rows selected': 'Rijen geselecteerd',
'save': 'bewaar',
'Save profile': 'Bewaar profiel',
@@ -300,7 +300,7 @@
'submit': 'submit',
'Support': 'Support',
'Sure you want to delete this object?': 'Weet je zeker dat je dit object wilt verwijderen?',
-'table': 'Tabel',
+'Table': 'Tabel',
'Table name': 'Tabelnaam',
'test': 'test',
'Testing application': 'Applicatie testen',
diff --git a/applications/welcome/languages/pl.py b/applications/welcome/languages/pl.py
index 366987c5..a94ec312 100644
--- a/applications/welcome/languages/pl.py
+++ b/applications/welcome/languages/pl.py
@@ -10,7 +10,7 @@
'%s selected': '%s wybranych',
'Administrative interface': 'Kliknij aby przejść do panelu administracyjnego',
'Authentication': 'Uwierzytelnienie',
-'Available databases and tables': 'Dostępne bazy danych i tabele',
+'Available Databases and Tables': 'Dostępne bazy danych i tabele',
'Cannot be empty': 'Nie może być puste',
'Change Password': 'Zmień hasło',
'Check to delete': 'Zaznacz aby usunąć',
@@ -59,7 +59,7 @@
'Register': 'Zarejestruj',
'Registration key': 'Klucz rejestracji',
'Role': 'Rola',
-'Rows in table': 'Wiersze w tabeli',
+'Rows in Table': 'Wiersze w tabeli',
'Rows selected': 'Wybrane wiersze',
'Stylesheet': 'Arkusz stylów',
'Submit': 'Wyślij',
@@ -83,8 +83,8 @@
'change password': 'change password',
'customize me!': 'dostosuj mnie!',
'data uploaded': 'dane wysłane',
-'database': 'baza danych',
-'database %s select': 'wybór z bazy danych %s',
+'Database': 'baza danych',
+'Database %s select': 'wybór z bazy danych %s',
'db': 'baza danych',
'design': 'projektuj',
'done!': 'zrobione!',
@@ -99,11 +99,11 @@
'next 100 rows': 'następne 100 wierszy',
'or import from csv file': 'lub zaimportuj z pliku csv',
'previous 100 rows': 'poprzednie 100 wierszy',
-'record': 'rekord',
+'Record': 'rekord',
'record does not exist': 'rekord nie istnieje',
-'record id': 'id rekordu',
+'Record id': 'id rekordu',
'register': 'register',
'state': 'stan',
-'table': 'tabela',
+'Table': 'tabela',
'unable to parse csv file': 'nie można sparsować pliku csv',
}
diff --git a/applications/welcome/languages/pt-br.py b/applications/welcome/languages/pt-br.py
index b9b8a8bf..ecdfba3f 100644
--- a/applications/welcome/languages/pt-br.py
+++ b/applications/welcome/languages/pt-br.py
@@ -12,7 +12,7 @@
'Access Control': 'Access Control',
'Administrative interface': 'Interface administrativa',
'Ajax Recipes': 'Ajax Recipes',
-'Available databases and tables': 'Bancos de dados e tabelas disponíveis',
+'Available Databases and Tables': 'Bancos de dados e tabelas disponíveis',
'Buy this book': 'Buy this book',
'Cannot be empty': 'Não pode ser vazio',
'Check to delete': 'Marque para apagar',
@@ -79,7 +79,7 @@
'Reset Password key': 'Reset Password key',
'Resources': 'Resources',
'Role': 'Role',
-'Rows in table': 'Linhas na tabela',
+'Rows in Table': 'Linhas na tabela',
'Rows selected': 'Linhas selecionadas',
'Semantic': 'Semantic',
'Services': 'Services',
@@ -115,8 +115,8 @@
'change password': 'modificar senha',
'customize me!': 'Personalize-me!',
'data uploaded': 'dados enviados',
-'database': 'banco de dados',
-'database %s select': 'Selecionar banco de dados %s',
+'Database': 'banco de dados',
+'Database %s select': 'Selecionar banco de dados %s',
'db': 'bd',
'design': 'design',
'done!': 'concluído!',
@@ -132,11 +132,11 @@
'next 100 rows': 'próximas 100 linhas',
'or import from csv file': 'ou importar de um arquivo csv',
'previous 100 rows': '100 linhas anteriores',
-'record': 'registro',
+'Record': 'registro',
'record does not exist': 'registro não existe',
-'record id': 'id do registro',
+'Record id': 'id do registro',
'register': 'Registre-se',
'state': 'estado',
-'table': 'tabela',
+'Table': 'tabela',
'unable to parse csv file': 'não foi possível analisar arquivo csv',
}
diff --git a/applications/welcome/languages/pt.py b/applications/welcome/languages/pt.py
index f8e01ab1..8b0c633f 100644
--- a/applications/welcome/languages/pt.py
+++ b/applications/welcome/languages/pt.py
@@ -12,7 +12,7 @@
'Administrative interface': 'Painel administrativo',
'Author Reference Auth User': 'Author Reference Auth User',
'Author Reference Auth User.username': 'Author Reference Auth User.username',
-'Available databases and tables': 'bases de dados e tabelas disponíveis',
+'Available Databases and Tables': 'bases de dados e tabelas disponíveis',
'Cannot be empty': 'não pode ser vazio',
'Category Create': 'Category Create',
'Category Select': 'Category Select',
@@ -58,7 +58,7 @@
'Powered by': 'Suportado por',
'Query:': 'Interrogação:',
'Replyto Reference Post': 'Replyto Reference Post',
-'Rows in table': 'Linhas numa tabela',
+'Rows in Table': 'Linhas numa tabela',
'Rows selected': 'Linhas seleccionadas',
'Stylesheet': 'Folha de estilo',
'Sure you want to delete this object?': 'Tem a certeza que deseja eliminar este objecto?',
@@ -83,8 +83,8 @@
'create new post': 'create new post',
'customize me!': 'Personaliza-me!',
'data uploaded': 'informação enviada',
-'database': 'base de dados',
-'database %s select': 'selecção de base de dados %s',
+'Database': 'base de dados',
+'Database %s select': 'selecção de base de dados %s',
'db': 'bd',
'design': 'design',
'done!': 'concluído!',
@@ -102,9 +102,9 @@
'next 100 rows': 'próximas 100 linhas',
'or import from csv file': 'ou importe a partir de ficheiro csv',
'previous 100 rows': '100 linhas anteriores',
-'record': 'registo',
+'Record': 'registo',
'record does not exist': 'registo inexistente',
-'record id': 'id de registo',
+'Record id': 'id de registo',
'register': 'register',
'search category': 'search category',
'search comment': 'search comment',
@@ -116,6 +116,6 @@
'show comment': 'show comment',
'show post': 'show post',
'state': 'estado',
-'table': 'tabela',
+'Table': 'tabela',
'unable to parse csv file': 'não foi possível carregar ficheiro csv',
}
diff --git a/applications/welcome/languages/ro.py b/applications/welcome/languages/ro.py
index e2f0a31a..1352c26e 100644
--- a/applications/welcome/languages/ro.py
+++ b/applications/welcome/languages/ro.py
@@ -53,7 +53,7 @@
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENȚIE: Nu puteți efectua mai multe teste o dată deoarece lansarea în execuție a mai multor subpocese nu este sigură.',
'ATTENTION: you cannot edit the running application!': 'ATENȚIE: nu puteți edita o aplicație în curs de execuție!',
'Authentication': 'Autentificare',
-'Available databases and tables': 'Baze de date și tabele disponibile',
+'Available Databases and Tables': 'Baze de date și tabele disponibile',
'Back': 'Înapoi',
'Buy this book': 'Cumpără această carte',
'cache': 'cache',
@@ -92,8 +92,8 @@
'customize me!': 'Personalizează-mă!',
'data uploaded': 'date încărcate',
'Database': 'Baza de date',
-'database': 'bază de date',
-'database %s select': 'selectare bază de date %s',
+'Database': 'bază de date',
+'Database %s select': 'selectare bază de date %s',
'database administration': 'administrare bază de date',
'Date and Time': 'Data și ora',
'db': 'db',
@@ -247,9 +247,9 @@
'Quick Examples': 'Exemple rapide',
'RAM Cache Keys': 'Chei cache RAM',
'Recipes': 'Rețete',
-'record': 'înregistrare',
+'Record': 'înregistrare',
'record does not exist': 'înregistrare inexistentă',
-'record id': 'id înregistrare',
+'Record id': 'id înregistrare',
'Record ID': 'ID înregistrare',
'register': 'înregistrare',
'Register': 'Înregistrare',
@@ -264,7 +264,7 @@
'restore': 'restaurare',
'revert': 'revenire',
'Role': 'Rol',
-'Rows in table': 'Linii în tabel',
+'Rows in Table': 'Linii în tabel',
'Rows selected': 'Linii selectate',
'save': 'salvare',
'Save profile': 'Salvează profil',
@@ -284,7 +284,7 @@
'Submit': 'Înregistrează',
'Support': 'Suport',
'Sure you want to delete this object?': 'Sigur ștergeți acest obiect?',
-'table': 'tabel',
+'Table': 'tabel',
'Table name': 'Nume tabel',
'test': 'test',
'Testing application': 'Testare aplicație',
diff --git a/applications/welcome/languages/ru.py b/applications/welcome/languages/ru.py
index 8782bb9a..ce632207 100644
--- a/applications/welcome/languages/ru.py
+++ b/applications/welcome/languages/ru.py
@@ -28,7 +28,7 @@
'Administrative interface': 'административный интерфейс',
'Ajax Recipes': 'Ajax Recipes',
'Are you sure you want to delete this object?': 'Вы уверены, что хотите удалить этот объект?',
-'Available databases and tables': 'Базы данных и таблицы',
+'Available Databases and Tables': 'Базы данных и таблицы',
'Buy this book': 'Buy this book',
'cache': 'cache',
'Cannot be empty': 'Пустое значение недопустимо',
@@ -45,9 +45,9 @@
'Current session': 'Текущая сессия',
'customize me!': 'настройте внешний вид!',
'data uploaded': 'данные загружены',
-'database': 'база данных',
+'Database': 'база данных',
'Database': 'Database',
-'database %s select': 'выбор базы данных %s',
+'Database %s select': 'выбор базы данных %s',
'db': 'БД',
'DB Model': 'DB Model',
'Delete:': 'Удалить:',
@@ -129,7 +129,7 @@
'Quick Examples': 'Quick Examples',
'Recipes': 'Recipes',
'record does not exist': 'запись не найдена',
-'record id': 'id записи',
+'Record id': 'id записи',
'Record ID': 'ID записи',
'Register': 'Зарегистрироваться',
'Registration identifier': 'Registration identifier',
@@ -137,7 +137,7 @@
'Remember me (for 30 days)': 'Запомнить меня (на 30 дней)',
'Reset Password key': 'Сбросить ключ пароля',
'Role': 'Роль',
-'Rows in table': 'Строк в таблице',
+'Rows in Table': 'Строк в таблице',
'Rows selected': 'Выделено строк',
'Semantic': 'Semantic',
'Services': 'Services',
@@ -146,7 +146,7 @@
'Submit': 'Отправить',
'Support': 'Support',
'Sure you want to delete this object?': 'Подтвердите удаление объекта',
-'table': 'таблица',
+'Table': 'таблица',
'Table name': 'Имя таблицы',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Запрос" - это условие вида "db.table1.field1==\'значение\'". Выражение вида "db.table1.field1==db.table2.field2" формирует SQL JOIN.',
'The Core': 'The Core',
diff --git a/applications/welcome/languages/sk.py b/applications/welcome/languages/sk.py
index fd28f3ff..34d34e63 100644
--- a/applications/welcome/languages/sk.py
+++ b/applications/welcome/languages/sk.py
@@ -9,7 +9,7 @@
'%s %%{row} updated': '%s upravených záznamov',
'%s selected': '%s označených',
'Administrative interface': 'pre administrátorské rozhranie kliknite sem',
-'Available databases and tables': 'Dostupné databázy a tabuľky',
+'Available Databases and Tables': 'Dostupné databázy a tabuľky',
'Cannot be empty': 'Nemôže byť prázdne',
'Check to delete': 'Označiť na zmazanie',
'Controller': 'Controller',
@@ -56,7 +56,7 @@
'Remember me (for 30 days)': 'Zapamätaj si ma (na 30 dní)',
'Reset Password key': 'Nastaviť registračný kľúč',
'Role': 'Rola',
-'Rows in table': 'riadkov v tabuľke',
+'Rows in Table': 'riadkov v tabuľke',
'Rows selected': 'označených riadkov',
'Stylesheet': 'Stylesheet',
'Submit': 'Odoslať',
@@ -85,8 +85,8 @@
'cache': 'cache',
'customize me!': 'prispôsob ma!',
'data uploaded': 'údaje naplnené',
-'database': 'databáza',
-'database %s select': 'databáza %s výber',
+'Database': 'databáza',
+'Database %s select': 'databáza %s výber',
'db': 'db',
'design': 'návrh',
'done!': 'hotovo!',
@@ -102,11 +102,11 @@
'or import from csv file': 'alebo naimportovať z csv súboru',
'password': 'heslo',
'previous 100 rows': 'predchádzajúcich 100 riadkov',
-'record': 'záznam',
+'Record': 'záznam',
'record does not exist': 'záznam neexistuje',
-'record id': 'id záznamu',
+'Record id': 'id záznamu',
'register': 'registrovať',
'state': 'stav',
-'table': 'tabuľka',
+'Table': 'tabuľka',
'unable to parse csv file': 'nedá sa načítať csv súbor',
}
diff --git a/applications/welcome/languages/uk.py b/applications/welcome/languages/uk.py
index 1bf341e2..4561f5cd 100644
--- a/applications/welcome/languages/uk.py
+++ b/applications/welcome/languages/uk.py
@@ -36,7 +36,7 @@
'Ajax Recipes': 'Рецепти для Ajax',
'appadmin is disabled because insecure channel': 'використовується незахищенний канал (HTTP). Appadmin вимкнено',
'Are you sure you want to delete this object?': "Ви впевнені, що хочете вилучити цей об'єкт?",
-'Available databases and tables': 'Доступні бази даних та таблиці',
+'Available Databases and Tables': 'Доступні бази даних та таблиці',
'Buy this book': 'Купити книжку',
'cache': 'кеш',
'Cache': 'Кеш',
@@ -58,9 +58,9 @@
'Current session': 'Поточна сесія (current session)',
'customize me!': 'причепуріть мене!',
'data uploaded': 'дані завантажено',
-'database': 'база даних',
+'Database': 'база даних',
'Database': 'База даних',
-'database %s select': 'Вибірка з бази даних %s',
+'Database %s select': 'Вибірка з бази даних %s',
'db': 'база даних',
'DB Model': 'Модель БД',
'Delete:': 'Вилучити:',
@@ -157,10 +157,10 @@
'RAM Cache Keys': 'Ключі ОЗП-кешу',
'Ram Cleared': 'ОЗП-кеш очищено',
'Recipes': 'Рецепти',
-'record': 'запис',
+'Record': 'запис',
'Record %(id)s updated': 'Запис %(id)s змінено',
'record does not exist': 'запису не існує',
-'record id': 'ід. запису',
+'Record id': 'ід. запису',
'Record ID': 'Ід.запису',
'Record Updated': 'Запис змінено',
'Register': 'Реєстрація',
@@ -171,7 +171,7 @@
'Request reset password': 'Запит на зміну пароля',
'Reset Password key': 'Ключ скидання пароля',
'Role': 'Роль',
-'Rows in table': 'Рядки в таблиці',
+'Rows in Table': 'Рядки в таблиці',
'Rows selected': 'Відмічено рядків',
'Save profile': 'Зберегти параметри',
'Semantic': 'Семантика',
@@ -183,7 +183,7 @@
'submit': 'submit',
'Submit': 'Застосувати',
'Support': 'Підтримка',
-'table': 'Таблиця',
+'Table': 'Таблиця',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Запит" це умова, на зразок "db.table1.field1==\'значення\'". Вираз "db.table1.field1==db.table2.field2" повертає результат об\'єднання (SQL JOIN) таблиць.',
'The Core': 'Ядро',
'The output of the file is a dictionary that was rendered by the view %s': 'Результат функції - словник пар (назва=значення) було відображено з допомогою відображення (view) %s',
diff --git a/applications/welcome/languages/zh.py b/applications/welcome/languages/zh.py
index 0567e55e..30c8a575 100644
--- a/applications/welcome/languages/zh.py
+++ b/applications/welcome/languages/zh.py
@@ -24,7 +24,7 @@
'Are you sure you want to uninstall application "%s"': '確定要移除應用程式 "%s"',
'Are you sure you want to uninstall application "%s"?': '確定要移除應用程式 "%s"',
'Authentication': '驗證',
-'Available databases and tables': '可提供的資料庫和資料表',
+'Available Databases and Tables': '可提供的資料庫和資料表',
'Cannot be empty': '不可空白',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': '無法編譯:應用程式中含有錯誤,請除錯後再試一次.',
'Change Password': '變更密碼',
@@ -101,7 +101,7 @@
'Reset Password key': '重設密碼',
'Resolve Conflict file': '解決衝突檔案',
'Role': '角色',
-'Rows in table': '在資料表裏的資料',
+'Rows in Table': '在資料表裏的資料',
'Rows selected': '筆資料被選擇',
'Saved file hash:': '檔案雜湊值已紀錄:',
'Static files': '靜態檔案',
@@ -144,8 +144,8 @@
'change password': '變更密碼',
'customize me!': '請調整我!',
'data uploaded': '資料已上傳',
-'database': '資料庫',
-'database %s select': '已選擇 %s 資料庫',
+'Database': '資料庫',
+'Database %s select': '已選擇 %s 資料庫',
'db': 'db',
'design': '設計',
'done!': '完成!',
@@ -160,11 +160,11 @@
'next 100 rows': '往後 100 筆',
'or import from csv file': '或是從逗號分隔檔(CSV)匯入',
'previous 100 rows': '往前 100 筆',
-'record': '紀錄',
+'Record': '紀錄',
'record does not exist': '紀錄不存在',
-'record id': '紀錄編號',
+'Record id': '紀錄編號',
'register': '註冊',
'state': '狀態',
-'table': '資料表',
+'Table': '資料表',
'unable to parse csv file': '無法解析逗號分隔檔(csv)',
}
diff --git a/applications/welcome/static/images/arrows-ffffff.png b/applications/welcome/static/images/arrows-ffffff.png
deleted file mode 100644
index 995df520..00000000
Binary files a/applications/welcome/static/images/arrows-ffffff.png and /dev/null differ
diff --git a/applications/welcome/static/images/css3buttons_backgrounds.png b/applications/welcome/static/images/css3buttons_backgrounds.png
deleted file mode 100644
index 7e440cd7..00000000
Binary files a/applications/welcome/static/images/css3buttons_backgrounds.png and /dev/null differ
diff --git a/applications/welcome/static/images/css3buttons_icons.png b/applications/welcome/static/images/css3buttons_icons.png
deleted file mode 100644
index d858c9a5..00000000
Binary files a/applications/welcome/static/images/css3buttons_icons.png and /dev/null differ
diff --git a/applications/welcome/static/images/poweredby.png b/applications/welcome/static/images/poweredby.png
deleted file mode 100644
index a7672437..00000000
Binary files a/applications/welcome/static/images/poweredby.png and /dev/null differ
diff --git a/applications/welcome/static/images/shadow.png b/applications/welcome/static/images/shadow.png
deleted file mode 100644
index c04d21b7..00000000
Binary files a/applications/welcome/static/images/shadow.png and /dev/null differ
diff --git a/applications/welcome/static/images/ui-icons_222222_256x240.png b/applications/welcome/static/images/ui-icons_222222_256x240.png
deleted file mode 100644
index b273ff11..00000000
Binary files a/applications/welcome/static/images/ui-icons_222222_256x240.png and /dev/null differ
diff --git a/applications/welcome/views/appadmin.html b/applications/welcome/views/appadmin.html
index 1d967c2f..541a18bb 100644
--- a/applications/welcome/views/appadmin.html
+++ b/applications/welcome/views/appadmin.html
@@ -10,8 +10,9 @@
//-->
{{if request.function=='index':}}
-{{=T("Available databases and tables")}}
+{{=T("Available Databases and Tables")}}
{{if not databases:}}{{=T("No databases in this application")}}{{pass}}
+
{{for db in sorted(databases):}}
{{for table in databases[db].tables:}}
{{qry='%s.%s.id>0'%(db,table)}}
@@ -28,20 +29,24 @@
{{qry=''}}
{{pass}}
{{pass}}
-
- {{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
-
- [ {{=A(str(T('insert new'))+' '+table,_href=URL('insert',args=[db,table]))}} ]
-
+
+ |
+ {{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
+ |
+
+ {{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
+ |
+
{{pass}}
+
{{pass}}
{{elif request.function=='select':}}
- {{=XML(str(T("database %s select"))%A(request.args[0],_href=URL('index'))) }}
+ {{=XML(str(T("Database %s select"))%A(request.args[0],_href=URL('index'))) }}
- {{if table:}}
- [ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]
- {{=T("Rows in table")}}
+ {{if table:}}
+ {{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}
+ {{=T("Rows in Table")}}
{{else:}}
{{=T("Rows selected")}}
{{pass}}
@@ -51,8 +56,8 @@
{{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}
{{=T("%s selected", nrows)}}
- {{if start>0:}}[ {{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)))}} ]{{pass}}
- {{if stop0:}}{{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)),_class="btn")}}{{pass}}
+ {{if stop
{{linkto=URL('update',args=request.args[0])}}
@@ -61,35 +66,35 @@
{{pass}}
{{=T("Import/Export")}}
- [ {{=T("export as csv file")}} ]
+ {{=T("export as csv file")}}
{{=formcsv or ''}}
{{elif request.function=='insert':}}
- {{=T("database")}} {{=A(request.args[0],_href=URL('index'))}}
+ {{=T("Database")}} {{=A(request.args[0],_href=URL('index'))}}
{{if hasattr(table,'_primarykey'):}}
{{fieldname=table._primarykey[0]}}
{{dbname=request.args[0]}}
{{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}}
- {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
+ {{=T("Table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{else:}}
- {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
+ {{=T("Table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{pass}}
{{=T("New Record")}}
{{=form}}
{{elif request.function=='update':}}
- {{=T("database")}} {{=A(request.args[0],_href=URL('index'))}}
+ {{=T("Database")}} {{=A(request.args[0],_href=URL('index'))}}
{{if hasattr(table,'_primarykey'):}}
{{fieldname=request.vars.keys()[0]}}
{{dbname=request.args[0]}}
{{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}}
- {{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
- {{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
+ {{=T("Table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
+ {{=T("Record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
{{else:}}
- {{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
- {{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
+ {{=T("Table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
+ {{=T("Record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
{{pass}}
{{=T("Edit current record")}}
{{=form}}
diff --git a/gluon/cache.py b/gluon/cache.py
index bc9cc0f9..3499ac14 100644
--- a/gluon/cache.py
+++ b/gluon/cache.py
@@ -298,7 +298,7 @@ class CacheOnDisk(CacheAbstract):
try:
storage = self._open_shelf_with_lock()
try:
- if not storage.has_key(CacheAbstract.cache_stats_name):
+ if not CacheAbstract.cache_stats_name in storage:
storage[CacheAbstract.cache_stats_name] = {
'hit_total': 0,
'misses': 0,
diff --git a/gluon/compileapp.py b/gluon/compileapp.py
index e00e6910..5a186c08 100644
--- a/gluon/compileapp.py
+++ b/gluon/compileapp.py
@@ -50,6 +50,8 @@ is_pypy = settings.global_settings.is_pypy
is_gae = settings.global_settings.web2py_runtime_gae
is_jython = settings.global_settings.is_jython
+pjoin = os.path.join
+
TEST_CODE = \
r"""
def _TEST():
@@ -319,7 +321,7 @@ def local_import_aux(name, reload_force=False, app='welcome'):
"""
OLD IMPLEMENTATION:
items = name.replace('/','.').split('.')
- filename, modulepath = items[-1], os.path.join(apath,'modules',*items[:-1])
+ filename, modulepath = items[-1], pjoin(apath,'modules',*items[:-1])
imp.acquire_lock()
try:
file=None
@@ -348,11 +350,9 @@ def build_environment(request, response, session, store_current=True):
"""
Build the environment dictionary into which web2py files are executed.
"""
-
- _validators = validators
- _html = html
- environment = dict(map(lambda key: (key, getattr(_html, key)), _html.__all__))
- environment.update(map(lambda key: (key, getattr(_validators, key)), _validators.__all__))
+ h,v = html,validators
+ environment = dict((k,getattr(h,k)) for k in h.__all__)
+ environment.update((k,getattr(v, k)) for k in v.__all__)
if not request.env:
request.env = Storage()
@@ -389,7 +389,7 @@ def build_environment(request, response, session, store_current=True):
environment['local_import'] = \
lambda name, reload=False, app=request.application:\
local_import_aux(name,reload,app)
- BaseAdapter.set_folder(os.path.join(request.folder, 'databases'))
+ BaseAdapter.set_folder(pjoin(request.folder, 'databases'))
response._view_environment = copy.copy(environment)
return environment
@@ -419,11 +419,11 @@ def compile_views(folder):
Compiles all the views in the application specified by `folder`
"""
- path = os.path.join(folder, 'views')
+ path = pjoin(folder, 'views')
for file in listdir(path, '^[\w/\-]+(\.\w+)+$'):
data = parse_template(file, path)
filename = ('views/%s.py' % file).replace('/', '_').replace('\\', '_')
- filename = os.path.join(folder, 'compiled', filename)
+ filename = pjoin(folder, 'compiled', filename)
write_file(filename, data)
save_pyc(filename)
os.unlink(filename)
@@ -434,10 +434,10 @@ def compile_models(folder):
Compiles all the models in the application specified by `folder`
"""
- path = os.path.join(folder, 'models')
+ path = pjoin(folder, 'models')
for file in listdir(path, '.+\.py$'):
- data = read_file(os.path.join(path, file))
- filename = os.path.join(folder, 'compiled','models',file)
+ data = read_file(pjoin(path, file))
+ filename = pjoin(folder, 'compiled','models',file)
mktree(filename)
write_file(filename, data)
save_pyc(filename)
@@ -449,15 +449,15 @@ def compile_controllers(folder):
Compiles all the controllers in the application specified by `folder`
"""
- path = os.path.join(folder, 'controllers')
+ path = pjoin(folder, 'controllers')
for file in listdir(path, '.+\.py$'):
- ### why is this here? save_pyc(os.path.join(path, file))
- data = read_file(os.path.join(path,file))
+ ### why is this here? save_pyc(pjoin(path, file))
+ data = read_file(pjoin(path,file))
exposed = regex_expose.findall(data)
for function in exposed:
command = data + "\nresponse._vars=response._caller(%s)\n" % \
function
- filename = os.path.join(folder, 'compiled', ('controllers/'
+ filename = pjoin(folder, 'compiled', ('controllers/'
+ file[:-3]).replace('/', '_')
+ '_' + function + '.py')
write_file(filename, command)
@@ -474,18 +474,18 @@ def run_models_in(environment):
folder = environment['request'].folder
c = environment['request'].controller
f = environment['request'].function
- cpath = os.path.join(folder, 'compiled')
+ cpath = pjoin(folder, 'compiled')
if os.path.exists(cpath):
for model in listdir(cpath, '^models_\w+\.pyc$', 0):
restricted(read_pyc(model), environment, layer=model)
- path = os.path.join(cpath, 'models')
+ path = pjoin(cpath, 'models')
models = listdir(path, '^\w+\.pyc$',0,sort=False)
compiled=True
else:
- path = os.path.join(folder, 'models')
+ path = pjoin(folder, 'models')
models = listdir(path, '^\w+\.py$',0,sort=False)
compiled=False
- paths = (path, os.path.join(path,c), os.path.join(path,c,f))
+ paths = (path, pjoin(path,c), pjoin(path,c,f))
for model in models:
if not os.path.split(model)[0] in paths and c!='appadmin':
continue
@@ -509,11 +509,11 @@ def run_controller_in(controller, function, environment):
# if compiled should run compiled!
folder = environment['request'].folder
- path = os.path.join(folder, 'compiled')
+ path = pjoin(folder, 'compiled')
badc = 'invalid controller (%s/%s)' % (controller, function)
badf = 'invalid function (%s/%s)' % (controller, function)
if os.path.exists(path):
- filename = os.path.join(path, 'controllers_%s_%s.pyc'
+ filename = pjoin(path, 'controllers_%s_%s.pyc'
% (controller, function))
if not os.path.exists(filename):
raise HTTP(404,
@@ -528,7 +528,7 @@ def run_controller_in(controller, function, environment):
[add_path_first(path) for path in paths]
# TESTING END
- filename = os.path.join(folder, 'controllers/%s.py'
+ filename = pjoin(folder, 'controllers/%s.py'
% controller)
if not os.path.exists(filename):
raise HTTP(404,
@@ -539,7 +539,7 @@ def run_controller_in(controller, function, environment):
code += TEST_CODE
restricted(code, environment, layer=filename)
else:
- filename = os.path.join(folder, 'controllers/%s.py'
+ filename = pjoin(folder, 'controllers/%s.py'
% controller)
if not os.path.exists(filename):
raise HTTP(404,
@@ -576,19 +576,20 @@ def run_view_in(environment):
request = environment['request']
response = environment['response']
+ view = response.view
folder = request.folder
- path = os.path.join(folder, 'compiled')
- badv = 'invalid view (%s)' % response.view
+ path = pjoin(folder, 'compiled')
+ badv = 'invalid view (%s)' % view
patterns = response.generic_patterns or []
regex = re.compile('|'.join(map(fnmatch.translate, patterns)))
short_action = '%(controller)s/%(function)s.%(extension)s' % request
allow_generic = patterns and regex.search(short_action)
- if not isinstance(response.view, str):
- ccode = parse_template(response.view, os.path.join(folder, 'views'),
+ if not isinstance(view, str):
+ ccode = parse_template(view, pjoin(folder, 'views'),
context=environment)
restricted(ccode, environment, 'file stream')
elif os.path.exists(path):
- x = response.view.replace('/', '_')
+ x = view.replace('/', '_')
files = ['views_%s.pyc' % x]
if allow_generic:
files.append('views_generic.%s.pyc' % request.extension)
@@ -599,7 +600,7 @@ def run_view_in(environment):
files.append('views_generic.pyc')
# end backward compatibility code
for f in files:
- filename = os.path.join(path,f)
+ filename = pjoin(path,f)
if os.path.exists(filename):
code = read_pyc(filename)
restricted(code, environment, layer=filename)
@@ -608,10 +609,10 @@ def run_view_in(environment):
rewrite.thread.routes.error_message % badv,
web2py_error=badv)
else:
- filename = os.path.join(folder, 'views', response.view)
+ filename = pjoin(folder, 'views', view)
if not os.path.exists(filename) and allow_generic:
- response.view = 'generic.' + request.extension
- filename = os.path.join(folder, 'views', response.view)
+ view = 'generic.' + request.extension
+ filename = pjoin(folder, 'views', view)
if not os.path.exists(filename):
raise HTTP(404,
rewrite.thread.routes.error_message % badv,
@@ -619,12 +620,12 @@ def run_view_in(environment):
layer = filename
if is_gae:
ccode = getcfs(layer, filename,
- lambda: compile2(parse_template(response.view,
- os.path.join(folder, 'views'),
+ lambda: compile2(parse_template(view,
+ pjoin(folder, 'views'),
context=environment),layer))
else:
- ccode = parse_template(response.view,
- os.path.join(folder, 'views'),
+ ccode = parse_template(view,
+ pjoin(folder, 'views'),
context=environment)
restricted(ccode, environment, layer)
@@ -633,8 +634,8 @@ def remove_compiled_application(folder):
Deletes the folder `compiled` containing the compiled application.
"""
try:
- shutil.rmtree(os.path.join(folder, 'compiled'))
- path = os.path.join(folder, 'controllers')
+ shutil.rmtree(pjoin(folder, 'compiled'))
+ path = pjoin(folder, 'controllers')
for file in listdir(path,'.*\.pyc$',drop=False):
os.unlink(file)
except OSError:
@@ -646,7 +647,7 @@ def compile_application(folder):
Compiles all models, views, controller for the application in `folder`.
"""
remove_compiled_application(folder)
- os.mkdir(os.path.join(folder, 'compiled'))
+ os.mkdir(pjoin(folder, 'compiled'))
compile_models(folder)
compile_controllers(folder)
compile_views(folder)
diff --git a/gluon/globals.py b/gluon/globals.py
index 7bf41549..5e94b726 100644
--- a/gluon/globals.py
+++ b/gluon/globals.py
@@ -117,7 +117,8 @@ class Request(Storage):
user_agent_parser.detect(self.env.http_user_agent)
user_agent = Storage(user_agent)
for key,value in user_agent.items():
- if isinstance(value,dict): user_agent[key] = Storage(value)
+ if isinstance(value,dict):
+ user_agent[key] = Storage(value)
return user_agent
def requires_https(self):
@@ -222,9 +223,9 @@ class Response(Storage):
return page
def include_meta(self):
- s = '\n'
- for key,value in (self.meta or {}).items():
- s += '\n' % (key,xmlescape(value))
+ s = '\n'.join(
+ '\n' % (k,xmlescape(v))
+ for k,v in (self.meta or {}).iteritems())
self.write(s,escape=False)
def include_files(self):
@@ -297,14 +298,15 @@ class Response(Storage):
default to the last request argument otherwise)
"""
+ headers = self.headers
# for attachment settings and backward compatibility
- keys = [item.lower() for item in self.headers]
+ keys = [item.lower() for item in headers]
if attachment:
if filename is None:
attname = ""
else:
attname = filename
- self.headers["Content-Disposition"] = \
+ headers["Content-Disposition"] = \
"attachment;filename=%s" % attname
if not request:
@@ -313,30 +315,31 @@ class Response(Storage):
stream_file_or_304_or_206(stream,
chunk_size=chunk_size,
request=request,
- headers=self.headers)
+ headers=headers)
# ## the following is for backward compatibility
if hasattr(stream, 'name'):
filename = stream.name
if filename and not 'content-type' in keys:
- self.headers['Content-Type'] = contenttype(filename)
+ headers['Content-Type'] = contenttype(filename)
if filename and not 'content-length' in keys:
try:
- self.headers['Content-Length'] = \
+ headers['Content-Length'] = \
os.path.getsize(filename)
except OSError:
pass
-
+
+ env = request.env
# Internet Explorer < 9.0 will not allow downloads over SSL unless caching is enabled
- if request.is_https and isinstance(request.env.http_user_agent,str) and \
- not re.search(r'Opera', request.env.http_user_agent) and \
- re.search(r'MSIE [5-8][^0-9]', request.env.http_user_agent):
- self.headers['Pragma'] = 'cache'
- self.headers['Cache-Control'] = 'private'
+ if request.is_https and isinstance(env.http_user_agent,str) and \
+ not re.search(r'Opera', env.http_user_agent) and \
+ re.search(r'MSIE [5-8][^0-9]', env.http_user_agent):
+ headers['Pragma'] = 'cache'
+ headers['Cache-Control'] = 'private'
- if request and request.env.web2py_use_wsgi_file_wrapper:
- wrapped = request.env.wsgi_file_wrapper(stream, chunk_size)
+ if request and env.web2py_use_wsgi_file_wrapper:
+ wrapped = env.wsgi_file_wrapper(stream, chunk_size)
else:
wrapped = streamer(stream, chunk_size=chunk_size)
return wrapped
@@ -364,11 +367,13 @@ class Response(Storage):
(filename, stream) = field.retrieve(name)
except IOError:
raise HTTP(404)
- self.headers['Content-Type'] = contenttype(name)
+ headers = self.headers
+ headers['Content-Type'] = contenttype(name)
if attachment:
- self.headers['Content-Disposition'] = \
- "attachment; filename=%s" % filename
- return self.stream(stream, chunk_size = chunk_size, request=request)
+ headers['Content-Disposition'] = \
+ 'attachment; filename=%s' % filename
+ return self.stream(stream, chunk_size=chunk_size, request=request)
+
def json(self, data, default=None):
return json(data, default = default or custom_json)
@@ -405,8 +410,15 @@ class Response(Storage):
dbstats = [TABLE(*[TR(PRE(row[0]),'%.2fms' % (row[1]*1000)) \
for row in i.db._timings]) \
for i in thread.instances]
+ dbtables = dict([(i.uri, {'defined': sorted(list(set(i.db.tables) -
+ set(i.db._LAZY_TABLES.keys()))) or
+ '[no defined tables]',
+ 'lazy': sorted(i.db._LAZY_TABLES.keys()) or
+ '[no lazy tables]'})
+ for i in thread.instances])
else:
dbstats = [] # if no db or on GAE
+ dbtables = {}
u = web2py_uuid()
return DIV(
BUTTON('design',_onclick="document.location='%s'" % admin),
@@ -416,6 +428,8 @@ class Response(Storage):
DIV(BEAUTIFY(current.session),_class="hidden",_id="session-%s"%u),
BUTTON('response',_onclick="jQuery('#response-%s').slideToggle()"%u),
DIV(BEAUTIFY(current.response),_class="hidden",_id="response-%s"%u),
+ BUTTON('db tables',_onclick="jQuery('#db-tables-%s').slideToggle()"%u),
+ DIV(BEAUTIFY(dbtables),_class="hidden",_id="db-tables-%s"%u),
BUTTON('db stats',_onclick="jQuery('#db-stats-%s').slideToggle()"%u),
DIV(BEAUTIFY(dbstats),_class="hidden",_id="db-stats-%s"%u),
SCRIPT("jQuery('.hidden').hide()")
@@ -452,14 +466,15 @@ class Session(Storage):
response.session_id_name = 'session_id_%s' % masterapp.lower()
# Load session data from cookie
-
+ cookies = request.cookies
+
if cookie_key:
response.session_cookie_key = cookie_key
response.session_cookie_key2 = hashlib.md5(cookie_key).digest()
- cookie_name = request.application.lower()+'_session_data'
+ cookie_name = masterapp.lower()+'_session_data'
response.session_cookie_name = cookie_name
- if cookie_data in request.cookies:
- cookie_value = request.cookies[cookie_name].value
+ if cookie_name in cookies:
+ cookie_value = cookies[cookie_name].value
cookie_parts = cookie_value.split(":")
enc = cookie_parts[2]
cipher = AES.new(cookie_key)
@@ -477,9 +492,9 @@ class Session(Storage):
return
response.session_new = False
client = request.client and request.client.replace(':', '.')
- if response.session_id_name in request.cookies:
+ if response.session_id_name in cookies:
response.session_id = \
- request.cookies[response.session_id_name].value
+ cookies[response.session_id_name].value
if regex_session_id.match(response.session_id):
response.session_filename = \
os.path.join(up(request.folder), masterapp,
@@ -532,38 +547,35 @@ class Session(Storage):
table_migrate = False
tname = tablename + '_' + masterapp
table = db.get(tname, None)
+ Field = db.Field
if table is None:
- table = db.define_table(
+ db.define_table(
tname,
- db.Field('locked', 'boolean', default=False),
- db.Field('client_ip', length=64),
- db.Field('created_datetime', 'datetime',
+ Field('locked', 'boolean', default=False),
+ Field('client_ip', length=64),
+ Field('created_datetime', 'datetime',
default=request.now),
- db.Field('modified_datetime', 'datetime'),
- db.Field('unique_key', length=64),
- db.Field('session_data', 'blob'),
+ Field('modified_datetime', 'datetime'),
+ Field('unique_key', length=64),
+ Field('session_data', 'blob'),
migrate=table_migrate,
)
+ table = db[tname] # to allow for lazy table
try:
- # Get session data out of the database
-
- # Key comes from the cookie
- key = request.cookies[response.session_id_name].value
+ # Get session data out of the database
+ # Key comes from the cookie
+ key = cookies[response.session_id_name].value
(record_id, unique_key) = key.split(':')
if record_id == '0':
raise Exception, 'record_id == 0'
- # Select from database.
+ # Select from database
rows = db(table.id == record_id).select()
-
- # Make sure the session data exists in the database
+ # Make sure the session data exists in the database
if len(rows) == 0 or rows[0].unique_key != unique_key:
raise Exception, 'No record'
-
- # rows[0].update_record(locked=True)
-
-
- # Unpickle the data
+ # rows[0].update_record(locked=True)
+ # Unpickle the data
session_data = cPickle.loads(rows[0].session_data)
self.update(session_data)
except Exception:
@@ -573,8 +585,9 @@ class Session(Storage):
response._dbtable_and_field = \
(response.session_id_name, table, record_id, unique_key)
response.session_id = '%s:%s' % (record_id, unique_key)
- response.cookies[response.session_id_name] = response.session_id
- response.cookies[response.session_id_name]['path'] = '/'
+ rcookies = response.cookies
+ rcookies[response.session_id_name] = response.session_id
+ rcookies[response.session_id_name]['path'] = '/'
self.__hash = hashlib.md5(str(self)).digest()
if self.flash:
(response.flash, self.flash) = (self.flash, None)
diff --git a/gluon/html.py b/gluon/html.py
index b758c597..2d6b8131 100644
--- a/gluon/html.py
+++ b/gluon/html.py
@@ -296,7 +296,8 @@ def URL(
if other.endswith('/'):
other += '/' # add trailing slash to make last trailing empty arg explicit
- if vars.has_key('_signature'): vars.pop('_signature')
+ if '_signature' in vars:
+ vars.pop('_signature')
list_vars = []
for (key, vals) in sorted(vars.items()):
if not isinstance(vals, (list, tuple)):
@@ -387,7 +388,7 @@ def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=
"""
- if not request.get_vars.has_key('_signature'):
+ if not '_signature' in request.get_vars:
return False # no signature in the request URL
# check if user_signature requires
@@ -484,15 +485,17 @@ class XmlComponent(object):
components += [other]
return CAT(*components)
- def add_class(self, name):
+ def add_class(self, name):
""" add a class to _class attribute """
- classes = set(self['_class'].split())|set(name.split())
+ c = self['_class']
+ classes = (set(c.split()) if c else set())|set(name.split())
self['_class'] = ' '.join(classes) if classes else None
return self
def remove_class(self, name):
""" remove a class from _class attribute """
- classes = set(self['_class'].split())-set(name.split())
+ c = self['_class']
+ classes = (set(c.split()) if c else set())-set(name.split())
self['_class'] = ' '.join(classes) if classes else None
return self
@@ -656,17 +659,17 @@ class DIV(XmlComponent):
self.attributes = attributes
self._fixup()
# converts special attributes in components attributes
- self._postprocessing()
self.parent = None
for c in self.components:
self._setnode(c)
+ self._postprocessing()
def update(self, **kargs):
"""
dictionary like updating of the tag attributes
"""
- for (key, value) in kargs.items():
+ for (key, value) in kargs.iteritems():
self[key] = value
return self
@@ -811,7 +814,9 @@ class DIV(XmlComponent):
c.latest = self.latest
c.session = self.session
c.formname = self.formname
- if hideerror: c['hideerror'] = hideerror
+ if hideerror and not \
+ self.attributes.get('hideerror',False):
+ c['hideerror'] = hideerror
newstatus = c._traverse(status,hideerror) and newstatus
# for input, textarea, select, option
@@ -1038,7 +1043,7 @@ class DIV(XmlComponent):
tag = getattr(self,'tag').replace('/', '')
if args and tag not in args:
check = False
- for (key, value) in kargs.items():
+ for (key, value) in kargs.iteritems():
if key not in ['first_only', 'replace', 'find_text']:
if isinstance(value, (str, int)):
if self[key] != str(value):
@@ -1109,16 +1114,15 @@ class DIV(XmlComponent):
sibs = [s for s in self.parent.components if not s == self]
matches = []
first_only = False
- if kargs.has_key("first_only"):
- first_only = kargs["first_only"]
- del kargs["first_only"]
+ if 'first_only' in kargs:
+ first_only = kargs.pop('first_only')
for c in sibs:
try:
check = True
tag = getattr(c,'tag').replace("/","")
if args and tag not in args:
check = False
- for (key, value) in kargs.items():
+ for (key, value) in kargs.iteritems():
if c[key] != value:
check = False
if check:
@@ -1682,14 +1686,14 @@ class INPUT(DIV):
if name is None or name == '':
return True
name = str(name)
-
+ request_vars_get = self.request_vars.get
if self['_type'] != 'checkbox':
self['old_value'] = self['value'] or self['_value'] or ''
- value = self.request_vars.get(name, '')
+ value = request_vars_get(name, '')
self['value'] = value
else:
self['old_value'] = self['value'] or False
- value = self.request_vars.get(name)
+ value = request_vars_get(name)
if isinstance(value, (tuple, list)):
self['value'] = self['_value'] in value
else:
@@ -1932,14 +1936,15 @@ class FORM(DIV):
# check formname and formkey
status = True
- if self.session:
- formkey = self.session.get('_formkey[%s]' % self.formname, None)
+ request_vars = self.request_vars
+ if session:
+ formkey = session.get('_formkey[%s]' % formname, None)
# check if user tampering with form and void CSRF
- if formkey != self.request_vars._formkey:
+ if formkey != request_vars._formkey:
status = False
- if self.formname != self.request_vars._formname:
+ if formname != request_vars._formname:
status = False
- if status and self.session:
+ if status and session:
# check if editing a record that has been modified by the server
if hasattr(self,'record_hash') and self.record_hash != formkey:
status = False
@@ -1983,10 +1988,10 @@ class FORM(DIV):
def hidden_fields(self):
c = []
+ attr = self.attributes.get('hidden',{})
if 'hidden' in self.attributes:
- for (key, value) in self.attributes.get('hidden',{}).items():
- c.append(INPUT(_type='hidden', _name=key, _value=value))
-
+ c = [INPUT(_type='hidden', _name=key, _value=value)
+ for (key, value) in attr.iteritems()]
if hasattr(self, 'formkey') and self.formkey:
c.append(INPUT(_type='hidden', _name='_formkey',
_value=self.formkey))
@@ -2055,7 +2060,7 @@ class FORM(DIV):
onsuccess(self)
if next:
if self.vars:
- for key,value in self.vars.items():
+ for key,value in self.vars.iteritems():
next = next.replace('[%s]' % key,
urllib.quote(str(value)))
if not next.startswith('/'):
@@ -2116,11 +2121,11 @@ class FORM(DIV):
inputs = [INPUT(_type='button',
_value=name,
_onclick=FORM.REDIRECT_JS % link) \
- for name,link in buttons.items()]
+ for name,link in buttons.iteritems()]
inputs += [INPUT(_type='hidden',
_name=name,
_value=value)
- for name,value in hidden.items()]
+ for name,value in hidden.iteritems()]
form = FORM(INPUT(_type='submit',_value=text),*inputs)
form.process()
return form
@@ -2268,10 +2273,11 @@ class MENU(DIV):
select = SELECT(**self.attributes)
for item in data:
if len(item) <= 4 or item[4] == True:
- if item[2]:
- select.append(OPTION(CAT(prefix, item[0]), _value=item[2], _selected=item[1]))
- if len(item)>3 and len(item[3]):
- self.serialize_mobile(item[3], select, prefix = CAT(prefix, item[0], '/'))
+ select.append(OPTION(CAT(prefix, item[0]),
+ _value=item[2], _selected=item[1]))
+ if len(item)>3 and len(item[3]):
+ self.serialize_mobile(
+ item[3], select, prefix = CAT(prefix, item[0], '/'))
select['_onchange'] = 'window.location=this.value'
return select
@@ -2323,7 +2329,7 @@ def test():
>>> print form.accepts({'myvar':'34'}, formname=None)
False
>>> print form.xml()
-
+
>>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
True
>>> print form.xml()
@@ -2337,7 +2343,7 @@ def test():
>>> print form.accepts({'myvar':'as df'}, formname=None)
False
>>> print form.xml()
-
+
>>> session={}
>>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$')))
>>> if form.accepts({}, session,formname=None): print 'passed'
diff --git a/gluon/http.py b/gluon/http.py
index c4389b9e..548ac6d9 100644
--- a/gluon/http.py
+++ b/gluon/http.py
@@ -77,41 +77,45 @@ class HTTP(BaseException):
str(cookie)[11:] for cookie in cookies.values()]
def to(self, responder):
- if self.status in defined_status:
- status = '%d %s' % (self.status, defined_status[self.status])
+ status = self.status
+ headers = self.headers
+ if status in defined_status:
+ status = '%d %s' % (status, defined_status[status])
else:
- status = str(self.status) + ' '
- if not 'Content-Type' in self.headers:
- self.headers['Content-Type'] = 'text/html; charset=UTF-8'
+ status = str(status) + ' '
+ if not 'Content-Type' in headers:
+ headers['Content-Type'] = 'text/html; charset=UTF-8'
body = self.body
if status[:1] == '4':
if not body:
body = status
if isinstance(body, str):
- if len(body)<512 and self.headers['Content-Type'].startswith('text/html'):
+ if len(body)<512 and headers['Content-Type'].startswith('text/html'):
body += '' % ('x'*512) ### trick IE
- self.headers['Content-Length'] = len(body)
- headers = []
- for (k, v) in self.headers.items():
+ headers['Content-Length'] = len(body)
+ rheaders = []
+ for k, v in headers.iteritems():
if isinstance(v, list):
- for item in v:
- headers.append((k, str(item)))
+ rheaders += [(k, str(item)) for item in v]
else:
- headers.append((k, str(v)))
- responder(status, headers)
- if hasattr(body, '__iter__') and not isinstance(self.body, str):
+ rheaders.append((k, str(v)))
+ responder(status, rheaders)
+ if isinstance(body,str):
+ return [body]
+ elif hasattr(body, '__iter__'):
return body
- return [str(body)]
+ else:
+ return [str(body)]
@property
def message(self):
- '''
+ """
compose a message describing this exception
- "status defined_status [web2py_error]"
+ "status defined_status [web2py_error]"
message elements that are not defined are omitted
- '''
+ """
msg = '%(status)d'
if self.status in defined_status:
msg = '%(status)d %(defined_status)s'
diff --git a/gluon/languages.py b/gluon/languages.py
index 2c8231d5..f2d78c52 100644
--- a/gluon/languages.py
+++ b/gluon/languages.py
@@ -10,7 +10,7 @@ Plural subsystem is created by Vladyslav Kozlovskyy (Ukraine)
"""
-from os import path as ospath, stat as ostat, sep as osep
+import os
import re
from utf8 import Utf8
from cgi import escape
@@ -20,7 +20,7 @@ import marshal
import copy_reg
from fileutils import abspath, listdir
import settings
-from cfs import getcfs, cfs
+from cfs import getcfs
from thread import allocate_lock
from html import XML, xmlescape
from contrib.markmin.markmin2html import render, markmin_escape
@@ -28,10 +28,37 @@ from string import maketrans
__all__ = ['translator', 'findT', 'update_all_languages']
+ospath = os.path
+ostat = os.stat
+osep = os.sep
+isdir = os.path.isdir
+is_gae = settings.global_settings.web2py_runtime_gae
+
+DEFAULT_LANGUAGE = 'en'
+
+# DEFAULT PLURAL-FORMS RULES:
+# language doesn't use plural forms
+DEFAULT_NPLURALS = 1
+# only one singular/plural form is used
+DEFAULT_GET_PLURAL_ID = lambda n: 0
+# word is unchangeable
+DEFAULT_CONSTRUCTOR_PLURAL_FORM = lambda word, plural_id: word
+
+def safe_eval(text):
+ if text.strip():
+ try:
+ import ast
+ return ast.literal_eval(text)
+ except ImportError:
+ return eval(text,{},{})
+ return None
+
# used as default filter in translator.M()
-markmin = lambda s: render( regex_param.sub(
- lambda m: '{' + markmin_escape(m.group('s')) + '}',
- s ), sep='br', autolinks=None, id_prefix='' )
+def markmin_aux(m):
+ return '{%s}' % markmin_escape(m.group('s'))
+def markmin(s):
+ return render(regex_param.sub(markmin_aux,s),
+ sep='br', autolinks=None, id_prefix='')
NUMBERS = (int,long,float)
@@ -49,28 +76,24 @@ regex_param=re.compile(r'{(?P.+?)}')
regex_language = \
re.compile('^([a-zA-Z]{2})(\-[a-zA-Z]{2})?(\-[a-zA-Z]+)?$')
regex_langfile = re.compile('^[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$')
-regex_langinfo = re.compile("^[^'\"]*['\"]([^'\"]*)['\"]\s*:\s*['\"]([^'\"]*)['\"].*$")
regex_backslash = re.compile(r"\\([\\{}%])")
regex_plural = re.compile('%({.+?})')
regex_plural_dict = re.compile('^{(?P[^()[\]][^()[\]]*?)\((?P[^()\[\]]+)\)}$') # %%{word(varname or number)}
regex_plural_tuple = re.compile('^{(?P[^[\]()]+)(?:\[(?P\d+)\])?}$') # %%{word[index]} or %%{word}
-regex_plural_q = re.compile('^asdf$') # %%{?word?cnt}, %%{??cnt} or %%{?cnt}
regex_plural_rules = re.compile('^plural_rules-[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$')
-upper_fun = lambda s: unicode(s,'utf-8').upper().encode('utf-8')
-title_fun = lambda s: unicode(s,'utf-8').title().encode('utf-8')
-cap_fun = lambda s: unicode(s,'utf-8').capitalize().encode('utf-8')
-
-# DEFAULT PLURAL-FORMS RULES:
-default_nplurals = 1 # language doesn't use plural forms
-default_get_plural_id = lambda n: 0 # only one singular/plural form is used
-default_construct_plural_form = lambda word, plural_id: word # word is unchangeable
-
+# UTF8 helper functions
+def upper_fun(s):
+ return unicode(s,'utf-8').upper().encode('utf-8')
+def title_fun(s):
+ return unicode(s,'utf-8').title().encode('utf-8')
+def cap_fun(s):
+ return lambda s: unicode(s,'utf-8').capitalize().encode('utf-8')
ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f')
ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
# cache of translated messages:
-# of structure:
+# global_language_cache:
# { 'languages/xx.py':
# ( {"def-message": "xx-message",
# ...
@@ -78,35 +101,38 @@ ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
# 'languages/yy.py': ( {dict}, lock_object )
# ...
# }
-tcache={}
+
+global_language_cache={}
def get_from_cache(cache, val, fun):
- lock=cache[1]
+ lang_dict, lock = cache
lock.acquire()
try:
- result=cache[0].get(val);
+ result = lang_dict.get(val);
finally:
lock.release()
if result:
return result
lock.acquire()
try:
- result=cache[0].setdefault(val, fun())
+ result = lang_dict.setdefault(val, fun())
finally:
lock.release()
return result
-def clear_cache(cache):
- lock=cache[1]
+def clear_cache(filename):
+ cache = global_language_cache.setdefault(
+ filename, ({}, allocate_lock()))
+ lang_dict, lock = cache
lock.acquire()
try:
- cache[0].clear();
+ lang_dict.clear();
finally:
lock.release()
-
def lang_sampling(lang_tuple, langlist):
- """ search *lang_tuple* in *langlist*
+ """
+ search *lang_tuple* in *langlist*
Args:
lang_tuple (tuple of strings): ('aa'[[,'-bb'],'-cc'])
@@ -137,32 +163,28 @@ def lang_sampling(lang_tuple, langlist):
def read_dict_aux(filename):
- fp = portalocker.LockedFile(filename, 'r')
- lang_text = fp.read().replace('\r\n', '\n')
- fp.close()
- # clear cache of processed messages:
- clear_cache(tcache.setdefault(filename, ({}, allocate_lock())))
- if not lang_text.strip():
- return {}
+ lang_text = portalocker.read_locked(filename).replace('\r\n', '\n')
+ clear_cache(filename)
try:
- return eval(lang_text)
+ return safe_eval(lang_text) or {}
except Exception, e:
- status='Syntax error in %s (%s)' % (filename, e)
+ status = 'Syntax error in %s (%s)' % (filename, e)
logging.error(status)
return {'__corrupted__':status}
-
def read_dict(filename):
- """ return dictionary with translation messages
+ """
+ return dictionary with translation messages
"""
return getcfs('lang:'+filename, filename,
lambda: read_dict_aux(filename))
def get_lang_info(lang, langdir):
- """retrieve lang information from *langdir*/*lang*.py file.
- Read few strings from lang.py file until keys !langname!,
- !langcode! or keys greater then '!*' were found
+ """
+ retrieve lang information from *langdir*/*lang*.py file.
+ Read few strings from lang.py file until keys !langname!,
+ !langcode! or keys greater then '!*' were found
args:
lang (str): lang-code or 'default'
@@ -173,159 +195,78 @@ def get_lang_info(lang, langdir):
e.g.: ('en', 'English', 1338549043.0)
"""
filename = ospath.join(langdir, lang+'.py')
- langcode=langname=''
- f = portalocker.LockedFile(filename, 'r')
- try:
- while not (langcode and langname):
- line = f.readline()
- if not line:
- break
- match=regex_langinfo.match(line)
- if match:
- k = match.group(1)
- if k == '!langname!':
- langname = match.group(2)
- elif k == '!langcode!':
- langcode = match.group(2)
- elif k[0:1] > '!':
- break
- finally:
- f.close()
- if not langcode:
- langcode = lang if lang != 'default' else 'en'
- return langcode, langname or langcode, ostat(filename).st_mtime
+ d = read_dict(filename)
+ langcode = d.get('!langcode!',DEFAULT_LANGUAGE)
+ langname = d.get('!langname!',langcode)
+ return (langcode, langname or langcode, ostat(filename).st_mtime)
-def read_possible_languages_aux(langdir):
+def read_possible_languages(appdir):
langs = {}
# scan languages directory for langfiles:
- for langfile in [f for f in
- listdir(langdir, regex_langfile) +
- listdir(langdir, '^default\.py$')
- if osep not in f]:
- lang=langfile[:-3]
- langs[lang]=get_lang_info(lang, langdir)
- if 'default' not in langs:
+ langdir = ospath.join(appdir,'languages')
+ for filename in os.listdir(langdir):
+ if regex_langfile.match(filename) or filename=='default.py':
+ lang = filename[:-3]
+ langs[lang] = get_lang_info(lang, langdir)
+ if not 'en' in langs:
# if default.py is not found, add default value:
- langs['default'] = ('en', 'English', 0)
- deflang=langs['default']
- if deflang[0] not in langs:
- # create language from default.py:
- langs[deflang[0]] = (deflang[0], deflang[1], 0)
+ langs['en'] = ('en', 'English', 0)
return langs
-def read_possible_languages(path):
- lang_path = ospath.join(path, 'languages')
- return getcfs('langs:'+lang_path, lang_path,
- lambda: read_possible_languages_aux(lang_path))
-
-
-def read_plural_rules_aux(filename):
- """retrieve plural rules from rules/*plural_rules-lang*.py file.
+def read_global_plural_rules(filename):
+ """
+ retrieve plural rules from rules/*plural_rules-lang*.py file.
args:
filename (str): plural_rules filename
returns:
- tuple(nplurals, get_plural_id, construct_plural_form)
- e.g.: (3, , )
+ (nplurals, get_plural_id, construct_plural_form, status)
+ e.g.: (3, , , ok)
"""
- f = portalocker.LockedFile(filename, 'r')
- plural_py=f.read().replace('\r\n','\n')
- f.close()
+ env = {}
+ data = portalocker.read_locked(filename)
try:
- exec(plural_py)
- nplurals=locals().get('nplurals', default_nplurals)
- get_plural_id=locals().get('get_plural_id', default_get_plural_id)
- construct_plural_form=locals().get('construct_plural_form',
- default_construct_plural_form)
+ exec(data) in env
status='ok'
except Exception, e:
- nplurals=default_nplurals
- get_plural_id=default_get_plural_id
- construct_plural_form=default_construct_plural_form
status='Syntax error in %s (%s)' % (filename, e)
logging.error(status)
+ nplurals = env.get('nplurals', DEFAULT_NPLURALS)
+ get_plural_id = env.get('get_plural_id', DEFAULT_GET_PLURAL_ID)
+ construct_plural_form = env.get('construct_plural_form',
+ DEFAULT_CONSTRUCTOR_PLURAL_FORM)
return (nplurals, get_plural_id, construct_plural_form, status)
-def read_plural_rules(lang):
- filename = abspath('gluon','contrib','rules', 'plural_rules-%s.py' % lang)
- return getcfs('plural_rules-'+lang, filename,
- lambda: read_plural_rules_aux(filename))
-pcache={}
def read_possible_plurals():
- """ create list of all possible plural rules files
- result is cached to increase speed
"""
- global pcache
+ create list of all possible plural rules files
+ result is cached to increase speed
+ """
pdir = abspath('gluon','contrib','rules')
plurals = {}
# scan rules directory for plural_rules-*.py files:
- for pname in [f for f in listdir(pdir, regex_plural_rules)
- if osep not in f]:
-
- lang=pname[13:-3]
- fname=ospath.join(pdir, pname)
- mtime=ostat(fname).st_mtime
- if lang in pcache and pcache[lang][2] == mtime:
- # if plural_file's mtime wasn't changed - use previous value:
- plurals[lang]=pcache[lang]
- else:
- # otherwise, reread plural_rules-file:
- if 'plural_rules-'+lang in cfs:
- n,f1,f2,status=read_plural_rules(lang)
- else:
- n,f1,f2,status=read_plural_rules_aux(fname)
- plurals[lang]=(n, pname, mtime, status)
- pcache=plurals
- return pcache
-
-def get_plural_rules(languages):
- """get plural-forms rules for language *lang*
- if rules not found - default rules will be return and lang=='unknown'
-
- args:
- lang (str): the languages, for one of which the plural-forms is return
-
- returns:
- tuples(lang, plural_rules-filename, nplurals,
- get_plural_id(), construct_plural_form(), status)
- """
- if isinstance(languages, str):
- languages = [languages]
-
- all_plurals=read_possible_plurals()
- for lang in languages:
- match_language = regex_language.match(lang.strip().lower())
- if match_language:
- match_language = tuple(part
- for part in match_language.groups()
- if part)
- lang = lang_sampling(match_language, all_plurals.keys())
- if lang:
- ( nplurals,
- get_plural_id,
- construct_plural_form,
- status
- ) = read_plural_rules(lang)
- return (lang, all_plurals[lang][1], nplurals,
- get_plural_id,
- construct_plural_form,
- status)
- return ('unknown', None, default_nplurals,
- default_get_plural_id,
- default_construct_plural_form,
- 'ok')
+ for pname in os.listdir(pdir):
+ if not isdir(pname) and regex_plural_rules.match(pname):
+ lang = pname[13:-3]
+ fname = ospath.join(pdir, pname)
+ n, f1, f2, status = read_global_plural_rules(fname)
+ if status == 'ok':
+ plurals[lang] = (lang, n, f1, f2, pname)
+ plurals['default'] = ('default',
+ DEFAULT_NPLURALS,
+ DEFAULT_GET_PLURAL_ID,
+ DEFAULT_CONSTRUCTOR_PLURAL_FORM,
+ None)
+ return plurals
+PLURAL_RULES = read_possible_plurals()
def read_plural_dict_aux(filename):
- fp = portalocker.LockedFile(filename, 'r')
- lang_text = fp.read().replace('\r\n', '\n')
- fp.close()
- if not lang_text.strip():
- return {}
+ lang_text = portalocker.read_locked(filename).replace('\r\n', '\n')
try:
- return eval(lang_text)
+ return eval(lang_text) or {}
except Exception, e:
status='Syntax error in %s (%s)' % (filename, e)
logging.error(status)
@@ -335,7 +276,6 @@ def read_plural_dict(filename):
return getcfs('plurals:'+filename, filename,
lambda: read_plural_dict_aux(filename))
-
def write_plural_dict(filename, contents):
if '__corrupted__' in contents:
return
@@ -466,13 +406,10 @@ class lazyT(object):
return lazyT(self.m, symbols, self.T, self.f, self.t, self.M)
class translator(object):
-
"""
this class is instantiated by gluon.compileapp.build_environment
as the T object
-
::
-
T.force(None) # turns off translation
T.force('fr, it') # forces web2py to translate using fr.py or it.py
@@ -480,27 +417,21 @@ class translator(object):
notice 1: there is no need to force since, by default, T uses
http_accept_language to determine a translation file.
-
- notice 2: en and en-en are considered different languages!
-
- notice 3: if language xx-yy is not found force() probes other similar
- languages using such algorithm: xx-yy.py -> xx.py -> xx-yy*.py -> xx*.py
+ notice 2:
+ en and en-en are considered different languages!
+ notice 3:
+ if language xx-yy is not found force() probes other similar
+ languages using such algorithm:
+ xx-yy.py -> xx.py -> xx-yy*.py -> xx*.py
"""
def __init__(self, request):
- global tcache
self.request = request
self.folder = request.folder
- dfile = ospath.join(self.folder,'languages','default.py')
- if ospath.exists(dfile):
- self.default_language_file = dfile
- self.default_t = read_dict(dfile)
- else: # languages/default.py is not found
- self.default_language_file = ospath.join(self.folder, 'languages','')
- self.default_t = {}
- self.cache = tcache.setdefault(self.default_language_file, ({}, allocate_lock()))
- self.current_languages = [self.get_possible_languages_info('default')[0]]
+ self.langpath = ospath.join(self.folder,'languages')
+ self.filenames = set(os.listdir(self.langpath))
self.http_accept_language = request.env.http_accept_language
+ # self.cache # filled in self.force()
# self.accepted_language = None # filled in self.force()
# self.language_file = None # filled in self.force()
# self.plural_language = None # filled in self.force()
@@ -511,12 +442,89 @@ class translator(object):
# self.plural_file = None # filled in self.force()
# self.plural_dict = None # filled in self.force()
# self.plural_status = None # filled in self.force()
- self.requested_languages = self.force(self.http_accept_language)
+
+ self.requested_languages = \
+ self.force(self.http_accept_language)
self.lazy = True
self.otherTs = {}
self.filter = markmin
self.ftag = 'markmin'
+ def get_possible_languages(self):
+ return [lang[:-3] for lang in self.filenames \
+ if regex_langfile.match(lang)]
+
+ def set_current_languages(self, *languages):
+ """
+ set current AKA "default" languages
+ setting one of this languages makes force() function
+ turn translation off to use default language
+ """
+ if len(languages) == 1 and isinstance(
+ languages[0], (tuple, list)):
+ languages = languages[0]
+ self.current_languages = languages
+ self.force(self.http_accept_language)
+
+ def set_plural(self, language):
+ """
+ initialize plural forms subsystem
+ invoked from self.force()
+ """
+ lang = language[:2]
+ (self.plural_language,
+ self.nplurals,
+ self.get_plural_id,
+ self.construct_plural_form,
+ self.plural_filename
+ ) = PLURAL_RULES.get(language,PLURAL_RULES['default'])
+ for lang in (language, language[:5], language[:2]):
+ filename = 'plural-%s.py' % lang
+ if filename in self.filenames:
+ self.plural_file = ospath.join(self.langpath,filename)
+ self.plural_dict = read_plural_dict(self.plural_file)
+ break
+ else:
+ self.plural_file = None
+ self.plural_dict = {}
+
+ def plural(self, word, n):
+ """
+ get plural form of word for number *n*
+ NOTE: *word* MUST be defined in current language
+ (T.accepted_language)
+
+ invoked from T()/M() in %%{} tag
+ args:
+ word (str): word in singular
+ n (numeric): number plural form created for
+
+ returns:
+ (str): word in appropriate singular/plural form
+ """
+ nplurals = self.nplurals
+ if int(n)==1:
+ return word
+ elif word:
+ id = min(int(n)-1,1) # self.get_plural_id(abs(int(n)))
+ # id = 0 first plural form
+ # id = 1 second plural form
+ # etc.
+ forms = self.plural_dict.get(word, [])
+ if len(forms)>=id:
+ # have this plural form
+ return forms[id-1]
+ else:
+ # guessing this plural form
+ forms += ['']*(nplurals-len(forms)-1)
+ form = self.construct_plural_form(word, id)
+ forms[id-1] = form
+ self.plural_dict[word] = forms
+ if self.plural_file and not is_gae:
+ write_plural_dict(self.plural_file,
+ self.plural_dict)
+ return form
+
def get_possible_languages_info(self, lang=None):
"""
return info for selected language or dictionary with all
@@ -533,90 +541,12 @@ class translator(object):
langname(from !langname! key),
langfile_mtime ) }
"""
- if lang:
- return read_possible_languages(self.folder).get(lang)
- return read_possible_languages(self.folder)
-
- def get_possible_languages(self):
- """ get list of all possible languages for current applications """
- return sorted( set(lang for lang in
- read_possible_languages(self.folder).iterkeys()
- if lang != 'default')
- | set(self.current_languages))
-
- def set_current_languages(self, *languages):
- """
- set current AKA "default" languages
- setting one of this languages makes force() function
- turn translation off to use default language
- """
- if len(languages) == 1 and isinstance(languages[0], (tuple, list)):
- languages = languages[0]
- self.current_languages = languages
- self.force(self.http_accept_language)
-
- def set_plural(self, languages):
- """ initialize plural forms subsystem
- invoked from self.force()
- """
- ( self.plural_language,
- self.plural_rules_file,
- self.nplurals,
- self.get_plural_id,
- self.construct_plural_form,
- self.plural_status
- ) = get_plural_rules(languages)
-
- if self.plural_language == 'unknown':
- self.plural_file = None
- self.plural_dict = {}
- else:
- self.plural_file = ospath.join(self.folder,
- 'languages',
- 'plural-%s.py' % self.plural_language)
- if ospath.exists(self.plural_file):
- self.plural_dict = read_plural_dict(self.plural_file)
- else:
- self.plural_dict = {}
-
- def plural(self, word, n):
- """ get plural form of word for number *n*
- NOTE: *word" MUST be defined in current language
- (T.accepted_language)
-
- invoked from T()/M() in %%{} tag
- args:
- word (str): word in singular
- n (numeric): number plural form created for
-
- returns:
- (str): word in appropriate singular/plural form
- """
- nplurals = self.nplurals
- if word:
- id = self.get_plural_id(abs(int(n)))
- if id > 0:
- forms = self.plural_dict.get(word, [])
- if forms:
- try:
- form = forms[id-1]
- except:
- form = None
- if form: return form
- form = self.construct_plural_form(word, id)
- if len(forms) < nplurals-1:
- forms.extend('' for i in xrange(nplurals-len(forms)-1))
- forms[id-1] = form
- self.plural_dict[word] = forms
- if (self.plural_file and
- not settings.global_settings.web2py_runtime_gae):
- write_plural_dict(self.plural_file, self.plural_dict)
- return form
- return word
+ info = read_possible_languages(self.folder)
+ if lang: info = info.get(lang)
+ return info
def force(self, *languages):
"""
-
select language(s) for translation
if a list of languages is passed as a parameter,
@@ -627,49 +557,41 @@ class translator(object):
default language will be selected if none
of them matches possible_languages.
"""
- global tcache
language = ''
-
- if not languages or languages[0] is None:
+ if isinstance(languages,str):
+ languages = regex_language.findall(languages.lower())
+ elif not languages or languages[0] is None:
languages = []
- if len(languages) == 1 and isinstance(languages[0], (str, unicode)):
- languages = languages[0]
-
- if languages:
- if isinstance(languages, (str, unicode)):
- parts = languages.split(';')
- languages = []
- for al in parts:
- languages.extend(al.split(','))
-
- possible_languages = self.get_possible_languages()
- for lang in languages:
- match_language = regex_language.match(lang.strip().lower())
- if match_language:
- match_language = tuple(part
- for part in match_language.groups()
- if part)
- language = lang_sampling(match_language,
- self.current_languages)
- if language:
- break
- language = lang_sampling(match_language, possible_languages)
- if language:
- self.language_file = ospath.join(self.folder,
- 'languages',
- language + '.py')
- if ospath.exists(self.language_file):
- self.t = read_dict(self.language_file)
- self.cache = tcache.setdefault(self.language_file,
- ({},allocate_lock()))
- self.set_plural(language)
- self.accepted_language = language
- return languages
- self.accepted_language = language or self.current_languages[0]
- self.language_file = self.default_language_file
- self.cache = tcache[self.language_file]
- self.t = self.default_t
- self.set_plural(language or self.current_languages)
+ for lang in languages:
+ if lang+'.py' in self.filenames:
+ language = lang
+ langfile = language+'.py'
+ break
+ elif len(lang)>5 and lang[:5]+'.py' in self.filenames:
+ language = lang[:5]
+ langfile = language+'.py'
+ break
+ elif len(lang)>2 and lang[:2]+'.py' in self.filenames:
+ language = lang[:2]
+ langfile = language+'.py'
+ break
+ else:
+ if 'default.py' in self.filenames:
+ language = DEFAULT_LANGUAGE
+ langfile = 'default.py'
+ else:
+ language = DEFAULT_LANGUAGE
+ langfile = None
+ self.accepted_language = language
+ if langfile:
+ self.language_file = ospath.join(self.langpath,langfile)
+ self.t = read_dict(self.language_file)
+ else:
+ self.language_file = None
+ self.t = {}
+ self.cache = global_language_cache.setdefault(
+ self.language_file,({},allocate_lock()))
+ self.set_plural(language)
return languages
def __call__(self, message, symbols={}, language=None, lazy=None):
@@ -700,26 +622,30 @@ class translator(object):
prefix = '@'+(ftag or 'userdef')+'\x01'
else:
prefix = '@'+self.ftag+'\x01'
- message = get_from_cache(self.cache, prefix+message,
- lambda: get_tr(message, prefix, filter))
+ message = get_from_cache(
+ self.cache, prefix+message,
+ lambda: get_tr(message, prefix, filter))
if symbols or symbols == 0 or symbols == "":
if isinstance(symbols, dict):
- symbols.update( (key, xmlescape(value).translate(ttab_in))
- for key, value in symbols.iteritems()
- if not isinstance(value, NUMBERS) )
+ symbols.update(
+ (key, xmlescape(value).translate(ttab_in))
+ for key, value in symbols.iteritems()
+ if not isinstance(value, NUMBERS) )
else:
if not isinstance(symbols, tuple):
symbols = (symbols,)
- symbols = tuple(value if isinstance(value, NUMBERS)
- else xmlescape(value).translate(ttab_in)
- for value in symbols)
+ symbols = tuple(
+ value if isinstance(value, NUMBERS)
+ else xmlescape(value).translate(ttab_in)
+ for value in symbols)
message = self.params_substitution(message, symbols)
return XML(message.translate(ttab_out))
- def M(self, message, symbols={}, language=None, lazy=None, filter=None, ftag=None):
- """ get cached translated markmin-message with inserted parametes
-
- if lazy==True lazyT object is returned
+ def M(self, message, symbols={}, language=None,
+ lazy=None, filter=None, ftag=None):
+ """
+ get cached translated markmin-message with inserted parametes
+ if lazy==True lazyT object is returned
"""
if lazy is None:
lazy = self.lazy
@@ -751,18 +677,20 @@ class translator(object):
"""
key = prefix+message
mt = self.t.get(key, None)
- if mt is not None:
- return mt
- if not message.startswith('##') and not '\n' in message:
- tokens = message.rsplit('##', 1)
- else:
- # this allows markmin syntax in translations
- tokens = [message]
- self.t[key] = mt = self.default_t.get(key, tokens[0])
- if (self.language_file != self.default_language_file and
- not settings.global_settings.web2py_runtime_gae):
- write_dict(self.language_file, self.t)
- return regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), mt)
+ if mt is None:
+ # we did not find a translation
+ if message.find('##')>0 and not '\n' in message:
+ # remove comments
+ message = message.rsplit('##', 1)[0]
+ # guess translation same as original
+ self.t[key] = mt = message
+ # update language file for later translation
+ if self.language_file and not is_gae:
+ write_dict(self.language_file, self.t)
+ # fix backslash escaping
+ mt = regex_backslash.sub(
+ lambda m: m.group(1).translate(ttab_in), mt)
+ return mt
def params_substitution(self, message, symbols):
"""
@@ -866,19 +794,21 @@ class translator(object):
"""
get cached translated message with inserted parameters(symbols)
"""
- message = get_from_cache(self.cache, message, lambda: self.get_t(message))
+ message = get_from_cache(self.cache, message,
+ lambda: self.get_t(message))
if symbols or symbols == 0 or symbols == "":
if isinstance(symbols, dict):
- symbols.update( (key, str(value).translate(ttab_in))
- for key, value in symbols.iteritems()
- if not isinstance(value, NUMBERS) )
+ symbols.update(
+ (key, str(value).translate(ttab_in))
+ for key, value in symbols.iteritems()
+ if not isinstance(value, NUMBERS) )
else:
if not isinstance(symbols, tuple):
symbols = (symbols,)
- symbols = tuple(value if isinstance(value, NUMBERS)
- else str(value).translate(ttab_in)
- for value in symbols)
-
+ symbols = tuple(
+ value if isinstance(value, NUMBERS)
+ else str(value).translate(ttab_in)
+ for value in symbols)
message = self.params_substitution(message, symbols)
return message.translate(ttab_out)
@@ -892,26 +822,25 @@ def findT(path, language='en'):
cp = ospath.join(path, 'controllers')
vp = ospath.join(path, 'views')
mop = ospath.join(path, 'modules')
- for file in listdir(mp, '^.+\.py$', 0) + listdir(cp, '^.+\.py$', 0)\
- + listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0):
- fp = portalocker.LockedFile(file, 'r')
- data = fp.read()
- fp.close()
+ for filename in \
+ listdir(mp, '^.+\.py$', 0)+listdir(cp, '^.+\.py$', 0)\
+ +listdir(vp, '^.+\.html$', 0)+listdir(mop, '^.+\.py$', 0):
+ data = portalocker.read_locked(filename)
items = regex_translate.findall(data)
for item in items:
try:
- message = eval(item)
- if not message.startswith('#') and not '\n' in message:
- tokens = message.rsplit('##', 1)
- else:
- # this allows markmin syntax in translations
- tokens = [message]
- if len(tokens) == 2:
- message = tokens[0].strip() + '##' + tokens[1].strip()
- if message and not message in sentences:
- sentences[message] = message
+ message = safe_eval(item)
except:
- pass
+ continue # silently ignore inproperly formatted strings
+ if not message.startswith('#') and not '\n' in message:
+ tokens = message.rsplit('##', 1)
+ else:
+ # this allows markmin syntax in translations
+ tokens = [message]
+ if len(tokens) == 2:
+ message = tokens[0].strip()+'##'+tokens[1].strip()
+ if message and not message in sentences:
+ sentences[message] = message
if not '!langcode!' in sentences:
sentences['!langcode!'] = (
'en' if language in ('default', 'en') else language)
@@ -937,6 +866,3 @@ def update_all_languages(application_path):
if __name__ == '__main__':
import doctest
doctest.testmod()
-
-
-
diff --git a/gluon/main.py b/gluon/main.py
index 7f95ecb0..85f57fca 100644
--- a/gluon/main.py
+++ b/gluon/main.py
@@ -21,7 +21,6 @@ import re
import copy
import sys
import time
-import thread
import datetime
import signal
import socket
@@ -29,6 +28,7 @@ import tempfile
import random
import string
import urllib2
+from thread import allocate_lock
from fileutils import abspath, write_file, parse_version
from settings import global_settings
@@ -69,8 +69,11 @@ import logging.config
import gluon.messageboxhandler
logging.gluon = gluon
+exists = os.path.exists
+pjoin = os.path.join
+
logpath = abspath("logging.conf")
-if os.path.exists(logpath):
+if exists(logpath):
logging.config.fileConfig(abspath("logging.conf"))
else:
logging.basicConfig()
@@ -87,10 +90,10 @@ from dal import BaseAdapter
from settings import global_settings
from validators import CRYPT
from cache import Cache
-from html import URL as Url, xmlescape
+from html import URL, xmlescape
from utils import is_valid_ip_address
+from rewrite import load, url_in, thread as rwthread, try_rewrite_on_error
import newcron
-import rewrite
__all__ = ['wsgibase', 'save_password', 'appfactory', 'HttpServer']
@@ -103,7 +106,7 @@ requests = 0 # gc timer
regex_client = re.compile('[\w\-:]+(\.[\w\-]+)*\.?') # ## to account for IPV6
try:
- version_info = open(os.path.join(global_settings.gluon_parent, 'VERSION'), 'r')
+ version_info = open(pjoin(global_settings.gluon_parent, 'VERSION'), 'r')
raw_version_string = version_info.read().strip()
version_info.close()
global_settings.web2py_version = parse_version(raw_version_string)
@@ -118,7 +121,7 @@ except:
if not global_settings.web2py_runtime_gae:
logger.warn('unable to import Rocket')
-rewrite.load()
+load()
def get_client(env):
"""
@@ -129,11 +132,16 @@ def get_client(env):
"""
g = regex_client.search(env.get('http_x_forwarded_for', ''))
if g:
- return g.group()
- g = regex_client.search(env.get('remote_addr', ''))
- if g:
- return g.group()
- return '127.0.0.1'
+ client = g.group()
+ else:
+ g = regex_client.search(env.get('remote_addr', ''))
+ if g:
+ client = g.group()
+ else:
+ client = '127.0.0.1'
+ if not is_valid_ip_address(client):
+ raise HTTP(400,"Bad Request (request.client=%s)" % client)
+ return client
def copystream_progress(request, chunk_size= 10**5):
"""
@@ -253,7 +261,8 @@ def middleware_aux(request, response, *middleware_apps):
for item in middleware_apps:
app=item(app)
def caller(app):
- return app(request.wsgi.environ,request.wsgi.start_response)
+ wsgi = request.wsgi
+ return app(wsgi.environ, wsgi.start_response)
return lambda caller=caller, app=app: caller(app)
return middleware
@@ -279,14 +288,14 @@ def parse_get_post_vars(request, environ):
# parse POST variables on POST, PUT, BOTH only in post_vars
try:
- request.body = copystream_progress(request) ### stores request body
+ request.body = body = copystream_progress(request)
except IOError:
raise HTTP(400,"Bad Request - HTTP body is incomplete")
- if (request.body and request.env.request_method in ('POST', 'PUT', 'BOTH')):
- dpost = cgi.FieldStorage(fp=request.body,environ=environ,keep_blank_values=1)
+ if (body and request.env.request_method in ('POST', 'PUT', 'BOTH')):
+ dpost = cgi.FieldStorage(fp=body,environ=environ,keep_blank_values=1)
# The same detection used by FieldStorage to detect multipart POSTs
is_multipart = dpost.type[:10] == 'multipart/'
- request.body.seek(0)
+ body.seek(0)
isle25 = sys.version_info[1] <= 5
def listify(a):
@@ -357,9 +366,10 @@ def wsgibase(environ, responder):
request = Request()
response = Response()
session = Session()
- request.env.web2py_path = global_settings.applications_parent
- request.env.web2py_version = web2py_version
- request.env.update(global_settings)
+ env = request.env
+ env.web2py_path = global_settings.applications_parent
+ env.web2py_version = web2py_version
+ env.update(global_settings)
static_file = False
try:
try:
@@ -373,79 +383,80 @@ def wsgibase(environ, responder):
# serve file if static
# ##################################################
- if not environ.get('PATH_INFO',None) and \
- environ.get('REQUEST_URI',None):
- # for fcgi, get path_info and query_string from request_uri
+ eget = environ.get
+ if not eget('PATH_INFO',None) and eget('REQUEST_URI',None):
+ # for fcgi, get path_info and
+ # query_string from request_uri
items = environ['REQUEST_URI'].split('?')
environ['PATH_INFO'] = items[0]
if len(items) > 1:
environ['QUERY_STRING'] = items[1]
else:
environ['QUERY_STRING'] = ''
- if not environ.get('HTTP_HOST',None):
- environ['HTTP_HOST'] = '%s:%s' % (environ.get('SERVER_NAME'),
- environ.get('SERVER_PORT'))
+ if not eget('HTTP_HOST',None):
+ environ['HTTP_HOST'] = \
+ eget('SERVER_NAME')+':'+eget('SERVER_PORT')
+
+
+ (static_file, environ) = url_in(request, environ)
- (static_file, environ) = rewrite.url_in(request, environ)
if static_file:
- if environ.get('QUERY_STRING', '')[:10] == 'attachment':
- response.headers['Content-Disposition'] = 'attachment'
+ if environ.get('QUERY_STRING','').startswith(
+ 'attachment'):
+ response.headers['Content-Disposition'] \
+ = 'attachment'
response.stream(static_file, request=request)
# ##################################################
# fill in request items
# ##################################################
-
- http_host = request.env.http_host.split(':',1)[0]
-
- local_hosts = [http_host,'::1','127.0.0.1','::ffff:127.0.0.1']
+ app = request.application ## must go after url_in!
+
+ http_host = env.http_host.split(':',1)[0]
+ local_hosts = [http_host,'::1','127.0.0.1',
+ '::ffff:127.0.0.1']
if not global_settings.web2py_runtime_gae:
local_hosts.append(socket.gethostname())
- try: local_hosts.append(socket.gethostbyname(http_host))
- except socket.gaierror: pass
- request.client = get_client(request.env)
- if not is_valid_ip_address(request.client):
- raise HTTP(400,"Bad Request (request.client=%s)" % \
- request.client)
- request.folder = abspath('applications',
- request.application) + os.sep
- x_req_with = str(request.env.http_x_requested_with).lower()
- request.ajax = x_req_with == 'xmlhttprequest'
- request.cid = request.env.http_web2py_component_element
- request.is_local = request.env.remote_addr in local_hosts
- request.is_https = request.env.wsgi_url_scheme \
- in ['https', 'HTTPS'] or request.env.https == 'on'
-
- # ##################################################
- # compute a request.uuid to be used for tickets and toolbar
- # ##################################################
-
- response.uuid = request.compute_uuid()
+ try:
+ local_hosts.append(
+ socket.gethostbyname(http_host))
+ except socket.gaierror:
+ pass
+ client = get_client(env)
+ x_req_with = str(env.http_x_requested_with).lower()
+
+ request.update(dict(
+ client = client,
+ folder = abspath('applications',app) + os.sep,
+ ajax = x_req_with == 'xmlhttprequest',
+ cid = env.http_web2py_component_element,
+ is_local = env.remote_addr in local_hosts,
+ is_https = env.wsgi_url_scheme \
+ in ['https', 'HTTPS'] or env.https=='on'))
+ request.uuid = request.compute_uuid() # requires client
# ##################################################
# access the requested application
# ##################################################
- if not os.path.exists(request.folder):
- if request.application == \
- rewrite.thread.routes.default_application \
- and request.application != 'welcome':
- request.application = 'welcome'
- redirect(Url(r=request))
- elif rewrite.thread.routes.error_handler:
- _handler = rewrite.thread.routes.error_handler
- redirect(Url(_handler['application'],
+ if not exists(request.folder):
+ if app == rwthread.routes.default_application \
+ and app != 'welcome':
+ redirect(URL('welcome','default','index'))
+ elif rwthread.routes.error_handler:
+ _handler = rwthread.routes.error_handler
+ redirect(URL(_handler['application'],
_handler['controller'],
_handler['function'],
- args=request.application))
+ args=app))
else:
- raise HTTP(404, rewrite.thread.routes.error_message \
+ raise HTTP(404, rwthread.routes.error_message \
% 'invalid request',
web2py_error='invalid application')
elif not request.is_local and \
- os.path.exists(os.path.join(request.folder,'DISABLED')):
+ exists(pjoin(request.folder,'DISABLED')):
raise HTTP(503, "Temporarily down for maintenance
")
- request.url = Url(r=request,
+ request.url = URL(r=request,
args=request.args,
extension=request.raw_extension)
@@ -492,22 +503,23 @@ def wsgibase(environ, responder):
# ##################################################
# set no-cache headers
# ##################################################
-
- response.headers['Content-Type'] = \
+
+ headers = response.headers
+ headers['Content-Type'] = \
contenttype('.'+request.extension)
- response.headers['Cache-Control'] = \
+ headers['Cache-Control'] = \
'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
- response.headers['Expires'] = \
+ headers['Expires'] = \
time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime())
- response.headers['Pragma'] = 'no-cache'
+ headers['Pragma'] = 'no-cache'
# ##################################################
# run controller
# ##################################################
- if global_settings.debugging and request.application != "admin":
+ if global_settings.debugging and app != "admin":
import gluon.debug
- # activate the debugger and wait to reach application code
+ # activate the debugger
gluon.debug.dbg.do_debug(mainpyfile=request.folder)
serve_controller(request, response, session)
@@ -549,22 +561,24 @@ def wsgibase(environ, responder):
# ##################################################
if request.cid:
- if response.flash and not 'web2py-component-flash' \
- in http_response.headers:
- http_response.headers['web2py-component-flash'] = \
+ rheaders
+ if response.flash and \
+ not 'web2py-component-flash' in rheaders:
+ rheaders['web2py-component-flash'] = \
urllib2.quote(xmlescape(response.flash)\
.replace('\n',''))
- if response.js and not 'web2py-component-command' \
- in http_response.headers:
- http_response.headers['web2py-component-command'] = \
+ if response.js and \
+ not 'web2py-component-command' in readers:
+ readers['web2py-component-command'] = \
response.js.replace('\n','')
+ rcookies = response.cookies
if session._forget and \
response.session_id_name in response.cookies:
- del response.cookies[response.session_id_name]
+ del rcookies[response.session_id_name]
elif session._secure:
- response.cookies[response.session_id_name]['secure'] = True
+ rcookies[response.session_id_name]['secure'] = True
- http_response.cookies2headers(response.cookies)
+ http_response.cookies2headers(rcookies)
ticket=None
except RestrictedError, e:
@@ -583,7 +597,7 @@ def wsgibase(environ, responder):
BaseAdapter.close_all_instances('rollback')
http_response = \
- HTTP(500, rewrite.thread.routes.error_message_ticket % \
+ HTTP(500, rwthread.routes.error_message_ticket % \
dict(ticket=ticket),
web2py_error='ticket %s' % ticket)
@@ -606,7 +620,7 @@ def wsgibase(environ, responder):
e = RestrictedError('Framework', '', '', locals())
ticket = e.log(request) or 'unrecoverable'
http_response = \
- HTTP(500, rewrite.thread.routes.error_message_ticket \
+ HTTP(500, rwthread.routes.error_message_ticket \
% dict(ticket=ticket),
web2py_error='ticket %s' % ticket)
@@ -616,7 +630,7 @@ def wsgibase(environ, responder):
response.session_file.close()
session._unlock(response)
- http_response, new_environ = rewrite.try_rewrite_on_error(
+ http_response, new_environ = try_rewrite_on_error(
http_response, request, environ, ticket)
if not http_response:
return wsgibase(new_environ,responder)
@@ -641,7 +655,7 @@ def save_password(password, port):
print '*********************************************************'
elif password == '':
# reuse the current password if any
- if os.path.exists(password_file):
+ if exists(password_file):
return
else:
password = ''
@@ -672,9 +686,9 @@ def appfactory(wsgiapp=wsgibase,
[, profilerfilename='profiler.log']]])
"""
- if profilerfilename and os.path.exists(profilerfilename):
+ if profilerfilename and exists(profilerfilename):
os.unlink(profilerfilename)
- locker = thread.allocate_lock()
+ locker = allocate_lock()
def app_with_logging(environ, responder):
"""
@@ -785,7 +799,7 @@ class HttpServer(object):
os.chdir(path)
[add_path_first(p) for p in (path, abspath('site-packages'), "")]
custom_import_install(web2py_path)
- if os.path.exists("logging.conf"):
+ if exists("logging.conf"):
logging.config.fileConfig("logging.conf")
save_password(password, port)
@@ -800,9 +814,9 @@ class HttpServer(object):
logger.info('SSL is off')
elif not rocket.ssl:
logger.warning('Python "ssl" module unavailable. SSL is OFF')
- elif not os.path.exists(ssl_certificate):
+ elif not exists(ssl_certificate):
logger.warning('unable to open SSL certificate. SSL is OFF')
- elif not os.path.exists(ssl_private_key):
+ elif not exists(ssl_private_key):
logger.warning('unable to open SSL private key. SSL is OFF')
else:
sock_list.extend([ssl_private_key, ssl_certificate])
@@ -848,9 +862,3 @@ class HttpServer(object):
except:
pass
-
-
-
-
-
-
diff --git a/gluon/portalocker.py b/gluon/portalocker.py
index f516e73e..9d92cb4f 100644
--- a/gluon/portalocker.py
+++ b/gluon/portalocker.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# portalocker.py - Cross-platform (posix/nt) API for flock-style file locking.
+# portalocker.py
+# Cross-platform (posix/nt) API for flock-style file locking.
# Requires python 1.5.2 or better.
"""
@@ -141,6 +142,17 @@ class LockedFile(object):
def __del__(self):
self.close()
+def read_locked(filename):
+ fp = LockedFile(filename, 'r')
+ data = fp.read()
+ fp.close()
+ return data
+
+def write_locked(filename,data):
+ fp = LockedFile(filename, 'w')
+ data = fp.write(data)
+ fp.close()
+
if __name__=='__main__':
f = LockedFile('test.txt',mode='wb')
f.write('test ok')
diff --git a/gluon/restricted.py b/gluon/restricted.py
index 74f3b34c..bb5ba670 100644
--- a/gluon/restricted.py
+++ b/gluon/restricted.py
@@ -88,16 +88,19 @@ class TicketStorage(Storage):
ticket_id,
):
if not self.db:
- ef = self._error_file(request, ticket_id, 'rb', app)
+ try:
+ ef = self._error_file(request, ticket_id, 'rb', app)
+ except IOError:
+ return {}
try:
return cPickle.load(ef)
finally:
ef.close()
- table = self._get_table(self.db, self.tablename, app)
- rows = self.db(table.ticket_id == ticket_id).select()
- if rows:
- return cPickle.loads(rows[0].ticket_data)
- return None
+ else:
+ table = self._get_table(self.db, self.tablename, app)
+ rows = self.db(table.ticket_id == ticket_id).select()
+ return cPickle.loads(rows[0].ticket_data) if rows else {}
+
class RestrictedError(Exception):
@@ -164,10 +167,10 @@ class RestrictedError(Exception):
ticket_storage = TicketStorage(db=request.tickets_db)
d = ticket_storage.load(request, app, ticket_id)
- self.layer = d['layer']
- self.code = d['code']
- self.output = d['output']
- self.traceback = d['traceback']
+ self.layer = d.get('layer')
+ self.code = d.get('code')
+ self.output = d.get('output')
+ self.traceback = d.get('traceback')
self.snapshot = d.get('snapshot')
def __str__(self):
diff --git a/gluon/rewrite.py b/gluon/rewrite.py
index d7faf450..731cee67 100644
--- a/gluon/rewrite.py
+++ b/gluon/rewrite.py
@@ -27,9 +27,17 @@ from http import HTTP
from fileutils import abspath, read_file
from settings import global_settings
-logger = logging.getLogger('web2py.rewrite')
+isdir = os.path.isdir
+isfile = os.path.isfile
+exists = os.path.exists
+pjoin = os.path.join
-thread = threading.local() # thread-local storage for routing parameters
+logger = logging.getLogger('web2py.rewrite')
+thread = threading.local() # thread-local storage for routing params
+
+regex_at = re.compile(r'(?(.*)')
def _router_default():
"return new copy of default base router"
@@ -75,7 +83,7 @@ def _params_default(app=None):
params_apps = dict()
params = _params_default(app=None) # regex rewrite parameters
-thread.routes = params # default to base regex rewrite parameters
+thread.routes = params # default to base regex rewrite parameters
routers = None
def log_rewrite(string):
@@ -97,13 +105,18 @@ def log_rewrite(string):
else:
logger.debug(string)
-ROUTER_KEYS = set(('default_application', 'applications', 'default_controller', 'controllers',
- 'default_function', 'functions', 'default_language', 'languages',
- 'domain', 'domains', 'root_static', 'path_prefix',
- 'exclusive_domain', 'map_hyphen', 'map_static',
- 'acfe_match', 'file_match', 'args_match'))
+ROUTER_KEYS = set(
+ ('default_application', 'applications',
+ 'default_controller', 'controllers',
+ 'default_function', 'functions',
+ 'default_language', 'languages',
+ 'domain', 'domains', 'root_static', 'path_prefix',
+ 'exclusive_domain', 'map_hyphen', 'map_static',
+ 'acfe_match', 'file_match', 'args_match'))
-ROUTER_BASE_KEYS = set(('applications', 'default_application', 'domains', 'path_prefix'))
+ROUTER_BASE_KEYS = set(
+ ('applications', 'default_application',
+ 'domains', 'path_prefix'))
# The external interface to rewrite consists of:
#
@@ -223,9 +236,7 @@ def try_redirect_on_error(http_object, request, ticket=None):
(redir,status,ticket,
urllib.quote_plus(request.env.request_uri),
request.url)
- return HTTP(303,
- 'You are being redirected here' % url,
- Location=url)
+ return HTTP(303,'You are being redirected here' % url,Location=url)
return http_object
@@ -258,7 +269,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
path = abspath(routes)
else:
path = abspath('applications', app, routes)
- if not os.path.exists(path):
+ if not exists(path):
return
data = read_file(path).replace('\r\n','\n')
@@ -309,9 +320,11 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
# parse the app-specific routes.py if present
#
all_apps = []
- for appname in [app for app in os.listdir(abspath('applications')) if not app.startswith('.')]:
- if os.path.isdir(abspath('applications', appname)) and \
- os.path.isdir(abspath('applications', appname, 'controllers')):
+ apppath = abspath('applications')
+ for appname in os.listdir(apppath):
+ if not appname.startswith('.') and \
+ isdir(abspath(apppath,appname)) and \
+ isdir(abspath(apppath,appname,'controllers')):
all_apps.append(appname)
if routers:
router = Storage(routers.BASE) # new copy
@@ -321,7 +334,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
raise SyntaxError, "BASE-only key '%s' in router '%s'" % (key, appname)
router.update(routers[appname])
routers[appname] = router
- if os.path.exists(abspath('applications', appname, routes)):
+ if exists(abspath('applications', appname, routes)):
load(routes, appname)
if routers:
@@ -336,14 +349,9 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
log_rewrite('URL rewrite is on. configuration in %s' % path)
-regex_at = re.compile(r'(?(.*)')
-
def compile_regex(k, v):
"""
Preprocess and compile the regular expressions in routes_app/in/out
-
The resulting regex will match a pattern of the form:
[remote address]:[protocol]://[host]:[method] [path]
@@ -380,8 +388,9 @@ def compile_regex(k, v):
def load_routers(all_apps):
"load-time post-processing of routers"
- for app in routers.keys():
- # initialize apps with routers that aren't present, on behalf of unit tests
+ for app in routers:
+ # initialize apps with routers that aren't present,
+ # on behalf of unit tests
if app not in all_apps:
all_apps.append(app)
router = Storage(routers.BASE) # new copy
@@ -420,10 +429,10 @@ def load_routers(all_apps):
routers.BASE.domains[router.domain] = app
if isinstance(router.controllers, str) and router.controllers == 'DEFAULT':
router.controllers = set()
- if os.path.isdir(abspath('applications', app)):
+ if isdir(abspath('applications', app)):
cpath = abspath('applications', app, 'controllers')
for cname in os.listdir(cpath):
- if os.path.isfile(abspath(cpath, cname)) and cname.endswith('.py'):
+ if isfile(abspath(cpath, cname)) and cname.endswith('.py'):
router.controllers.add(cname[:-3])
if router.controllers:
router.controllers.add('static')
@@ -458,16 +467,20 @@ def load_routers(all_apps):
#
domains = dict()
if routers.BASE.domains:
- for (domain, app) in [(d.strip(':'), a.strip('/')) for (d, a) in routers.BASE.domains.items()]:
- port = None
+ for (d, a) in routers.BASE.domains.iteritems():
+ (domain, app) = (d.strip(':'), a.strip('/'))
if ':' in domain:
(domain, port) = domain.split(':')
- ctlr = None
- fcn = None
+ else:
+ port = None
if '/' in app:
(app, ctlr) = app.split('/', 1)
+ else:
+ ctlr = None
if ctlr and '/' in ctlr:
(ctlr, fcn) = ctlr.split('/')
+ else:
+ fcn = None
if app not in all_apps and app not in routers:
raise SyntaxError, "unknown app '%s' in domains" % app
domains[(domain, port)] = (app, ctlr, fcn)
@@ -514,7 +527,8 @@ def regex_filter_in(e):
query = e.get('QUERY_STRING', None)
e['WEB2PY_ORIGINAL_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '')
if thread.routes.routes_in:
- path = regex_uri(e, thread.routes.routes_in, "routes_in", e['PATH_INFO'])
+ path = regex_uri(e, thread.routes.routes_in,
+ "routes_in", e['PATH_INFO'])
rmatch = regex_redirect.match(path)
if rmatch:
raise HTTP(int(rmatch.group(1)),location=rmatch.group(2))
@@ -581,6 +595,9 @@ regex_args = re.compile(r'''
/?$) # trailing slash
''', re.X)
+def sluggify(key):
+ return key.lower().replace('.','_')
+
def regex_url_in(request, environ):
"rewrite and parse incoming URL"
@@ -594,9 +611,8 @@ def regex_url_in(request, environ):
if thread.routes.routes_in:
environ = regex_filter_in(environ)
-
- for (key, value) in environ.items():
- request.env[key.lower().replace('.', '_')] = value
+
+ request.env.update((sluggify(k),v) for k,v in environ.iteritems())
path = request.env.path_info.replace('\\', '/')
@@ -606,7 +622,7 @@ def regex_url_in(request, environ):
match = regex_static.match(regex_space.sub('_', path))
if match and match.group('x'):
- static_file = os.path.join(request.env.applications_parent,
+ static_file = pjoin(request.env.applications_parent,
'applications', match.group('b'),
'static', match.group('x'))
return (static_file, environ)
@@ -908,7 +924,7 @@ class MapUrlIn(object):
'''
if len(self.args) == 1 and self.arg0 in self.router.root_static:
self.controller = self.request.controller = 'static'
- root_static_file = os.path.join(self.request.env.applications_parent,
+ root_static_file = pjoin(self.request.env.applications_parent,
'applications', self.application,
self.controller, self.arg0)
log_rewrite("route: root static=%s" % root_static_file)
@@ -962,7 +978,8 @@ class MapUrlIn(object):
bad_static = bad_static or name in ('', '.', '..') or not self.router._file_match.match(name)
if bad_static:
log_rewrite('bad static path=%s' % file)
- raise HTTP(400, thread.routes.error_message % 'invalid request',
+ raise HTTP(400,
+ thread.routes.error_message % 'invalid request',
web2py_error='invalid static file')
#
# support language-specific static subdirectories,
@@ -970,13 +987,13 @@ class MapUrlIn(object):
# if language-specific file doesn't exist, try same file in static
#
if self.language:
- static_file = os.path.join(self.request.env.applications_parent,
- 'applications', self.application,
- 'static', self.language, file)
- if not self.language or not os.path.isfile(static_file):
- static_file = os.path.join(self.request.env.applications_parent,
- 'applications', self.application,
- 'static', file)
+ static_file = pjoin(self.request.env.applications_parent,
+ 'applications', self.application,
+ 'static', self.language, file)
+ if not self.language or not isfile(static_file):
+ static_file = pjoin(self.request.env.applications_parent,
+ 'applications', self.application,
+ 'static', file)
log_rewrite("route: static=%s" % static_file)
return static_file
@@ -1040,12 +1057,14 @@ class MapUrlIn(object):
uri += '.' + self.extension
if self.language:
uri = '/%s%s' % (self.language, uri)
- uri = '/%s%s' % (app, uri)
- uri += self.args and urllib.quote('/' + '/'.join([str(x) for x in self.args])) or ''
- uri += (self.query and ('?' + self.query) or '')
+ uri = '/%s%s%s%s' % (
+ app,
+ uri,
+ urllib.quote('/'+'/'.join(str(x) for x in self.args)) if self.args else '',
+ ('?' + self.query) if self.query else '')
self.env['REQUEST_URI'] = uri
- for (key, value) in self.env.items():
- self.request.env[key.lower().replace('.', '_')] = value
+ self.request.env.update(
+ (sluggify(k),v) for k,v in self.env.iteritems())
@property
def arg0(self):
diff --git a/gluon/rocket.py b/gluon/rocket.py
index 12fd8dc6..43284a21 100644
--- a/gluon/rocket.py
+++ b/gluon/rocket.py
@@ -1407,7 +1407,7 @@ class Worker(Thread):
raise BadRequest
req = match.groupdict()
- for k,v in req.items():
+ for k,v in req.iteritems():
if not v:
req[k] = ""
if k == 'path':
@@ -1694,7 +1694,8 @@ class FileSystemWorker(Worker):
try:
# Get our file path
- headers = dict([(str(k.lower()), v) for k, v in self.read_headers(sock_file).items()])
+ reader = self.read_headers(sock_file)
+ headers = dict((k.lower(),v) for k,v in reader.iteritems())
rpath = request.get('path', '').lstrip('/')
filepath = os.path.join(self.root, rpath)
filepath = os.path.abspath(filepath)
diff --git a/gluon/settings.py b/gluon/settings.py
index 3715edc6..a8d23280 100644
--- a/gluon/settings.py
+++ b/gluon/settings.py
@@ -18,7 +18,8 @@ if not hasattr(os, 'mkdir'):
if global_settings.db_sessions is not True:
global_settings.db_sessions = set()
-global_settings.gluon_parent = os.environ.get('web2py_path', os.getcwd())
+global_settings.gluon_parent = \
+ os.environ.get('web2py_path', os.getcwd())
global_settings.applications_parent = global_settings.gluon_parent
@@ -26,12 +27,14 @@ global_settings.app_folders = set()
global_settings.debugging = False
-global_settings.is_pypy = hasattr(platform,'python_implementation') and \
- platform.python_implementation() == 'PyPy'
+global_settings.is_pypy = \
+ hasattr(platform,'python_implementation') and \
+ platform.python_implementation() == 'PyPy'
-global_settings.is_jython = 'java' in sys.platform.lower() or \
- hasattr(sys, 'JYTHON_JAR') or \
- str(sys.copyright).find('Jython') > 0
+global_settings.is_jython = \
+ 'java' in sys.platform.lower() or \
+ hasattr(sys, 'JYTHON_JAR') or \
+ str(sys.copyright).find('Jython') > 0
diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py
index b3df0aee..6f3b9220 100644
--- a/gluon/sqlhtml.py
+++ b/gluon/sqlhtml.py
@@ -36,6 +36,8 @@ import re
import cStringIO
from gluon import current, redirect, A, URL, DIV, H3, UL, LI, SPAN, INPUT
import inspect
+import settings
+is_gae = settings.global_settings.web2py_runtime_gae
table_field = re.compile('[\w_]+\.[\w_]+')
widget_class = re.compile('^\w*')
@@ -581,7 +583,7 @@ class AutocompleteWidget(object):
def callback(self):
if self.keyword in self.request.vars:
field = self.fields[0]
- if settings.global_settings.web2py_runtime_gae:
+ if is_gae:
rows = self.db(field.__ge__(self.request.vars[self.keyword])&field.__lt__(self.request.vars[self.keyword]+ u'\ufffd')).select(orderby=self.orderby,limitby=self.limitby,*self.fields)
else:
rows = self.db(field.like(self.request.vars[self.keyword]+'%')).select(orderby=self.orderby,limitby=self.limitby,distinct=self.distinct,*self.fields)
@@ -1834,7 +1836,7 @@ class SQLFORM(FORM):
else:
rows = dbset.select(left=left,orderby=orderby,*columns)
- if exportManager.has_key(export_type):
+ if export_type in exportManager:
value = exportManager[export_type]
clazz = value[0] if hasattr(value, '__getitem__') else value
oExp = clazz(rows)
diff --git a/gluon/template.py b/gluon/template.py
index 9f147895..a8481338 100644
--- a/gluon/template.py
+++ b/gluon/template.py
@@ -15,10 +15,10 @@ Contributors:
"""
import os
-import re
import cgi
import cStringIO
import logging
+from re import compile, sub, escape, DOTALL
try:
# have web2py
from restricted import RestrictedError
@@ -57,6 +57,18 @@ class SuperNode(Node):
def __repr__(self):
return "%s->%s" % (self.name, self.value)
+def output_aux(node,blocks):
+ # If we have a block level
+ # If we can override this block.
+ # Override block from vars.
+ # Else we take the default
+ # Else its just a string
+ return (blocks[node.name].output(blocks)
+ if node.name in blocks else
+ node.output(blocks)) \
+ if isinstance(node, BlockNode) \
+ else str(node)
+
class BlockNode(Node):
"""
Block Container.
@@ -81,8 +93,7 @@ class BlockNode(Node):
def __repr__(self):
lines = ['%sblock %s%s' % (self.left,self.name,self.right)]
- for node in self.nodes:
- lines.append(str(node))
+ lines += [str(node) for node in self.nodes]
lines.append('%send%s' % (self.left, self.right))
return ''.join(lines)
@@ -90,11 +101,8 @@ class BlockNode(Node):
"""
Get this BlockNodes content, not including child Nodes
"""
- lines = []
- for node in self.nodes:
- if not isinstance(node, BlockNode):
- lines.append(str(node))
- return ''.join(lines)
+ return ''.join(str(node) for node in self.nodes \
+ if not isinstance(node, BlockNode))
def append(self, node):
"""
@@ -122,30 +130,14 @@ class BlockNode(Node):
else:
raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other)
+
def output(self, blocks):
"""
Merges all nodes into a single string.
-
blocks -- Dictionary of blocks that are extending
from this template.
"""
- lines = []
- # Get each of our nodes
- for node in self.nodes:
- # If we have a block level node.
- if isinstance(node, BlockNode):
- # If we can override this block.
- if node.name in blocks:
- # Override block from vars.
- lines.append(blocks[node.name].output(blocks))
- # Else we take the default
- else:
- lines.append(node.output(blocks))
- # Else its just a string
- else:
- lines.append(str(node))
- # Now combine all of our lines together.
- return ''.join(lines)
+ return ''.join(output_aux(node,blocks) for node in self.nodes)
class Content(BlockNode):
"""
@@ -165,29 +157,13 @@ class Content(BlockNode):
self.pre_extend = pre_extend
def __str__(self):
- lines = []
- # For each of our nodes
- for node in self.nodes:
- # If it is a block node.
- if isinstance(node, BlockNode):
- # And the node has a name that corresponds with a block in us
- if node.name in self.blocks:
- # Use the overriding output.
- lines.append(self.blocks[node.name].output(self.blocks))
- else:
- # Otherwise we just use the nodes output.
- lines.append(node.output(self.blocks))
- else:
- # It is just a string, so include it.
- lines.append(str(node))
- # Merge our list together.
- return ''.join(lines)
+ return ''.join(output_aux(node,self.blocks) for node in self.nodes)
def _insert(self, other, index = 0):
"""
Inserts object at index.
"""
- if isinstance(other, str) or isinstance(other, Node):
+ if isinstance(other, (str, Node)):
self.nodes.insert(index, other)
else:
raise TypeError("Invalid type, must be instance of ``str`` or ``Node``.")
@@ -201,8 +177,7 @@ class Content(BlockNode):
if isinstance(other, (list, tuple)):
# Must reverse so the order stays the same.
other.reverse()
- for item in other:
- self._insert(item, index)
+ (self._insert(item, index) for item in other)
else:
self._insert(other, index)
@@ -210,7 +185,7 @@ class Content(BlockNode):
"""
Adds a node to list. If it is a BlockNode then we assign a block for it.
"""
- if isinstance(node, str) or isinstance(node, Node):
+ if isinstance(node, (str, Node)):
self.nodes.append(node)
if isinstance(node, BlockNode):
self.blocks[node.name] = node
@@ -233,18 +208,18 @@ class Content(BlockNode):
class TemplateParser(object):
default_delimiters = ('{{','}}')
- r_tag = re.compile(r'(\{\{.*?\}\})', re.DOTALL)
+ r_tag = compile(r'(\{\{.*?\}\})', DOTALL)
- r_multiline = re.compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', re.DOTALL)
+ r_multiline = compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', DOTALL)
# These are used for re-indentation.
# Indent + 1
- re_block = re.compile('^(elif |else:|except:|except |finally:).*$',
- re.DOTALL)
+ re_block = compile('^(elif |else:|except:|except |finally:).*$',DOTALL)
+
# Indent - 1
- re_unblock = re.compile('^(return|continue|break|raise)( .*)?$', re.DOTALL)
+ re_unblock = compile('^(return|continue|break|raise)( .*)?$', DOTALL)
# Indent - 1
- re_pass = re.compile('^pass( .*)?$', re.DOTALL)
+ re_pass = compile('^pass( .*)?$', DOTALL)
def __init__(self, text,
name = "ParserContainer",
@@ -291,13 +266,16 @@ class TemplateParser(object):
# allow optional alternative delimiters
self.delimiters = delimiters
if delimiters != self.default_delimiters:
- escaped_delimiters = (re.escape(delimiters[0]),re.escape(delimiters[1]))
- self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters, re.DOTALL)
- elif context.has_key('response') and hasattr(context['response'],'delimiters'):
+ escaped_delimiters = (escape(delimiters[0]),
+ escape(delimiters[1]))
+ self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, DOTALL)
+ elif hasattr(context.get('response',None),'delimiters'):
if context['response'].delimiters != self.default_delimiters:
- escaped_delimiters = (re.escape(context['response'].delimiters[0]),
- re.escape(context['response'].delimiters[1]))
- self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters,re.DOTALL)
+ escaped_delimiters = (
+ escape(context['response'].delimiters[0]),
+ escape(context['response'].delimiters[1]))
+ self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters,
+ DOTALL)
# Create a root level Content that everything will go into.
self.content = Content(name=name)
@@ -524,17 +502,19 @@ class TemplateParser(object):
# the parent nodes.
self.content.nodes = []
+ t_content = t.content
+
# Set our include, unique by filename
- t.content.blocks['__include__' + filename] = buf
+ t_content.blocks['__include__' + filename] = buf
# Make sure our pre_extended nodes go first
- t.content.insert(pre)
+ t_content.insert(pre)
# Then we extend our blocks
- t.content.extend(self.content)
+ t_content.extend(self.content)
# Work off the parent node.
- self.content = t.content
+ self.content = t_content
def parse(self, text):
@@ -553,69 +533,20 @@ class TemplateParser(object):
ij = self.r_tag.split(text)
# j = current index
# i = current item
+ stack = self.stack
for j in range(len(ij)):
i = ij[j]
if i:
- if len(self.stack) == 0:
+ if not stack:
self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag')
# Our current element in the stack.
- top = self.stack[-1]
+ top = stack[-1]
if in_tag:
line = i
- # If we are missing any strings!!!!
- # This usually happens with the following example
- # template code
- #
- # {{a = '}}'}}
- # or
- # {{a = '}}blahblah{{'}}
- #
- # This will fix these
- # This is commented out because the current template
- # system has this same limitation. Since this has a
- # performance hit on larger templates, I do not recommend
- # using this code on production systems. This is still here
- # for "i told you it *can* be fixed" purposes.
- #
- #
-# if line.count("'") % 2 != 0 or line.count('"') % 2 != 0:
-#
-# # Look ahead
-# la = 1
-# nextline = ij[j+la]
-#
-# # As long as we have not found our ending
-# # brackets keep going
-# while '}}' not in nextline:
-# la += 1
-# nextline += ij[j+la]
-# # clear this line, so we
-# # don't attempt to parse it
-# # this is why there is an "if i"
-# # around line 530
-# ij[j+la] = ''
-#
-# # retrieve our index.
-# index = nextline.index('}}')
-#
-# # Everything before the new brackets
-# before = nextline[:index+2]
-#
-# # Everything after
-# after = nextline[index+2:]
-#
-# # Make the next line everything after
-# # so it parses correctly, this *should* be
-# # all html
-# ij[j+1] = after
-#
-# # Add everything before to the current line
-# line += before
-
# Get rid of '{{' and '}}'
line = line[2:-2].strip()
@@ -633,9 +564,9 @@ class TemplateParser(object):
# Perform block comment escaping.
# This performs escaping ON anything
# in between """ and """
- line = re.sub(TemplateParser.r_multiline,
- remove_newline,
- line)
+ line = sub(TemplateParser.r_multiline,
+ remove_newline,
+ line)
if line.startswith('='):
# IE: {{=response.title}}
@@ -672,7 +603,7 @@ class TemplateParser(object):
self.lexers[name](parser = self,
value = value,
top = top,
- stack = self.stack,)
+ stack = stack)
elif name == '=':
# So we have a variable to insert into
@@ -693,7 +624,7 @@ class TemplateParser(object):
# so anything after this gets added
# to this node. This allows us to
# "nest" nodes.
- self.stack.append(node)
+ stack.append(node)
elif name == 'end' and not value.startswith('='):
# We are done with this node.
@@ -702,7 +633,7 @@ class TemplateParser(object):
self.blocks[top.name] = top
# Pop it.
- self.stack.pop()
+ stack.pop()
elif name == 'super' and not value.startswith('='):
# Get our correct target name
@@ -757,17 +688,17 @@ class TemplateParser(object):
# So we can properly put a response.write() in place.
continuation = False
len_parsed = 0
- for k in range(len(tokens)):
+ for k, token in enumerate(tokens):
- tokens[k] = tokens[k].strip()
- len_parsed += len(tokens[k])
+ token = tokens[k] = token.strip()
+ len_parsed += len(token)
- if tokens[k].startswith('='):
- if tokens[k].endswith('\\'):
+ if token.startswith('='):
+ if token.endswith('\\'):
continuation = True
- tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip())
+ tokens[k] = "\n%s(%s" % (self.writer, token[1:].strip())
else:
- tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip())
+ tokens[k] = "\n%s(%s)" % (self.writer, token[1:].strip())
elif continuation:
tokens[k] += ')'
continuation = False
@@ -912,7 +843,7 @@ def render(content = "hello world",
# Add it to the context so we can use it.
if not 'NOESCAPE' in context:
- context['NOESCAPE'] = XML
+ context['NOESCAPE'] = NOESCAPE
# save current response class
if context and 'response' in context:
diff --git a/gluon/tests/test_dal.py b/gluon/tests/test_dal.py
index 9d5c085c..79e35c26 100644
--- a/gluon/tests/test_dal.py
+++ b/gluon/tests/test_dal.py
@@ -129,6 +129,26 @@ class TestFields(unittest.TestCase):
)
self.assertEqual(db.t.insert(a=t0), 1)
self.assertEqual(db().select(db.t.a)[0].a, t0)
+
+ ## Row APIs
+ row = db().select(db.t.a)[0]
+ self.assertEqual(db.t[1].a,t0)
+ self.assertEqual(db.t['a'],db.t.a)
+ self.assertEqual(db.t(1).a,t0)
+ self.assertTrue(db.t(1,a=None)==None)
+ self.assertFalse(db.t(1,a=t0)==None)
+ self.assertEqual(row.a,t0)
+ self.assertEqual(row['a'],t0)
+ self.assertEqual(row['t.a'],t0)
+ self.assertEqual(row('t.a'),t0)
+
+ ## Lazy and Virtual fields
+ db.t.b = Field.Virtual(lambda row: row.t.a)
+ db.t.c = Field.Lazy(lambda row: row.t.a)
+ row = db().select(db.t.a)[0]
+ self.assertEqual(row.b,t0)
+ self.assertEqual(row.c(),t0)
+
db.t.drop()
db.define_table('t', Field('a', 'time', default='11:30'))
t0 = datetime.time(10, 30, 55)
@@ -369,6 +389,17 @@ class TestJoin(unittest.TestCase):
db.t1.drop()
db.t2.drop()
+ db.define_table('person',Field('name'))
+ id = db.person.insert(name="max")
+ self.assertEqual(id.name,'max')
+ db.define_table('dog',Field('name'),Field('owner','reference person'))
+ db.dog.insert(name='skipper',owner=1)
+ row = db(db.person.id==db.dog.owner).select().first()
+ self.assertEqual(row[db.person.name],'max')
+ self.assertEqual(row['person.name'],'max')
+ db.dog.drop()
+ self.assertEqual(len(db.person._referenced_by),0)
+ db.person.drop()
class TestMinMaxSum(unittest.TestCase):
diff --git a/gluon/tests/test_languages.py b/gluon/tests/test_languages.py
index 7bf30d71..bcff8de4 100644
--- a/gluon/tests/test_languages.py
+++ b/gluon/tests/test_languages.py
@@ -17,6 +17,7 @@ import languages
import tempfile
import threading
import logging
+from storage import Storage
try:
import multiprocessing
@@ -53,6 +54,39 @@ try:
for result in results:
self.assertTrue(result)
+
+ class TestTranslations(unittest.TestCase):
+
+ def setUp(self):
+ self.request = Storage()
+ self.request.folder = 'applications/welcome'
+ self.request.env = Storage()
+ self.request.env.http_accept_language = 'en'
+
+
+ def tearDown(self):
+ pass
+
+ def test_plain(self):
+ T = languages.translator(self.request)
+ self.assertEqual(str(T('Hello World')),
+ 'Hello World')
+ self.assertEqual(str(T('Hello World## comment')),
+ 'Hello World')
+ self.assertEqual(str(T('%s %%{shop}', 1)),
+ '1 shop')
+ self.assertEqual(str(T('%s %%{shop}', 2)),
+ '2 shops')
+ self.assertEqual(str(T('%s %%{shop[0]}', 1)),
+ '1 shop')
+ self.assertEqual(str(T('%s %%{shop[0]}', 2)),
+ '2 shops')
+ self.assertEqual(str(T.M('**Hello World**')),
+ 'Hello World')
+ T.force('it')
+ self.assertEqual(str(T('Hello World')),
+ 'Salve Mondo')
+
except ImportError:
logging.warning("Skipped test case, no multiprocessing module.")
diff --git a/gluon/tools.py b/gluon/tools.py
index ce086c9d..e51e3d33 100644
--- a/gluon/tools.py
+++ b/gluon/tools.py
@@ -31,6 +31,7 @@ from utils import web2py_uuid
from fileutils import read_file, check_credentials
from gluon import *
from gluon.contrib.autolinks import expand_one
+from gluon.dal import Row
import serializers
@@ -798,6 +799,141 @@ def addrow(form, a, b, c, style, _id, position=-1):
class Auth(object):
+
+ default_settings = {
+ 'hideerror': False,
+ 'password_min_length': 4,
+ 'cas_maps': None,
+ 'reset_password_requires_verification': False,
+ 'registration_requires_verification': False,
+ 'registration_requires_approval': False,
+ 'login_after_registration': False,
+ 'login_after_password_change': True,
+ 'alternate_requires_registration': False,
+ 'create_user_groups': "user_%(id)s",
+ 'everybody_group_id': None,
+ 'login_captcha': None,
+ 'register_captcha': None,
+ 'retrieve_username_captcha': None,
+ 'retrieve_password_captcha': None,
+ 'captcha': None,
+ 'expiration': 3600, # one hour
+ 'long_expiration': 3600*30*24, # one month
+ 'remember_me_form': True,
+ 'allow_basic_login': False,
+ 'allow_basic_login_only': False,
+ 'on_failed_authentication': lambda x: redirect(x),
+ 'formstyle': 'table3cols',
+ 'label_separator': ': ',
+ 'password_field': 'password',
+ 'table_user_name': 'auth_user',
+ 'table_group_name': 'auth_group',
+ 'table_membership_name': 'auth_membership',
+ 'table_permission_name': 'auth_permission',
+ 'table_event_name': 'auth_event',
+ 'table_cas_name': 'auth_cas',
+ 'table_user': None,
+ 'table_group': None,
+ 'table_membership': None,
+ 'table_permission': None,
+ 'table_event': None,
+ 'table_cas': None,
+ 'showid': False,
+ 'login_email_validate': True,
+ 'login_userfield': None,
+ 'logout_onlogout': None,
+ 'register_fields': None,
+ 'register_verify_password': True,
+ 'profile_fields': None,
+ 'email_case_sensitive': True,
+ 'username_case_sensitive': True,
+ }
+ # ## these are messages that can be customized
+ default_messages = {
+ 'login_button': 'Login',
+ 'register_button': 'Register',
+ 'password_reset_button': 'Request reset password',
+ 'password_change_button': 'Change password',
+ 'profile_save_button': 'Save profile',
+ 'submit_button': 'Submit',
+ 'verify_password': 'Verify Password',
+ 'delete_label': 'Check to delete',
+ 'function_disabled': 'Function disabled',
+ 'access_denied': 'Insufficient privileges',
+ 'registration_verifying': 'Registration needs verification',
+ 'registration_pending': 'Registration is pending approval',
+ 'login_disabled': 'Login disabled by administrator',
+ 'logged_in': 'Logged in',
+ 'email_sent': 'Email sent',
+ 'unable_to_send_email': 'Unable to send email',
+ 'email_verified': 'Email verified',
+ 'logged_out': 'Logged out',
+ 'registration_successful': 'Registration successful',
+ 'invalid_email': 'Invalid email',
+ 'unable_send_email': 'Unable to send email',
+ 'invalid_login': 'Invalid login',
+ 'invalid_user': 'Invalid user',
+ 'invalid_password': 'Invalid password',
+ 'is_empty': "Cannot be empty",
+ 'mismatched_password': "Password fields don't match",
+ 'verify_email': 'Click on the link %(link)s to verify your email',
+ 'verify_email_subject': 'Email verification',
+ 'username_sent': 'Your username was emailed to you',
+ 'new_password_sent': 'A new password was emailed to you',
+ 'password_changed': 'Password changed',
+ 'retrieve_username': 'Your username is: %(username)s',
+ 'retrieve_username_subject': 'Username retrieve',
+ 'retrieve_password': 'Your password is: %(password)s',
+ 'retrieve_password_subject': 'Password retrieve',
+ 'reset_password': \
+ 'Click on the link %(link)s to reset your password',
+ 'reset_password_subject': 'Password reset',
+ 'invalid_reset_password': 'Invalid reset password',
+ 'profile_updated': 'Profile updated',
+ 'new_password': 'New password',
+ 'old_password': 'Old password',
+ 'group_description': 'Group uniquely assigned to user %(id)s',
+ 'register_log': 'User %(id)s Registered',
+ 'login_log': 'User %(id)s Logged-in',
+ 'login_failed_log': None,
+ 'logout_log': 'User %(id)s Logged-out',
+ 'profile_log': 'User %(id)s Profile updated',
+ 'verify_email_log': 'User %(id)s Verification email sent',
+ 'retrieve_username_log': 'User %(id)s Username retrieved',
+ 'retrieve_password_log': 'User %(id)s Password retrieved',
+ 'reset_password_log': 'User %(id)s Password reset',
+ 'change_password_log': 'User %(id)s Password changed',
+ 'add_group_log': 'Group %(group_id)s created',
+ 'del_group_log': 'Group %(group_id)s deleted',
+ 'add_membership_log': None,
+ 'del_membership_log': None,
+ 'has_membership_log': None,
+ 'add_permission_log': None,
+ 'del_permission_log': None,
+ 'has_permission_log': None,
+ 'impersonate_log': 'User %(id)s is impersonating %(other_id)s',
+ 'label_first_name': 'First name',
+ 'label_last_name': 'Last name',
+ 'label_username': 'Username',
+ 'label_email': 'E-mail',
+ 'label_password': 'Password',
+ 'label_registration_key': 'Registration key',
+ 'label_reset_password_key': 'Reset Password key',
+ 'label_registration_id': 'Registration identifier',
+ 'label_role': 'Role',
+ 'label_description': 'Description',
+ 'label_user_id': 'User ID',
+ 'label_group_id': 'Group ID',
+ 'label_name': 'Name',
+ 'label_table_name': 'Object or table name',
+ 'label_record_id': 'Record ID',
+ 'label_time_stamp': 'Timestamp',
+ 'label_client_ip': 'Client IP',
+ 'label_origin': 'Origin',
+ 'label_remember_me': "Remember me (for 30 days)",
+ 'verify_password_comment': 'please input your password again',
+ }
+
"""
Class for authentication, authorization, role based access control.
@@ -894,10 +1030,11 @@ class Auth(object):
open(filename,'w').write(key)
return key
- def url(self, f=None, args=None, vars=None):
+ def url(self, f=None, args=None, vars=None, scheme=False):
if args is None: args=[]
if vars is None: vars={}
- return URL(c=self.settings.controller, f=f, args=args, vars=vars)
+ return URL(c=self.settings.controller,
+ f=f, args=args, vars=vars,scheme=scheme)
def here(self):
return URL(args=current.request.args,vars=current.request.vars)
@@ -933,221 +1070,67 @@ class Auth(object):
else:
self.user = None
session.auth = None
- settings = self.settings = Settings()
-
# ## what happens after login?
self.next = current.request.vars._next
if isinstance(self.next,(list,tuple)):
self.next = self.next[0]
-
+ url_index = URL(controller,'index')
+ url_login = URL(controller,function,args='login')
# ## what happens after registration?
-
- settings.hideerror = False
- settings.password_min_length = 4
- settings.cas_domains = [request.env.http_host]
- settings.cas_provider = cas_provider
- settings.cas_actions = {'login':'login',
- 'validate':'validate',
- 'servicevalidate':'serviceValidate',
- 'proxyvalidate':'proxyValidate',
- 'logout':'logout'}
- settings.cas_maps = None
- settings.extra_fields = {}
- settings.actions_disabled = []
- settings.reset_password_requires_verification = False
- settings.registration_requires_verification = False
- settings.registration_requires_approval = False
- settings.login_after_registration = False
- settings.alternate_requires_registration = False
- settings.create_user_groups = "user_%(id)s"
- settings.everybody_group_id = None
-
- settings.controller = controller
- settings.function = function
- settings.login_url = self.url(function, args='login')
- settings.logged_url = self.url(function, args='profile')
- settings.download_url = self.url('download')
- settings.mailer = (mailer==True) and Mail() or mailer
- settings.login_captcha = None
- settings.register_captcha = None
- settings.retrieve_username_captcha = None
- settings.retrieve_password_captcha = None
- settings.captcha = None
- settings.expiration = 3600 # one hour
- settings.long_expiration = 3600*30*24 # one month
- settings.remember_me_form = True
- settings.allow_basic_login = False
- settings.allow_basic_login_only = False
- settings.on_failed_authorization = \
- self.url(function, args='not_authorized')
-
- settings.on_failed_authentication = lambda x: redirect(x)
-
- settings.formstyle = 'table3cols'
- settings.label_separator = ': '
-
- # ## table names to be used
-
- settings.password_field = 'password'
- settings.table_user_name = 'auth_user'
- settings.table_group_name = 'auth_group'
- settings.table_membership_name = 'auth_membership'
- settings.table_permission_name = 'auth_permission'
- settings.table_event_name = 'auth_event'
- settings.table_cas_name = 'auth_cas'
-
- # ## if none, they will be created, unless DAL(lazy_tables=True)!!!
-
- settings.table_user = None
- settings.table_group = None
- settings.table_membership = None
- settings.table_permission = None
- settings.table_event = None
- settings.table_cas = None
-
- # ##
-
- settings.showid = False
-
- # ## these should be functions or lambdas
-
- settings.login_next = self.url('index')
- settings.login_onvalidation = []
- settings.login_onaccept = []
- settings.login_methods = [self]
- settings.login_form = self
- settings.login_email_validate = True
- settings.login_userfield = None
-
- settings.logout_next = self.url('index')
- settings.logout_onlogout = None
-
- settings.register_next = self.url('index')
- settings.register_onvalidation = []
- settings.register_onaccept = []
- settings.register_fields = None
- settings.register_verify_password = True
-
- settings.verify_email_next = self.url(function, args='login')
- settings.verify_email_onaccept = []
-
- settings.profile_next = self.url('index')
- settings.profile_onvalidation = []
- settings.profile_onaccept = []
- settings.profile_fields = None
- settings.retrieve_username_next = self.url('index')
- settings.retrieve_password_next = self.url('index')
- settings.request_reset_password_next = self.url(function, args='login')
- settings.reset_password_next = self.url(function, args='login')
-
- settings.change_password_next = self.url('index')
- settings.change_password_onvalidation = []
- settings.change_password_onaccept = []
-
- settings.retrieve_password_onvalidation = []
- settings.reset_password_onvalidation = []
- settings.reset_password_onaccept = []
-
- settings.email_case_sensitive = True
- settings.username_case_sensitive = True
-
- settings.hmac_key = hmac_key
+
+ settings = self.settings = Settings()
+ settings.update(Auth.default_settings)
+ settings.update({
+ 'cas_domains': [request.env.http_host],
+ 'cas_provider': cas_provider,
+ 'cas_actions': {'login':'login',
+ 'validate':'validate',
+ 'servicevalidate':'serviceValidate',
+ 'proxyvalidate':'proxyValidate',
+ 'logout':'logout'},
+ 'extra_fields': {},
+ 'actions_disabled': [],
+ 'controller': controller,
+ 'function': function,
+ 'login_url': url_login,
+ 'logged_url': URL(controller, function, args='profile'),
+ 'download_url': URL(controller,'download'),
+ 'mailer': (mailer==True) and Mail() or mailer,
+ 'on_failed_authorization': \
+ URL(controller,function, args='not_authorized'),
+ 'login_next': url_index,
+ 'login_onvalidation': [],
+ 'login_onaccept': [],
+ 'login_methods': [self],
+ 'login_form': self,
+ 'logout_next': url_index,
+ 'logout_onlogout': None,
+ 'register_next': url_index,
+ 'register_onvalidation': [],
+ 'register_onaccept': [],
+ 'verify_email_next': url_login,
+ 'verify_email_onaccept': [],
+ 'profile_next': url_index,
+ 'profile_onvalidation': [],
+ 'profile_onaccept': [],
+ 'retrieve_username_next': url_index,
+ 'retrieve_password_next': url_index,
+ 'request_reset_password_next': url_login,
+ 'reset_password_next': url_index,
+ 'change_password_next': url_index,
+ 'change_password_onvalidation': [],
+ 'change_password_onaccept': [],
+ 'retrieve_password_onvalidation': [],
+ 'reset_password_onvalidation': [],
+ 'reset_password_onaccept': [],
+ 'hmac_key': hmac_key,
+ })
settings.lock_keys = True
# ## these are messages that can be customized
messages = self.messages = Messages(current.T)
- messages.login_button = 'Login'
- messages.register_button = 'Register'
- messages.password_reset_button = 'Request reset password'
- messages.password_change_button = 'Change password'
- messages.profile_save_button = 'Save profile'
- messages.submit_button = 'Submit'
- messages.verify_password = 'Verify Password'
- messages.delete_label = 'Check to delete'
- messages.function_disabled = 'Function disabled'
- messages.access_denied = 'Insufficient privileges'
- messages.registration_verifying = 'Registration needs verification'
- messages.registration_pending = 'Registration is pending approval'
- messages.login_disabled = 'Login disabled by administrator'
- messages.logged_in = 'Logged in'
- messages.email_sent = 'Email sent'
- messages.unable_to_send_email = 'Unable to send email'
- messages.email_verified = 'Email verified'
- messages.logged_out = 'Logged out'
- messages.registration_successful = 'Registration successful'
- messages.invalid_email = 'Invalid email'
- messages.unable_send_email = 'Unable to send email'
- messages.invalid_login = 'Invalid login'
- messages.invalid_user = 'Invalid user'
- messages.invalid_password = 'Invalid password'
- messages.is_empty = "Cannot be empty"
- messages.mismatched_password = "Password fields don't match"
- messages.verify_email = \
- 'Click on the link ' + \
- URL('default','user',args='verify_email',scheme=True) + \
- '/%(key)s to verify your email'
- messages.verify_email_subject = 'Email verification'
- messages.username_sent = 'Your username was emailed to you'
- messages.new_password_sent = 'A new password was emailed to you'
- messages.password_changed = 'Password changed'
- messages.retrieve_username = 'Your username is: %(username)s'
- messages.retrieve_username_subject = 'Username retrieve'
- messages.retrieve_password = 'Your password is: %(password)s'
- messages.retrieve_password_subject = 'Password retrieve'
- messages.reset_password = \
- 'Click on the link ' + \
- URL('default','user',args='reset_password',scheme=True) + \
- '/%(key)s to reset your password'
- messages.reset_password_subject = 'Password reset'
- messages.invalid_reset_password = 'Invalid reset password'
- messages.profile_updated = 'Profile updated'
- messages.new_password = 'New password'
- messages.old_password = 'Old password'
- messages.group_description = \
- 'Group uniquely assigned to user %(id)s'
-
- messages.register_log = 'User %(id)s Registered'
- messages.login_log = 'User %(id)s Logged-in'
- messages.login_failed_log = None
- messages.logout_log = 'User %(id)s Logged-out'
- messages.profile_log = 'User %(id)s Profile updated'
- messages.verify_email_log = 'User %(id)s Verification email sent'
- messages.retrieve_username_log = 'User %(id)s Username retrieved'
- messages.retrieve_password_log = 'User %(id)s Password retrieved'
- messages.reset_password_log = 'User %(id)s Password reset'
- messages.change_password_log = 'User %(id)s Password changed'
- messages.add_group_log = 'Group %(group_id)s created'
- messages.del_group_log = 'Group %(group_id)s deleted'
- messages.add_membership_log = None
- messages.del_membership_log = None
- messages.has_membership_log = None
- messages.add_permission_log = None
- messages.del_permission_log = None
- messages.has_permission_log = None
- messages.impersonate_log = 'User %(id)s is impersonating %(other_id)s'
-
- messages.label_first_name = 'First name'
- messages.label_last_name = 'Last name'
- messages.label_username = 'Username'
- messages.label_email = 'E-mail'
- messages.label_password = 'Password'
- messages.label_registration_key = 'Registration key'
- messages.label_reset_password_key = 'Reset Password key'
- messages.label_registration_id = 'Registration identifier'
- messages.label_role = 'Role'
- messages.label_description = 'Description'
- messages.label_user_id = 'User ID'
- messages.label_group_id = 'Group ID'
- messages.label_name = 'Name'
- messages.label_table_name = 'Object or table name'
- messages.label_record_id = 'Record ID'
- messages.label_time_stamp = 'Timestamp'
- messages.label_client_ip = 'Client IP'
- messages.label_origin = 'Origin'
- messages.label_remember_me = "Remember me (for 30 days)"
- messages['T'] = current.T
- messages.verify_password_comment = 'please input your password again'
+ messages.update(Auth.default_messages)
messages.lock_keys = True
# for "remember me" option
@@ -1336,37 +1319,39 @@ class Auth(object):
settings = self.settings
request = current.request
T = current.T
- def lazy_user (auth = self): return auth.user_id
reference_user = 'reference %s' % settings.table_user_name
+ def lazy_user (auth = self):
+ return auth.user_id
def represent(id,record=None,s=settings):
try:
user = s.table_user(id)
return '%(first_name)s %(last_name)s' % user
- except: return id
- self.signature = db.Table(self.db,'auth_signature',
- Field('is_active','boolean',
- default=True,
- readable=False, writable=False,
- label=T('Is Active')),
- Field('created_on','datetime',
- default=request.now,
- writable=False, readable=False,
- label=T('Created On')),
- Field('created_by',
- reference_user,
- default=lazy_user, represent=represent,
- writable=False, readable=False,
- label=T('Created By')),
- Field('modified_on','datetime',
- update=request.now,default=request.now,
- writable=False,readable=False,
- label=T('Modified On')),
- Field('modified_by',
- reference_user,represent=represent,
- default=lazy_user,update=lazy_user,
- writable=False,readable=False,
- label=T('Modified By')))
-
+ except:
+ return id
+ self.signature = db.Table(
+ self.db,'auth_signature',
+ Field('is_active','boolean',
+ default=True,
+ readable=False, writable=False,
+ label=T('Is Active')),
+ Field('created_on','datetime',
+ default=request.now,
+ writable=False, readable=False,
+ label=T('Created On')),
+ Field('created_by',
+ reference_user,
+ default=lazy_user, represent=represent,
+ writable=False, readable=False,
+ label=T('Created By')),
+ Field('modified_on','datetime',
+ update=request.now,default=request.now,
+ writable=False,readable=False,
+ label=T('Modified On')),
+ Field('modified_by',
+ reference_user,represent=represent,
+ default=lazy_user,update=lazy_user,
+ writable=False,readable=False,
+ label=T('Modified By')))
def define_tables(self, username=False, signature=None,
migrate=True, fake_migrate=False):
@@ -1396,8 +1381,7 @@ class Auth(object):
elif isinstance(signature,self.db.Table):
signature_list = [signature]
else:
- signature_list = signature
- lazy_tables, db._lazy_tables = db._lazy_tables, False
+ signature_list = signature
is_not_empty = IS_NOT_EMPTY(error_message=self.messages.is_empty)
is_crypted = CRYPT(key=settings.hmac_key,
min_length=settings.password_min_length)
@@ -1600,7 +1584,6 @@ class Auth(object):
actions=actions,
maps=maps)
-
def log_event(self, description, vars=None, origin='auth'):
"""
usage:
@@ -1667,6 +1650,11 @@ class Auth(object):
return user
def basic(self):
+ """
+ perform basic login.
+ reads current.request.env.http_authorization
+ and returns basic_allowed,basic_accepted,user
+ """
if not self.settings.allow_basic_login:
return (False,False,False)
basic = current.request.env.http_authorization
@@ -1675,11 +1663,23 @@ class Auth(object):
(username, password) = base64.b64decode(basic[6:]).split(':')
return (True, True, self.login_bare(username, password))
+ def login_user(self,user):
+ """
+ login the user = db.auth_user(id)
+ """
+ # user=Storage(self.table_user()._filter_fields(user,id=True))
+ current.session.auth = Storage(
+ user = user,
+ last_visit = current.request.now,
+ expiration = self.settings.expiration,
+ hmac_key = web2py_uuid())
+ self.user = user
+ self.update_groups()
+
def login_bare(self, username, password):
"""
- logins user
+ logins user as specified by usernname (or email) and password
"""
-
request = current.request
session = current.session
table_user = self.table_user()
@@ -1694,12 +1694,7 @@ class Auth(object):
if user and user.get(passfield,False):
password = table_user[passfield].validate(password)[0]
if not user.registration_key and password == user[passfield]:
- user = Storage(table_user._filter_fields(user, id=True))
- session.auth = Storage(user=user, last_visit=request.now,
- expiration=self.settings.expiration,
- hmac_key = web2py_uuid())
- self.user = user
- self.update_groups()
+ self.login_user(user)
return user
else:
# user not in database try other login methods
@@ -1738,15 +1733,15 @@ class Auth(object):
renew=interactivelogin)
service = session._cas_service
del session._cas_service
- if request.vars.has_key('warn') and not interactivelogin:
+ if 'warn' in request.vars and not interactivelogin:
response.headers['refresh'] = "5;URL=%s"%service+"?ticket="+ticket
return A("Continue to %s"%service,
_href=service+"?ticket="+ticket)
else:
redirect(service+"?ticket="+ticket)
- if self.is_logged_in() and not request.vars.has_key('renew'):
+ if self.is_logged_in() and not 'renew' in request.vars:
return allow_access()
- elif not self.is_logged_in() and request.vars.has_key('gateway'):
+ elif not self.is_logged_in() and 'gateway' in request.vars:
redirect(service)
def cas_onaccept(form, onaccept=onaccept):
if not onaccept is DEFAULT: onaccept(form)
@@ -1759,7 +1754,7 @@ class Auth(object):
db, table = self.db, self.table_cas()
current.response.headers['Content-Type']='text'
ticket = request.vars.ticket
- renew = True if request.vars.has_key('renew') else False
+ renew = 'renew' in request.vars
row = table(ticket=ticket)
success = False
if row:
@@ -1973,25 +1968,18 @@ class Auth(object):
# process authenticated users
if user:
- user = Storage(table_user._filter_fields(user, id=True))
-
+ user = Row(table_user._filter_fields(user, id=True))
# process authenticated users
# user wants to be logged in for longer
- session.auth = Storage(
- user = user,
- last_visit = request.now,
- expiration = request.vars.get("remember",False) and \
- self.settings.long_expiration or self.settings.expiration,
- remember = request.vars.has_key("remember"),
- hmac_key = web2py_uuid()
- )
-
- self.user = user
+ self.login_user(user)
+ session.auth.expiration = \
+ request.vars.get('remember',False) and \
+ self.settings.long_expiration or \
+ self.settings.expiration
+ session.auth.remember = 'remember' in request.vars
self.log_event(log, user)
session.flash = self.messages.logged_in
- self.update_groups()
-
# how to continue
if self.settings.login_form == self:
if accepted_form:
@@ -2131,11 +2119,14 @@ class Auth(object):
if self.settings.everybody_group_id:
self.add_membership(self.settings.everybody_group_id, form.vars.id)
if self.settings.registration_requires_verification:
+ link = self.url('user',args=('verify_email',key),scheme=True)
+
if not self.settings.mailer or \
- not self.settings.mailer.send(to=form.vars.email,
- subject=self.messages.verify_email_subject,
- message=self.messages.verify_email
- % dict(key=key)):
+ not self.settings.mailer.send(
+ to=form.vars.email,
+ subject=self.messages.verify_email_subject,
+ message=self.messages.verify_email \
+ % dict(key=key,link=link)):
self.db.rollback()
response.flash = self.messages.unable_send_email
return form
@@ -2149,13 +2140,10 @@ class Auth(object):
if not self.settings.registration_requires_verification:
table_user[form.vars.id] = dict(registration_key='')
session.flash = self.messages.registration_successful
- user = self.db(table_user[username] == form.vars[username]).select().first()
- user = Storage(table_user._filter_fields(user, id=True))
- session.auth = Storage(user=user, last_visit=request.now,
- expiration=self.settings.expiration,
- hmac_key = web2py_uuid())
- self.user = user
- self.update_groups()
+ user = self.db(
+ table_user[username] == form.vars[username]
+ ).select().first()
+ self.login_user(user)
session.flash = self.messages.logged_in
self.log_event(log, form.vars)
callback(onaccept,form)
@@ -2192,7 +2180,7 @@ class Auth(object):
key = getarg(-1)
table_user = self.table_user()
- user = self.db(table_user.registration_key == key).select().first()
+ user = table_user(registration_key=key)
if not user:
redirect(self.settings.login_url)
if self.settings.registration_requires_approval:
@@ -2267,7 +2255,7 @@ class Auth(object):
if form.accepts(request, session,
formname='retrieve_username', dbio=False,
onvalidation=onvalidation,hideerror=self.settings.hideerror):
- user = self.db(table_user.email == form.vars.email).select().first()
+ user = table_user(email=form.vars.email)
if not user:
current.session.flash = \
self.messages.invalid_email
@@ -2345,7 +2333,7 @@ class Auth(object):
if form.accepts(request, session,
formname='retrieve_password', dbio=False,
onvalidation=onvalidation,hideerror=self.settings.hideerror):
- user = self.db(table_user.email == form.vars.email).select().first()
+ user = table_user(email=form.vars.email)
if not user:
current.session.flash = \
self.messages.invalid_email
@@ -2398,12 +2386,12 @@ class Auth(object):
session = current.session
if next is DEFAULT:
- next = self.next or self.settings.reset_password_next
+ next = self.settings.reset_password_next
try:
key = request.vars.key or getarg(-1)
t0 = int(key.split('-')[0])
if time.time()-t0 > 60*60*24: raise Exception
- user = self.db(table_user.reset_password_key == key).select().first()
+ user = table_user(reset_password_key=key)
if not user: raise Exception
except Exception:
session.flash = self.messages.invalid_reset_password
@@ -2422,11 +2410,15 @@ class Auth(object):
formstyle=self.settings.formstyle,
separator=self.settings.label_separator
)
- if form.accepts(request,session,hideerror=self.settings.hideerror):
- user.update_record(**{passfield:str(form.vars.new_password),
- 'registration_key':'',
- 'reset_password_key':''})
+ if form.accepts(request,session,
+ hideerror=self.settings.hideerror):
+ user.update_record(
+ **{passfield:str(form.vars.new_password),
+ 'registration_key':'',
+ 'reset_password_key':''})
session.flash = self.messages.password_changed
+ if self.settings.login_after_password_change:
+ self.login_user(user)
redirect(next)
return form
@@ -2481,7 +2473,7 @@ class Auth(object):
formname='reset_password', dbio=False,
onvalidation=onvalidation,
hideerror=self.settings.hideerror):
- user = self.db(table_user.email == form.vars.email).select().first()
+ user = table_user(email=form.vars.email)
if not user:
session.flash = self.messages.invalid_email
redirect(self.url(args=request.args))
@@ -2504,11 +2496,14 @@ class Auth(object):
def email_reset_password(self,user):
reset_password_key = str(int(time.time()))+'-' + web2py_uuid()
+ link = self.url('user',
+ args=('reset_password',reset_password_key),
+ scheme=True)
if self.settings.mailer.send(
to=user.email,
subject=self.messages.reset_password_subject,
message=self.messages.reset_password % \
- dict(key=reset_password_key)):
+ dict(key=reset_password_key,link=link)):
user.update_record(reset_password_key=reset_password_key)
return True
return False
@@ -2684,7 +2679,8 @@ class Auth(object):
self.user = auth.user
if self.settings.login_onaccept:
form = Storage(dict(vars=self.user))
- self.settings.login_onaccept(form)
+ for callback in self.settings.login_onaccept:
+ callback(form)
log = self.messages.impersonate_log
self.log_event(log,dict(id=current_id, other_id=auth.user.id))
elif user_id in (0, '0') and self.is_impersonating():
@@ -3142,18 +3138,21 @@ class Auth(object):
elif form.record and fieldname in form.record:
new_record[fieldname]=form.record[fieldname]
if fields:
- for key,value in fields.items():
- new_record[key] = value
+ new_record.update(fields)
id = archive_table.insert(**new_record)
return id
- def wiki(self,slug=None,env=None,manage_permissions=False,force_prefix=''):
+
+ def wiki(self,slug=None,env=None,manage_permissions=False,force_prefix='', resolve=True):
if not hasattr(self,'_wiki'):
self._wiki = Wiki(self,
manage_permissions=manage_permissions,
force_prefix=force_prefix,env=env)
else:
self._wiki.env.update(env or {})
- return self._wiki.read(slug)['content'] if slug else self._wiki()
+ # if resolve is set to True, process request as wiki call
+ # resolve=False allows initial setup without wiki redirection
+ if resolve:
+ return self._wiki.read(slug)['content'] if slug else self._wiki()
class Crud(object):
@@ -3714,14 +3713,14 @@ def universal_caller(f, *a, **b):
# There might be pos_args left, that are sent as named_values. Gather them as well.
# If a argument already is populated with values we simply replaces them.
for arg_name in pos_args[len(arg_dict):]:
- if b.has_key(arg_name):
+ if arg_name in b:
arg_dict[arg_name] = b[arg_name]
if len(arg_dict) >= len(pos_args):
# All the positional arguments is found. The function may now be called.
# However, we need to update the arg_dict with the values from the named arguments as well.
for arg_name in named_args:
- if b.has_key(arg_name):
+ if arg_name in b:
arg_dict[arg_name] = b[arg_name]
return f(**arg_dict)
@@ -4136,7 +4135,7 @@ class Service(object):
prefix='pys',
documentation = documentation,
ns = True)
- for method, (function, returns, args, doc) in procedures.items():
+ for method, (function, returns, args, doc) in procedures.iteritems():
dispatcher.register_function(method, function, returns, args, doc)
if request.env.request_method == 'POST':
# Process normal Soap Operation
@@ -4392,8 +4391,9 @@ class PluginManager(object):
self.__dict__.clear()
settings = self.__getattr__(plugin)
settings.installed = True
- [settings.update({key:value}) for key,value in defaults.items() \
- if not key in settings]
+ settings.update(
+ (k,v) for k,v in defaults.items() if not k in settings)
+
def __getattr__(self, key):
if not key in self.__dict__:
self.__dict__[key] = Storage()
@@ -4494,32 +4494,48 @@ class Wiki(object):
self.host = current.request.env.http_host
perms = self.manage_permissions = manage_permissions
db = auth.db
- db.define_table(
- 'wiki_page',
- Field('slug',
- requires=[IS_SLUG(),IS_NOT_IN_DB(db,'wiki_page.slug')],
- readable=False,writable=False),
- Field('title',unique=True),
- Field('body','text',notnull=True),
- Field('tags','list:string'),
- Field('can_read','list:string',writable=perms,readable=perms,
- default=[Wiki.everybody]),
- Field('can_edit','list:string',writable=perms,readable=perms,
- default=[Wiki.everybody]),
- Field('changelog'),
- Field('html','text',readable=False,writable=False,compute=render),
- auth.signature,format='%(title)s')
- db.define_table(
- 'wiki_tag',
- Field('name'),
- Field('wiki_page',db.wiki_page),
- auth.signature,format='%(name)s')
- db.define_table(
- 'wiki_media',
- Field('wiki_page',db.wiki_page),
- Field('title',required=True),
- Field('file','upload',required=True),
- auth.signature,format='%(title)s')
+ table_definitions = {
+ 'wiki_page':{
+ 'args':[
+ Field('slug',
+ requires=[IS_SLUG(),
+ IS_NOT_IN_DB(db,'wiki_page.slug')],
+ readable=False,writable=False),
+ Field('title',unique=True),
+ Field('body','text',notnull=True),
+ Field('tags','list:string'),
+ Field('can_read','list:string',
+ writable=perms,
+ readable=perms,
+ default=[Wiki.everybody]),
+ Field('can_edit', 'list:string',
+ writable=perms,readable=perms,
+ default=[Wiki.everybody]),
+ Field('changelog'),
+ Field('html','text',compute=render,
+ readable=False, writable=False),
+ auth.signature],
+ 'vars':{'format':'%(title)s'}},
+ 'wiki_tag':{
+ 'args':[
+ Field('name'),
+ Field('wiki_page','reference wiki_page'),
+ auth.signature],
+ 'vars':{'format':'%(name)s'}},
+ 'wiki_media':{
+ 'args':[
+ Field('wiki_page','reference wiki_page'),
+ Field('title',required=True),
+ Field('file','upload',required=True),
+ auth.signature],
+ 'vars':{'format':'%(title)s'}}
+ }
+
+ # define only non-existent tables
+ for key, value in table_definitions.iteritems():
+ if not key in db.tables():
+ db.define_table(key, *value['args'], **value['vars'])
+
def update_tags_insert(page,id,db=db):
for tag in page.tags or []:
tag = tag.strip().lower()