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]))}} ] -

+ + + + {{pass}} +
+ {{=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}} {{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="
t
";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>");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>");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=/)<[^<]*)*<\/script>/gi,bA=/^(?:select|textarea)/i,bB=/\s+/,bC=/([?&])_=[^&]*/,bD=/(^|\-)([a-z])/g,bE=function(a,b,c){return b+c.toUpperCase()},bF=/^([\w\+\.\-]+:)\/\/([^\/?#:]*)(?::(\d+))?/,bG=d.fn.load,bH={},bI={},bJ,bK;try{bJ=c.location.href}catch(bL){bJ=c.createElement("a"),bJ.href="",bJ=bJ.href}bK=bF.exec(bJ.toLowerCase()),d.fn.extend({load:function(a,c,e){if(typeof a!=="string"&&bG)return bG.apply(this,arguments);if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var g=a.slice(f,a.length);a=a.slice(0,f)}var h="GET";c&&(d.isFunction(c)?(e=c,c=b):typeof c==="object"&&(c=d.param(c,d.ajaxSettings.traditional),h="POST"));var i=this;d.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?d("
").append(c.replace(bz,"")).find(g):c)),e&&i.each(e,[c,b,a])}});return this},serialize:function(){return d.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?d.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bA.test(this.nodeName)||bu.test(this.type))}).map(function(a,b){var c=d(this).val();return c==null?null:d.isArray(c)?d.map(c,function(a,c){return{name:b.name,value:a.replace(br,"\r\n")}}):{name:b.name,value:c.replace(br,"\r\n")}}).get()}}),d.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){d.fn[b]=function(a){return this.bind(b,a)}}),d.each(["get","post"],function(a,c){d[c]=function(a,e,f,g){d.isFunction(e)&&(g=g||f,f=e,e=b);return d.ajax({type:c,url:a,data:e,success:f,dataType:g})}}),d.extend({getScript:function(a,c){return d.get(a,b,c,"script")},getJSON:function(a,b,c){return d.get(a,b,c,"json")},ajaxSetup:function(a,b){b?d.extend(!0,a,d.ajaxSettings,b):(b=a,a=d.extend(!0,d.ajaxSettings,b));for(var c in {context:1,url:1})c in b?a[c]=b[c]:c in d.ajaxSettings&&(a[c]=d.ajaxSettings[c]);return a},ajaxSettings:{url:bJ,isLocal:bv.test(bK[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":d.parseJSON,"text xml":d.parseXML}},ajaxPrefilter:bM(bH),ajaxTransport:bM(bI),ajax:function(a,c){function v(a,c,l,n){if(r!==2){r=2,p&&clearTimeout(p),o=b,m=n||"",u.readyState=a?4:0;var q,t,v,w=l?bP(e,u,l):b,x,y;if(a>=200&&a<300||a===304){if(e.ifModified){if(x=u.getResponseHeader("Last-Modified"))d.lastModified[k]=x;if(y=u.getResponseHeader("Etag"))d.etag[k]=y}if(a===304)c="notmodified",q=!0;else try{t=bQ(e,w),c="success",q=!0}catch(z){c="parsererror",v=z}}else{v=c;if(!c||a)c="error",a<0&&(a=0)}u.status=a,u.statusText=c,q?h.resolveWith(f,[t,c,u]):h.rejectWith(f,[u,c,v]),u.statusCode(j),j=b,s&&g.trigger("ajax"+(q?"Success":"Error"),[u,e,q?t:v]),i.resolveWith(f,[u,c]),s&&(g.trigger("ajaxComplete",[u,e]),--d.active||d.event.trigger("ajaxStop"))}}typeof a==="object"&&(c=a,a=b),c=c||{};var e=d.ajaxSetup({},c),f=e.context||e,g=f!==e&&(f.nodeType||f instanceof d)?d(f):d.event,h=d.Deferred(),i=d._Deferred(),j=e.statusCode||{},k,l={},m,n,o,p,q,r=0,s,t,u={readyState:0,setRequestHeader:function(a,b){r||(l[a.toLowerCase().replace(bD,bE)]=b);return this},getAllResponseHeaders:function(){return r===2?m:null},getResponseHeader:function(a){var c;if(r===2){if(!n){n={};while(c=bt.exec(m))n[c[1].toLowerCase()]=c[2]}c=n[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){r||(e.mimeType=a);return this},abort:function(a){a=a||"abort",o&&o.abort(a),v(0,a);return this}};h.promise(u),u.success=u.done,u.error=u.fail,u.complete=i.done,u.statusCode=function(a){if(a){var b;if(r<2)for(b in a)j[b]=[j[b],a[b]];else b=a[u.status],u.then(b,b)}return this},e.url=((a||e.url)+"").replace(bs,"").replace(bx,bK[1]+"//"),e.dataTypes=d.trim(e.dataType||"*").toLowerCase().split(bB),e.crossDomain||(q=bF.exec(e.url.toLowerCase()),e.crossDomain=q&&(q[1]!=bK[1]||q[2]!=bK[2]||(q[3]||(q[1]==="http:"?80:443))!=(bK[3]||(bK[1]==="http:"?80:443)))),e.data&&e.processData&&typeof e.data!=="string"&&(e.data=d.param(e.data,e.traditional)),bN(bH,e,c,u);if(r===2)return!1;s=e.global,e.type=e.type.toUpperCase(),e.hasContent=!bw.test(e.type),s&&d.active++===0&&d.event.trigger("ajaxStart");if(!e.hasContent){e.data&&(e.url+=(by.test(e.url)?"&":"?")+e.data),k=e.url;if(e.cache===!1){var w=d.now(),x=e.url.replace(bC,"$1_="+w);e.url=x+(x===e.url?(by.test(e.url)?"&":"?")+"_="+w:"")}}if(e.data&&e.hasContent&&e.contentType!==!1||c.contentType)l["Content-Type"]=e.contentType;e.ifModified&&(k=k||e.url,d.lastModified[k]&&(l["If-Modified-Since"]=d.lastModified[k]),d.etag[k]&&(l["If-None-Match"]=d.etag[k])),l.Accept=e.dataTypes[0]&&e.accepts[e.dataTypes[0]]?e.accepts[e.dataTypes[0]]+(e.dataTypes[0]!=="*"?", */*; q=0.01":""):e.accepts["*"];for(t in e.headers)u.setRequestHeader(t,e.headers[t]);if(e.beforeSend&&(e.beforeSend.call(f,u,e)===!1||r===2)){u.abort();return!1}for(t in {success:1,error:1,complete:1})u[t](e[t]);o=bN(bI,e,c,u);if(o){u.readyState=1,s&&g.trigger("ajaxSend",[u,e]),e.async&&e.timeout>0&&(p=setTimeout(function(){u.abort("timeout")},e.timeout));try{r=1,o.send(l,v)}catch(y){status<2?v(-1,y):d.error(y)}}else v(-1,"No Transport");return u},param:function(a,c){var e=[],f=function(a,b){b=d.isFunction(b)?b():b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=d.ajaxSettings.traditional);if(d.isArray(a)||a.jquery&&!d.isPlainObject(a))d.each(a,function(){f(this.name,this.value)});else for(var g in a)bO(g,a[g],c,f);return e.join("&").replace(bp,"+")}}),d.extend({active:0,lastModified:{},etag:{}});var bR=d.now(),bS=/(\=)\?(&|$)|()\?\?()/i;d.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return d.expando+"_"+bR++}}),d.ajaxPrefilter("json jsonp",function(b,c,e){var f=typeof b.data==="string";if(b.dataTypes[0]==="jsonp"||c.jsonpCallback||c.jsonp!=null||b.jsonp!==!1&&(bS.test(b.url)||f&&bS.test(b.data))){var g,h=b.jsonpCallback=d.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2",m=function(){a[h]=i,g&&d.isFunction(i)&&a[h](g[0])};b.jsonp!==!1&&(j=j.replace(bS,l),b.url===j&&(f&&(k=k.replace(bS,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},e.then(m,m),b.converters["script json"]=function(){g||d.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),d.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){d.globalEval(a);return a}}}),d.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),d.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var bT=d.now(),bU,bV;d.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&bX()||bY()}:bX,bV=d.ajaxSettings.xhr(),d.support.ajax=!!bV,d.support.cors=bV&&"withCredentials"in bV,bV=b,d.support.ajax&&d.ajaxTransport(function(a){if(!a.crossDomain||d.support.cors){var c;return{send:function(e,f){var g=a.xhr(),h,i;a.username?g.open(a.type,a.url,a.async,a.username,a.password):g.open(a.type,a.url,a.async);if(a.xhrFields)for(i in a.xhrFields)g[i]=a.xhrFields[i];a.mimeType&&g.overrideMimeType&&g.overrideMimeType(a.mimeType),(!a.crossDomain||a.hasContent)&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(i in e)g.setRequestHeader(i,e[i])}catch(j){}g.send(a.hasContent&&a.data||null),c=function(e,i){var j,k,l,m,n;try{if(c&&(i||g.readyState===4)){c=b,h&&(g.onreadystatechange=d.noop,delete bU[h]);if(i)g.readyState!==4&&g.abort();else{j=g.status,l=g.getAllResponseHeaders(),m={},n=g.responseXML,n&&n.documentElement&&(m.xml=n),m.text=g.responseText;try{k=g.statusText}catch(o){k=""}j||!a.isLocal||a.crossDomain?j===1223&&(j=204):j=m.text?200:404}}}catch(p){i||f(-1,p)}m&&f(j,k,m,l)},a.async&&g.readyState!==4?(bU||(bU={},bW()),h=bT++,g.onreadystatechange=bU[h]=c):c()},abort:function(){c&&c(0,1)}}}});var bZ={},b$=/^(?:toggle|show|hide)$/,b_=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,ca,cb=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];d.fn.extend({show:function(a,b,c){var e,f;if(a||a===0)return this.animate(cc("show",3),a,b,c);for(var g=0,h=this.length;g=0;a--)c[a].elem===this&&(b&&c[a](!0),c.splice(a,1))}),b||this.dequeue();return this}}),d.each({slideDown:cc("show",1),slideUp:cc("hide",1),slideToggle:cc("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){d.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),d.extend({speed:function(a,b,c){var e=a&&typeof a==="object"?d.extend({},a):{complete:c||!c&&b||d.isFunction(a)&&a,duration:a,easing:c&&b||b&&!d.isFunction(b)&&b};e.duration=d.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in d.fx.speeds?d.fx.speeds[e.duration]:d.fx.speeds._default,e.old=e.complete,e.complete=function(){e.queue!==!1&&d(this).dequeue(),d.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig||(b.orig={})}}),d.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(d.fx.step[this.prop]||d.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=d.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function g(a){return e.step(a)}var e=this,f=d.fx;this.startTime=d.now(),this.start=a,this.end=b,this.unit=c||this.unit||(d.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,g.elem=this.elem,g()&&d.timers.push(g)&&!ca&&(ca=setInterval(f.tick,f.interval))},show:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),d(this.elem).show()},hide:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=d.now(),c=!0;if(a||b>=this.options.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),this.options.curAnim[this.prop]=!0;for(var e in this.options.curAnim)this.options.curAnim[e]!==!0&&(c=!1);if(c){if(this.options.overflow!=null&&!d.support.shrinkWrapBlocks){var f=this.elem,g=this.options;d.each(["","X","Y"],function(a,b){f.style["overflow"+b]=g.overflow[a]})}this.options.hide&&d(this.elem).hide();if(this.options.hide||this.options.show)for(var h in this.options.curAnim)d.style(this.elem,h,this.options.orig[h]);this.options.complete.call(this.elem)}return!1}var i=b-this.startTime;this.state=i/this.options.duration;var j=this.options.specialEasing&&this.options.specialEasing[this.prop],k=this.options.easing||(d.easing.swing?"swing":"linear");this.pos=d.easing[j||k](this.state,i,0,1,this.options.duration),this.now=this.start+(this.end-this.start)*this.pos,this.update();return!0}},d.extend(d.fx,{tick:function(){var a=d.timers;for(var b=0;b
";d.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),e=b.firstChild,f=e.firstChild,h=e.nextSibling.firstChild.firstChild,this.doesNotAddBorder=f.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,f.style.position="fixed",f.style.top="20px",this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15,f.style.position=f.style.top="",e.style.overflow="hidden",e.style.position="relative",this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),a=b=e=f=g=h=null,d.offset.initialize=d.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;d.offset.initialize(),d.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(d.css(a,"marginTop"))||0,c+=parseFloat(d.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var e=d.css(a,"position");e==="static"&&(a.style.position="relative");var f=d(a),g=f.offset(),h=d.css(a,"top"),i=d.css(a,"left"),j=e==="absolute"&&d.inArray("auto",[h,i])>-1,k={},l={},m,n;j&&(l=f.position()),m=j?l.top:parseInt(h,10)||0,n=j?l.left:parseInt(i,10)||0,d.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):f.css(k)}},d.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),e=cf.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(d.css(a,"marginTop"))||0,c.left-=parseFloat(d.css(a,"marginLeft"))||0,e.top+=parseFloat(d.css(b[0],"borderTopWidth"))||0,e.left+=parseFloat(d.css(b[0],"borderLeftWidth"))||0;return{top:c.top-e.top,left:c.left-e.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&(!cf.test(a.nodeName)&&d.css(a,"position")==="static"))a=a.offsetParent;return a})}}),d.each(["Left","Top"],function(a,c){var e="scroll"+c;d.fn[e]=function(c){var f=this[0],g;if(!f)return null;if(c!==b)return this.each(function(){g=cg(this),g?g.scrollTo(a?d(g).scrollLeft():c,a?c:d(g).scrollTop()):this[e]=c});g=cg(f);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:d.support.boxModel&&g.document.documentElement[e]||g.document.body[e]:f[e]}}),d.each(["Height","Width"],function(a,c){var e=c.toLowerCase();d.fn["inner"+c]=function(){return this[0]?parseFloat(d.css(this[0],e,"padding")):null},d.fn["outer"+c]=function(a){return this[0]?parseFloat(d.css(this[0],e,a?"margin":"border")):null},d.fn[e]=function(a){var f=this[0];if(!f)return a==null?null:this;if(d.isFunction(a))return this.each(function(b){var c=d(this);c[e](a.call(this,b,c[e]()))});if(d.isWindow(f)){var g=f.document.documentElement["client"+c];return f.document.compatMode==="CSS1Compat"&&g||f.document.body["client"+c]||g}if(f.nodeType===9)return Math.max(f.documentElement["client"+c],f.body["scroll"+c],f.documentElement["scroll"+c],f.body["offset"+c],f.documentElement["offset"+c]);if(a===b){var h=d.css(f,e),i=parseFloat(h);return d.isNaN(i)?h:i}return this.css(e,typeof a==="string"?a:a+"px")}}),a.jQuery=a.$=d})(window); diff --git a/applications/examples/static/js/jquery.prettyPhoto.js b/applications/examples/static/js/jquery.prettyPhoto.js deleted file mode 100644 index bc30642c..00000000 --- a/applications/examples/static/js/jquery.prettyPhoto.js +++ /dev/null @@ -1,69 +0,0 @@ -/* ------------------------------------------------------------------------ - Class: prettyPhoto - Use: Lightbox clone for jQuery - Author: Stephane Caron (http://www.no-margin-for-errors.com) - Version: 3.0.3 -------------------------------------------------------------------------- */ - -(function($){$.prettyPhoto={version:'3.0.2'};$.fn.prettyPhoto=function(pp_settings){pp_settings=jQuery.extend({animation_speed:'fast',slideshow:false,autoplay_slideshow:false,opacity:0.80,show_title:true,allow_resize:true,default_width:500,default_height:344,counter_separator_label:'/',theme:'facebook',hideflash:false,wmode:'opaque',autoplay:true,modal:false,overlay_gallery:true,keyboard_shortcuts:true,changepicturecallback:function(){},callback:function(){},markup:'
\ -
 
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ - Expand \ -
\ - next \ - previous \ -
\ -
\ -
\ -

\ - Close \ -
\ - Previous \ -

0/0

\ - Next \ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
',gallery_markup:'',image_markup:'',flash_markup:'',quicktime_markup:'',iframe_markup:'',inline_markup:'
{content}
',custom_markup:''},pp_settings);var matchedObjects=this,percentBased=false,pp_dimensions,pp_open,pp_contentHeight,pp_contentWidth,pp_containerHeight,pp_containerWidth,windowHeight=$(window).height(),windowWidth=$(window).width(),pp_slideshow;doresize=true,scroll_pos=_get_scroll();$(window).unbind('resize.prettyphoto').bind('resize.prettyphoto',function(){_center_overlay();_resize_overlay();});if(pp_settings.keyboard_shortcuts){$(document).unbind('keydown.prettyphoto').bind('keydown.prettyphoto',function(e){if(typeof $pp_pic_holder!='undefined'){if($pp_pic_holder.is(':visible')){switch(e.keyCode){case 37:$.prettyPhoto.changePage('previous');e.preventDefault();break;case 39:$.prettyPhoto.changePage('next');e.preventDefault();break;case 27:if(!settings.modal) -$.prettyPhoto.close();e.preventDefault();break;};};};});} -$.prettyPhoto.initialize=function(){settings=pp_settings;if($.browser.msie&&parseInt($.browser.version)==6)settings.theme="light_square";theRel=$(this).attr('rel');galleryRegExp=/\[(?:.*)\]/;isSet=(galleryRegExp.exec(theRel))?true:false;pp_images=(isSet)?jQuery.map(matchedObjects,function(n,i){if($(n).attr('rel').indexOf(theRel)!=-1)return $(n).attr('href');}):$.makeArray($(this).attr('href'));pp_titles=(isSet)?jQuery.map(matchedObjects,function(n,i){if($(n).attr('rel').indexOf(theRel)!=-1)return($(n).find('img').attr('alt'))?$(n).find('img').attr('alt'):"";}):$.makeArray($(this).find('img').attr('alt'));pp_descriptions=(isSet)?jQuery.map(matchedObjects,function(n,i){if($(n).attr('rel').indexOf(theRel)!=-1)return($(n).attr('title'))?$(n).attr('title'):"";}):$.makeArray($(this).attr('title'));_buildOverlay(this);if(settings.allow_resize) -$(window).bind('scroll.prettyphoto',function(){_center_overlay();});set_position=jQuery.inArray($(this).attr('href'),pp_images);$.prettyPhoto.open();return false;} -$.prettyPhoto.open=function(event){if(typeof settings=="undefined"){settings=pp_settings;if($.browser.msie&&$.browser.version==6)settings.theme="light_square";pp_images=$.makeArray(arguments[0]);pp_titles=(arguments[1])?$.makeArray(arguments[1]):$.makeArray("");pp_descriptions=(arguments[2])?$.makeArray(arguments[2]):$.makeArray("");isSet=(pp_images.length>1)?true:false;set_position=0;_buildOverlay(event.target);} -if($.browser.msie&&$.browser.version==6)$('select').css('visibility','hidden');if(settings.hideflash)$('object,embed').css('visibility','hidden');_checkPosition($(pp_images).size());$('.pp_loaderIcon').show();if($ppt.is(':hidden'))$ppt.css('opacity',0).show();$pp_overlay.show().fadeTo(settings.animation_speed,settings.opacity);$pp_pic_holder.find('.currentTextHolder').text((set_position+1)+settings.counter_separator_label+$(pp_images).size());$pp_pic_holder.find('.pp_description').show().html(unescape(pp_descriptions[set_position]));(settings.show_title&&pp_titles[set_position]!=""&&typeof pp_titles[set_position]!="undefined")?$ppt.html(unescape(pp_titles[set_position])):$ppt.html(' ');movie_width=(parseFloat(grab_param('width',pp_images[set_position])))?grab_param('width',pp_images[set_position]):settings.default_width.toString();movie_height=(parseFloat(grab_param('height',pp_images[set_position])))?grab_param('height',pp_images[set_position]):settings.default_height.toString();if(movie_height.indexOf('%')!=-1){movie_height=parseFloat(($(window).height()*parseFloat(movie_height)/100)-150);percentBased=true;} -if(movie_width.indexOf('%')!=-1){movie_width=parseFloat(($(window).width()*parseFloat(movie_width)/100)-150);percentBased=true;} -$pp_pic_holder.fadeIn(function(){imgPreloader="";switch(_getFileType(pp_images[set_position])){case'image':imgPreloader=new Image();nextImage=new Image();if(isSet&&set_position<$(pp_images).size()-1)nextImage.src=pp_images[set_position+1];prevImage=new Image();if(isSet&&pp_images[set_position-1])prevImage.src=pp_images[set_position-1];$pp_pic_holder.find('#pp_full_res')[0].innerHTML=settings.image_markup.replace(/{path}/g,pp_images[set_position]);imgPreloader.onload=function(){pp_dimensions=_fitToViewport(imgPreloader.width,imgPreloader.height);_showContent();};imgPreloader.onerror=function(){alert('Image cannot be loaded. Make sure the path is correct and image exist.');$.prettyPhoto.close();};imgPreloader.src=pp_images[set_position];break;case'youtube':pp_dimensions=_fitToViewport(movie_width,movie_height);movie='http://www.youtube.com/v/'+grab_param('v',pp_images[set_position]);if(settings.autoplay)movie+="&autoplay=1";toInject=settings.flash_markup.replace(/{width}/g,pp_dimensions['width']).replace(/{height}/g,pp_dimensions['height']).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,movie);break;case'vimeo':pp_dimensions=_fitToViewport(movie_width,movie_height);movie_id=pp_images[set_position];var regExp=/http:\/\/(www\.)?vimeo.com\/(\d+)/;var match=movie_id.match(regExp);movie='http://player.vimeo.com/video/'+match[2]+'?title=0&byline=0&portrait=0';if(settings.autoplay)movie+="&autoplay=1;";vimeo_width=pp_dimensions['width']+'/embed/?moog_width='+pp_dimensions['width'];toInject=settings.iframe_markup.replace(/{width}/g,vimeo_width).replace(/{height}/g,pp_dimensions['height']).replace(/{path}/g,movie);break;case'quicktime':pp_dimensions=_fitToViewport(movie_width,movie_height);pp_dimensions['height']+=15;pp_dimensions['contentHeight']+=15;pp_dimensions['containerHeight']+=15;toInject=settings.quicktime_markup.replace(/{width}/g,pp_dimensions['width']).replace(/{height}/g,pp_dimensions['height']).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,pp_images[set_position]).replace(/{autoplay}/g,settings.autoplay);break;case'flash':pp_dimensions=_fitToViewport(movie_width,movie_height);flash_vars=pp_images[set_position];flash_vars=flash_vars.substring(pp_images[set_position].indexOf('flashvars')+10,pp_images[set_position].length);filename=pp_images[set_position];filename=filename.substring(0,filename.indexOf('?'));toInject=settings.flash_markup.replace(/{width}/g,pp_dimensions['width']).replace(/{height}/g,pp_dimensions['height']).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,filename+'?'+flash_vars);break;case'iframe':pp_dimensions=_fitToViewport(movie_width,movie_height);frame_url=pp_images[set_position];frame_url=frame_url.substr(0,frame_url.indexOf('iframe')-1);toInject=settings.iframe_markup.replace(/{width}/g,pp_dimensions['width']).replace(/{height}/g,pp_dimensions['height']).replace(/{path}/g,frame_url);break;case'custom':pp_dimensions=_fitToViewport(movie_width,movie_height);toInject=settings.custom_markup;break;case'inline':myClone=$(pp_images[set_position]).clone().css({'width':settings.default_width}).wrapInner('
').appendTo($('body')).show();doresize=false;pp_dimensions=_fitToViewport($(myClone).width(),$(myClone).height());doresize=true;$(myClone).remove();toInject=settings.inline_markup.replace(/{content}/g,$(pp_images[set_position]).html());break;};if(!imgPreloader){$pp_pic_holder.find('#pp_full_res')[0].innerHTML=toInject;_showContent();};});return false;};$.prettyPhoto.changePage=function(direction){currentGalleryPage=0;if(direction=='previous'){set_position--;if(set_position<0){set_position=0;return;};}else if(direction=='next'){set_position++;if(set_position>$(pp_images).size()-1){set_position=0;}}else{set_position=direction;};if(!doresize)doresize=true;$('.pp_contract').removeClass('pp_contract').addClass('pp_expand');_hideContent(function(){$.prettyPhoto.open();});};$.prettyPhoto.changeGalleryPage=function(direction){if(direction=='next'){currentGalleryPage++;if(currentGalleryPage>totalPage){currentGalleryPage=0;};}else if(direction=='previous'){currentGalleryPage--;if(currentGalleryPage<0){currentGalleryPage=totalPage;};}else{currentGalleryPage=direction;};itemsToSlide=(currentGalleryPage==totalPage)?pp_images.length-((totalPage)*itemsPerPage):itemsPerPage;$pp_pic_holder.find('.pp_gallery li').each(function(i){$(this).animate({'left':(i*itemWidth)-((itemsToSlide*itemWidth)*currentGalleryPage)});});};$.prettyPhoto.startSlideshow=function(){if(typeof pp_slideshow=='undefined'){$pp_pic_holder.find('.pp_play').unbind('click').removeClass('pp_play').addClass('pp_pause').click(function(){$.prettyPhoto.stopSlideshow();return false;});pp_slideshow=setInterval($.prettyPhoto.startSlideshow,settings.slideshow);}else{$.prettyPhoto.changePage('next');};} -$.prettyPhoto.stopSlideshow=function(){$pp_pic_holder.find('.pp_pause').unbind('click').removeClass('pp_pause').addClass('pp_play').click(function(){$.prettyPhoto.startSlideshow();return false;});clearInterval(pp_slideshow);pp_slideshow=undefined;} -$.prettyPhoto.close=function(){if($pp_overlay.is(":animated"))return;$.prettyPhoto.stopSlideshow();$pp_pic_holder.stop().find('object,embed').css('visibility','hidden');$('div.pp_pic_holder,div.ppt,.pp_fade').fadeOut(settings.animation_speed,function(){$(this).remove();});$pp_overlay.fadeOut(settings.animation_speed,function(){if($.browser.msie&&$.browser.version==6)$('select').css('visibility','visible');if(settings.hideflash)$('object,embed').css('visibility','visible');$(this).remove();$(window).unbind('scroll');settings.callback();doresize=true;pp_open=false;delete settings;});};function _showContent(){$('.pp_loaderIcon').hide();$ppt.fadeTo(settings.animation_speed,1);projectedTop=scroll_pos['scrollTop']+((windowHeight/2)-(pp_dimensions['containerHeight']/2));if(projectedTop<0)projectedTop=0;$pp_pic_holder.find('.pp_content').animate({height:pp_dimensions['contentHeight'],width:pp_dimensions['contentWidth']},settings.animation_speed);$pp_pic_holder.animate({'top':projectedTop,'left':(windowWidth/2)-(pp_dimensions['containerWidth']/2),width:pp_dimensions['containerWidth']},settings.animation_speed,function(){$pp_pic_holder.find('.pp_hoverContainer,#fullResImage').height(pp_dimensions['height']).width(pp_dimensions['width']);$pp_pic_holder.find('.pp_fade').fadeIn(settings.animation_speed);if(isSet&&_getFileType(pp_images[set_position])=="image"){$pp_pic_holder.find('.pp_hoverContainer').show();}else{$pp_pic_holder.find('.pp_hoverContainer').hide();} -if(pp_dimensions['resized']){$('a.pp_expand,a.pp_contract').show();}else{$('a.pp_expand,a.pp_contract').hide();} -if(settings.autoplay_slideshow&&!pp_slideshow&&!pp_open)$.prettyPhoto.startSlideshow();settings.changepicturecallback();pp_open=true;});_insert_gallery();};function _hideContent(callback){$pp_pic_holder.find('#pp_full_res object,#pp_full_res embed').css('visibility','hidden');$pp_pic_holder.find('.pp_fade').fadeOut(settings.animation_speed,function(){$('.pp_loaderIcon').show();callback();});};function _checkPosition(setCount){(setCount>1)?$('.pp_nav').show():$('.pp_nav').hide();};function _fitToViewport(width,height){resized=false;_getDimensions(width,height);imageWidth=width,imageHeight=height;if(((pp_containerWidth>windowWidth)||(pp_containerHeight>windowHeight))&&doresize&&settings.allow_resize&&!percentBased){resized=true,fitting=false;while(!fitting){if((pp_containerWidth>windowWidth)){imageWidth=(windowWidth-200);imageHeight=(height/width)*imageWidth;}else if((pp_containerHeight>windowHeight)){imageHeight=(windowHeight-200);imageWidth=(width/height)*imageHeight;}else{fitting=true;};pp_containerHeight=imageHeight,pp_containerWidth=imageWidth;};_getDimensions(imageWidth,imageHeight);};return{width:Math.floor(imageWidth),height:Math.floor(imageHeight),containerHeight:Math.floor(pp_containerHeight),containerWidth:Math.floor(pp_containerWidth)+40,contentHeight:Math.floor(pp_contentHeight),contentWidth:Math.floor(pp_contentWidth),resized:resized};};function _getDimensions(width,height){width=parseFloat(width);height=parseFloat(height);$pp_details=$pp_pic_holder.find('.pp_details');$pp_details.width(width);detailsHeight=parseFloat($pp_details.css('marginTop'))+parseFloat($pp_details.css('marginBottom'));$pp_details=$pp_details.clone().appendTo($('body')).css({'position':'absolute','top':-10000});detailsHeight+=$pp_details.height();detailsHeight=(detailsHeight<=34)?36:detailsHeight;if($.browser.msie&&$.browser.version==7)detailsHeight+=8;$pp_details.remove();$pp_title=$pp_pic_holder.find('.ppt');$pp_title.width(width);titleHeight=parseFloat($pp_title.css('marginTop'))+parseFloat($pp_title.css('marginBottom'));$pp_title=$pp_title.clone().appendTo($('body')).css({'position':'absolute','top':-10000});titleHeight+=$pp_title.height();$pp_title.remove();pp_contentHeight=height+detailsHeight;pp_contentWidth=width;pp_containerHeight=pp_contentHeight+titleHeight+$pp_pic_holder.find('.pp_top').height()+$pp_pic_holder.find('.pp_bottom').height();pp_containerWidth=width;} -function _getFileType(itemSrc){if(itemSrc.match(/youtube\.com\/watch/i)){return'youtube';}else if(itemSrc.match(/vimeo\.com/i)){return'vimeo';}else if(itemSrc.match(/\b.mov\b/i)){return'quicktime';}else if(itemSrc.match(/\b.swf\b/i)){return'flash';}else if(itemSrc.match(/\biframe=true\b/i)){return'iframe';}else if(itemSrc.match(/\bcustom=true\b/i)){return'custom';}else if(itemSrc.substr(0,1)=='#'){return'inline';}else{return'image';};};function _center_overlay(){if(doresize&&typeof $pp_pic_holder!='undefined'){scroll_pos=_get_scroll();contentHeight=$pp_pic_holder.height(),contentwidth=$pp_pic_holder.width();projectedTop=(windowHeight/2)+scroll_pos['scrollTop']-(contentHeight/2);if(projectedTop<0)projectedTop=0;$pp_pic_holder.css({'top':projectedTop,'left':(windowWidth/2)+scroll_pos['scrollLeft']-(contentwidth/2)});};};function _get_scroll(){if(self.pageYOffset){return{scrollTop:self.pageYOffset,scrollLeft:self.pageXOffset};}else if(document.documentElement&&document.documentElement.scrollTop){return{scrollTop:document.documentElement.scrollTop,scrollLeft:document.documentElement.scrollLeft};}else if(document.body){return{scrollTop:document.body.scrollTop,scrollLeft:document.body.scrollLeft};};};function _resize_overlay(){windowHeight=$(window).height(),windowWidth=$(window).width();if(typeof $pp_overlay!="undefined")$pp_overlay.height($(document).height()).width(windowWidth);};function _insert_gallery(){if(isSet&&settings.overlay_gallery&&_getFileType(pp_images[set_position])=="image"){itemWidth=52+5;navWidth=(settings.theme=="facebook")?58:38;itemsPerPage=Math.floor((pp_dimensions['containerWidth']-100-navWidth)/itemWidth);itemsPerPage=(itemsPerPage";};toInject=settings.gallery_markup.replace(/{gallery}/g,toInject);$pp_pic_holder.find('#pp_full_res').after(toInject);$pp_pic_holder.find('.pp_gallery .pp_arrow_next').click(function(){$.prettyPhoto.changeGalleryPage('next');$.prettyPhoto.stopSlideshow();return false;});$pp_pic_holder.find('.pp_gallery .pp_arrow_previous').click(function(){$.prettyPhoto.changeGalleryPage('previous');$.prettyPhoto.stopSlideshow();return false;});$pp_pic_holder.find('.pp_content').hover(function(){$pp_pic_holder.find('.pp_gallery:not(.disabled)').fadeIn();},function(){$pp_pic_holder.find('.pp_gallery:not(.disabled)').fadeOut();});itemWidth=52+5;$pp_pic_holder.find('.pp_gallery ul li').each(function(i){$(this).css({'position':'absolute','left':i*itemWidth});$(this).find('a').unbind('click').click(function(){$.prettyPhoto.changePage(i);$.prettyPhoto.stopSlideshow();return false;});});};if(settings.slideshow){$pp_pic_holder.find('.pp_nav').prepend('Play') -$pp_pic_holder.find('.pp_nav .pp_play').click(function(){$.prettyPhoto.startSlideshow();return false;});} -$pp_pic_holder.attr('class','pp_pic_holder '+settings.theme);$pp_overlay.css({'opacity':0,'height':$(document).height(),'width':$(window).width()}).bind('click',function(){if(!settings.modal)$.prettyPhoto.close();});$('a.pp_close').bind('click',function(){$.prettyPhoto.close();return false;});$('a.pp_expand').bind('click',function(e){if($(this).hasClass('pp_expand')){$(this).removeClass('pp_expand').addClass('pp_contract');doresize=false;}else{$(this).removeClass('pp_contract').addClass('pp_expand');doresize=true;};_hideContent(function(){$.prettyPhoto.open();});return false;});$pp_pic_holder.find('.pp_previous, .pp_nav .pp_arrow_previous').bind('click',function(){$.prettyPhoto.changePage('previous');$.prettyPhoto.stopSlideshow();return false;});$pp_pic_holder.find('.pp_next, .pp_nav .pp_arrow_next').bind('click',function(){$.prettyPhoto.changePage('next');$.prettyPhoto.stopSlideshow();return false;});_center_overlay();};return this.unbind('click.prettyphoto').bind('click.prettyphoto',$.prettyPhoto.initialize);};function grab_param(name,url){name=name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");var regexS="[\\?&]"+name+"=([^&#]*)";var regex=new RegExp(regexS);var results=regex.exec(url);return(results==null)?"":results[1];}})(jQuery); diff --git a/applications/examples/static/powered_by/web2py_sticker_3d8799.png b/applications/examples/static/powered_by/web2py_sticker_3d8799.png deleted file mode 100644 index b5a88b97..00000000 Binary files a/applications/examples/static/powered_by/web2py_sticker_3d8799.png and /dev/null differ diff --git a/applications/examples/static/powered_by/web2py_sticker_3d9960.png b/applications/examples/static/powered_by/web2py_sticker_3d9960.png deleted file mode 100644 index cac03181..00000000 Binary files a/applications/examples/static/powered_by/web2py_sticker_3d9960.png and /dev/null differ diff --git a/applications/examples/static/powered_by/web2py_sticker_463d99.png b/applications/examples/static/powered_by/web2py_sticker_463d99.png deleted file mode 100644 index 67e6a99b..00000000 Binary files a/applications/examples/static/powered_by/web2py_sticker_463d99.png and /dev/null differ diff --git a/applications/examples/static/powered_by/web2py_sticker_73993d.png b/applications/examples/static/powered_by/web2py_sticker_73993d.png deleted file mode 100644 index c1a08408..00000000 Binary files a/applications/examples/static/powered_by/web2py_sticker_73993d.png and /dev/null differ diff --git a/applications/examples/static/powered_by/web2py_sticker_993d3d.png b/applications/examples/static/powered_by/web2py_sticker_993d3d.png deleted file mode 100644 index a86918ee..00000000 Binary files a/applications/examples/static/powered_by/web2py_sticker_993d3d.png and /dev/null differ diff --git a/applications/examples/static/powered_by/web2py_sticker_993d98.png b/applications/examples/static/powered_by/web2py_sticker_993d98.png deleted file mode 100644 index 04ade159..00000000 Binary files a/applications/examples/static/powered_by/web2py_sticker_993d98.png and /dev/null differ diff --git a/applications/examples/static/powered_by/web2py_sticker_996f3d.png b/applications/examples/static/powered_by/web2py_sticker_996f3d.png deleted file mode 100644 index 1e9fb2ef..00000000 Binary files a/applications/examples/static/powered_by/web2py_sticker_996f3d.png and /dev/null differ diff --git a/applications/examples/static/powered_by/web2py_sticker_99963d.png b/applications/examples/static/powered_by/web2py_sticker_99963d.png deleted file mode 100644 index afe8c0cd..00000000 Binary files a/applications/examples/static/powered_by/web2py_sticker_99963d.png and /dev/null differ diff --git a/applications/examples/static/web2py_logo_light.png b/applications/examples/static/web2py_logo_light.png deleted file mode 100644 index cf41af38..00000000 Binary files a/applications/examples/static/web2py_logo_light.png and /dev/null differ diff --git a/applications/examples/views/appadmin.html b/applications/examples/views/appadmin.html index 1d967c2f..541a18bb 100644 --- a/applications/examples/views/appadmin.html +++ b/applications/examples/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]))}} ] -

+ + + + {{pass}} +
+ {{=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}} {{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]))}} ] -

+ + + + {{pass}} +
+ {{=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}} {{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() -
invalid expression
+
invalid expression
>>> 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() -
only alphanumeric!
+
only alphanumeric!
>>> 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()