commit merge

This commit is contained in:
Michele Comitini
2012-08-29 14:24:14 +02:00
parent f9afeb51f8
commit e5f9358cf9
75 changed files with 1546 additions and 2066 deletions
+3
View File
@@ -3,6 +3,9 @@
### DAL Improvements ### DAL Improvements
- Support for DAL(lazy_tables=True) and db.define_table(on_define=lambda table:), thanks Jonathan - 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 - MongoDB support in DAL (experimental), thanks Mark Breedveld
- geodal and spatialite, thanks Denes and Fran (experimental) - 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) - db.mytable._before_insert, _after_insert, _before_update, _after_update, _before_delete. _after_delete (list of callbacks)
+1 -1
View File
@@ -29,7 +29,7 @@ update:
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
echo "remember that pymysql was tweaked" echo "remember that pymysql was tweaked"
src: 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 ### rm -f all junk files
make clean make clean
### clean up baisc apps ### clean up baisc apps
+1 -1
View File
@@ -1 +1 @@
Version 2.00.0 (2012-08-26 00:00:11) dev Version 2.00.1 (2012-08-28 22:14:54) rc4
@@ -11,6 +11,8 @@ import copy
import gluon.contenttype import gluon.contenttype
import gluon.fileutils import gluon.fileutils
response.subtitle = 'Database Administration (appadmin)'
# ## critical --- make a copy of the environment # ## critical --- make a copy of the environment
global_env = copy.copy(globals()) global_env = copy.copy(globals())
+5 -5
View File
@@ -23,9 +23,9 @@ except ImportError:
GIT_MISSING = 'requires python-git module, but not installed or incompatible version' GIT_MISSING = 'requires python-git module, but not installed or incompatible version'
from gluon.languages import (regex_language, read_possible_languages, from gluon.languages import (regex_language, read_possible_languages,
read_possible_plurals, lang_sampling, lang_sampling,
read_dict, write_dict, read_plural_dict, 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']: 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 # get only existed files
languages = sorted(all_languages) languages = sorted(all_languages)
plural_rules={} plural_rules = {}
all_plurals=read_possible_plurals() all_plurals = PLURAL_RULES
for langfile,lang in all_languages.iteritems(): for langfile,lang in all_languages.iteritems():
lang=lang.strip() lang=lang.strip()
match_language = regex_language.match(lang) match_language = regex_language.match(lang)
@@ -961,7 +961,7 @@ def design():
plang = lang_sampling(match_language, all_plurals.keys()) plang = lang_sampling(match_language, all_plurals.keys())
if plang: if plang:
plural=all_plurals[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: else:
plural_rules[langfile]=(0,lang,'plural_rules-%s.py'%lang,'') plural_rules[langfile]=(0,lang,'plural_rules-%s.py'%lang,'')
+122
View File
@@ -4,5 +4,127 @@
'!langname!': 'English (US)', '!langname!': 'English (US)',
'%Y-%m-%d': '%m-%d-%Y', '%Y-%m-%d': '%m-%d-%Y',
'%Y-%m-%d %H:%M:%S': '%m-%d-%Y %H:%M:%S', '%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': '<<back', 'back': '<<back',
'can be a git repo': 'can be a git repo',
'Change admin password': 'Change admin password',
'Check for upgrades': 'Check for upgrades',
'Checking for upgrades...': 'Checking for upgrades...',
'Clean': 'Clean',
'code': 'code',
'collapse/expand all': 'collapse/expand all',
'Compile': 'Compile',
'Controllers': 'Controllers',
'controllers': 'controllers',
'Create': 'Create',
'create file with filename:': 'create file with filename:',
'Create rules': 'Create rules',
'created by': 'created by',
'crontab': 'crontab',
'currently running': 'currently running',
'database administration': 'database administration',
'Debug': 'Debug',
'defines tables': 'defines tables',
'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)',
'Deploy': 'Deploy',
'Deploy on Google App Engine': 'Deploy on Google App Engine',
'Deploy to OpenShift': 'Deploy to OpenShift',
'Detailed traceback description': 'Detailed traceback description',
'direction: ltr': 'direction: ltr',
'Disable': 'Disable',
'download layouts': 'download layouts',
'download plugins': 'download plugins',
'Edit': 'Edit',
'Edit application': 'Edit application',
'Error snapshot': 'Error snapshot',
'Error ticket': 'Error ticket',
'Errors': 'Errors',
'Exception instance attributes': 'Exception instance attributes',
'exposes': 'exposes',
'extends': 'extends',
'filter': 'filter',
'Frames': 'Frames',
'Get from URL:': 'Get from URL:',
'Git Pull': 'Git Pull',
'Git Push': 'Git Push',
'Help': 'Help',
'includes': 'includes',
'inspect attributes': 'inspect attributes',
'Install': 'Install',
'Installed applications': 'Installed applications',
'languages': 'languages',
'Languages': 'Languages',
'loading...': 'loading...',
'locals': 'locals',
'Login': 'Login',
'Login to the Administrative Interface': 'Login to the Administrative Interface',
'Logout': 'Logout',
'Models': 'Models',
'models': 'models',
'modules': 'modules',
'Modules': 'Modules',
'New application wizard': 'New application wizard',
'New simple application': 'New simple application',
'Overwrite installed app': 'Overwrite installed app',
'Pack all': 'Pack all',
'Plugins': 'Plugins',
'plugins': 'plugins',
'Plural-Forms:': 'Plural-Forms:',
'Powered by': 'Powered by',
'Private files': 'Private files',
'private files': 'private files',
'Reload routes': 'Reload routes',
'request': 'request',
'response': 'response',
'rules are not defined': 'rules are not defined',
'rules:': 'rules:',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')",
'Running on %s': 'Running on %s',
'session': 'session',
'shell': 'shell',
'Site': 'Site',
'Start wizard': 'Start wizard',
'static': 'static',
'Static files': 'Static files',
'Submit': 'Submit',
'test': 'test',
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
'There are no models': 'There are no models',
'There are no plugins': 'There are no plugins',
'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app',
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
'Ticket ID': 'Ticket ID',
'Ticket Missing': 'Ticket Missing',
'To create a plugin, name a file/folder plugin_[name]': 'To create a plugin, name a file/folder plugin_[name]',
'Traceback': 'Traceback',
'Translation strings for the application': 'Translation strings for the application',
'Uninstall': 'Uninstall',
'update all languages': 'update all languages',
'upload': 'upload',
'Upload a package:': 'Upload a package:',
'Upload and install packed application': 'Upload and install packed application',
'upload file:': 'upload file:',
'upload plugin file:': 'upload plugin file:',
'variables': 'variables',
'Version': 'Version',
'Version %s.%s.%s (%s) %s': 'Version %s.%s.%s (%s) %s',
'Versioning': 'Versioning',
'views': 'views',
'Views': 'Views',
'Web Framework': 'Web Framework',
'web2py is up to date': 'web2py is up to date',
'web2py Recent Tweets': 'web2py Recent Tweets',
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 718 B

@@ -1,571 +0,0 @@
/*
http://keith-wood.name/timeEntry.html
Time entry for jQuery v1.4.8.
Written by Keith Wood (kbwood{at}iinet.com.au) June 2007.
Minor changes by Massimo Di Pierro Nov 2010 (simplified and changed behavior)
Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
Please attribute the author if you use it.
Turn an input field into an entry point for a time value.
The time can be entered via directly typing the value,
via the arrow keys.
It is configurable to show 12 or 24-hour time, to show or hide seconds,
to enforce a minimum and/or maximum time, to change the spinner image.
Example: jQuery('input.time').timeEntry();
*/
(function(jQuery) { // Hide scope, no jQuery conflict
var PROP_NAME = 'timeEntry';
/* TimeEntry manager.
Use the singleton instance of this class, jQuery.timeEntry, to interact with the time entry
functionality. Settings for (groups of) fields are maintained in an instance object
(TimeEntryInstance), allowing multiple different settings on the same page.
*/
function TimeEntry() {
this._disabledInputs = []; // List of time entry inputs that have been disabled
this._defaults = {
showSeconds: true, // True to show seconds as well, false for hours/minutes only
defaultTime: null, // The time to use if none has been set, leave at null for now
minTime: null, // The earliest selectable time, or null for no limit
maxTime: null, // The latest selectable time, or null for no limit
show24Hours: true, // True to use 24 hour time, false for 12 hour (AM/PM)
ampmNames: ['am', 'pm'] // Names of morning/evening markers
};
jQuery.extend(this._defaults);
}
jQuery.extend(TimeEntry.prototype, {
/*
Class name added to elements to indicate already configured with time entry.
*/
markerClassName: 'hasTimeEntry',
/* Override the default settings for all instances of the time entry.
@param options (object) the new settings to use as defaults (anonymous object)
@return (DateEntry) this object
*/
setDefaults: function(options) {
extendRemove(this._defaults, options || {});
return this;
},
/* Attach the time entry handler to an input field.
@param target (element) the field to attach to
@param options (object) custom settings for this instance
*/
_connectTimeEntry: function(target, options) {
var input = jQuery(target);
if (input.hasClass(this.markerClassName)) {
return;
}
var inst = {};
inst.options = jQuery.extend({}, options);
inst._selectedHour = 0; // The currently selected hour
inst._selectedMinute = 0; // The currently selected minute
inst._selectedSecond = 0; // The currently selected second
inst._field = 0; // The selected subfield
inst.input = jQuery(target); // The attached input field
jQuery.data(target, PROP_NAME, inst);
input.addClass(this.markerClassName).bind('focus.timeEntry', this._doFocus).
bind('blur.timeEntry', this._doBlur).bind('click.timeEntry', this._doClick).
bind('keydown.timeEntry', this._doKeyDown).bind('keypress.timeEntry', this._doKeyPress);
// Check pastes
if (jQuery.browser.mozilla)
input.bind('input.timeEntry', function(event) { jQuery.timeEntry._parseTime(inst); });
if (jQuery.browser.msie)
input.bind('paste.timeEntry', function(event) { setTimeout(function() { jQuery.timeEntry._parseTime(inst); }, 1); });
},
/* Check whether an input field has been disabled.
@param input (element) input field to check
@return (boolean) true if this field has been disabled, false if it is enabled
*/
_isDisabledTimeEntry: function(input) {
return jQuery.inArray(input, this._disabledInputs) > -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);
+26 -21
View File
@@ -10,8 +10,9 @@
//--></script> //--></script>
{{if request.function=='index':}} {{if request.function=='index':}}
<h2>{{=T("Available databases and tables")}}</h2> <h2>{{=T("Available Databases and Tables")}}</h2>
{{if not databases:}}{{=T("No databases in this application")}}{{pass}} {{if not databases:}}{{=T("No databases in this application")}}{{pass}}
<table>
{{for db in sorted(databases):}} {{for db in sorted(databases):}}
{{for table in databases[db].tables:}} {{for table in databases[db].tables:}}
{{qry='%s.%s.id>0'%(db,table)}} {{qry='%s.%s.id>0'%(db,table)}}
@@ -28,20 +29,24 @@
{{qry=''}} {{qry=''}}
{{pass}} {{pass}}
{{pass}} {{pass}}
<h3> <tr>
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}} <th style="font-size: 1.75em;">
</h3> {{=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]))}} ] </th>
<br /><br /> <td>
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
</td>
</tr>
{{pass}} {{pass}}
</table>
{{pass}} {{pass}}
{{elif request.function=='select':}} {{elif request.function=='select':}}
<h2>{{=XML(str(T("database %s select"))%A(request.args[0],_href=URL('index'))) }} <h2>{{=XML(str(T("Database %s select"))%A(request.args[0],_href=URL('index'))) }}
</h2> </h2>
{{if table:}} {{if table:}}
[ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]<br/><br/> {{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
<h3>{{=T("Rows in table")}}</h3><br/> <h3>{{=T("Rows in Table")}}</h3><br/>
{{else:}} {{else:}}
<h3>{{=T("Rows selected")}}</h3><br/> <h3>{{=T("Rows selected")}}</h3><br/>
{{pass}} {{pass}}
@@ -51,8 +56,8 @@
{{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}</p> {{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}</p>
<br/><br/> <br/><br/>
<h4>{{=T("%s selected", nrows)}}</h4> <h4>{{=T("%s selected", nrows)}}</h4>
{{if start>0:}}[ {{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)))}} ]{{pass}} {{if start>0:}}{{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)),_class="btn")}}{{pass}}
{{if stop<nrows:}}[ {{=A(T('next 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start+100)))}} ]{{pass}} {{if stop<nrows:}}{{=A(T('next 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start+100)),_class="btn")}}{{pass}}
{{if rows:}} {{if rows:}}
<div style="overflow: auto;" width="80%"> <div style="overflow: auto;" width="80%">
{{linkto=URL('update',args=request.args[0])}} {{linkto=URL('update',args=request.args[0])}}
@@ -61,35 +66,35 @@
</div> </div>
{{pass}} {{pass}}
<br/><br/><h3>{{=T("Import/Export")}}</h3><br/> <br/><br/><h3>{{=T("Import/Export")}}</h3><br/>
[ <a href="{{=URL('csv',args=request.args[0],vars=dict(query=query))}}">{{=T("export as csv file")}}</a> ] <a href="{{=URL('csv',args=request.args[0],vars=dict(query=query))}}" class="btn">{{=T("export as csv file")}}</a>
{{=formcsv or ''}} {{=formcsv or ''}}
{{elif request.function=='insert':}} {{elif request.function=='insert':}}
<h2>{{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} <h2>{{=T("Database")}} {{=A(request.args[0],_href=URL('index'))}}
{{if hasattr(table,'_primarykey'):}} {{if hasattr(table,'_primarykey'):}}
{{fieldname=table._primarykey[0]}} {{fieldname=table._primarykey[0]}}
{{dbname=request.args[0]}} {{dbname=request.args[0]}}
{{tablename=request.args[1]}} {{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} {{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:}} {{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}} {{pass}}
</h2> </h2>
<h3>{{=T("New Record")}}</h3><br/> <h3>{{=T("New Record")}}</h3><br/>
{{=form}} {{=form}}
{{elif request.function=='update':}} {{elif request.function=='update':}}
<h2>{{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} <h2>{{=T("Database")}} {{=A(request.args[0],_href=URL('index'))}}
{{if hasattr(table,'_primarykey'):}} {{if hasattr(table,'_primarykey'):}}
{{fieldname=request.vars.keys()[0]}} {{fieldname=request.vars.keys()[0]}}
{{dbname=request.args[0]}} {{dbname=request.args[0]}}
{{tablename=request.args[1]}} {{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} {{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))))}}
{{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}} {{=T("Record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
{{else:}} {{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]))))}}
{{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}} {{=T("Record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
{{pass}} {{pass}}
</h2> </h2>
<h3>{{=T("Edit current record")}}</h3><br/><br/>{{=form}} <h3>{{=T("Edit current record")}}</h3><br/><br/>{{=form}}
+7 -1
View File
@@ -13,12 +13,16 @@
<th>web2py&trade;</th> <th>web2py&trade;</th>
<td>{{=myversion}}</td> <td>{{=myversion}}</td>
</tr> </tr>
{{if snapshot:}}
<tr> <tr>
<th>Python</th> <th>Python</th>
<td>{{=snapshot.get('pyver','')}}</td> <td>{{=snapshot.get('pyver','')}}</td>
</tr> </tr>
{{pass}}
</tbody> </tbody>
</table> </table>
{{if traceback or code or layer:}}
<h3>{{=T('Traceback')}}</h3> <h3>{{=T('Traceback')}}</h3>
<div class="inspect"> <div class="inspect">
{{=traceback}} {{=traceback}}
@@ -130,4 +134,6 @@
<h3>In file: {{=layer}}</h3> <h3>In file: {{=layer}}</h3>
{{=CODE(code.replace('\r',''),language='python',link='/examples/global/vars/')}} {{=CODE(code.replace('\r',''),language='python',link='/examples/global/vars/')}}
</div> </div>
{{else:}}
<h3>{{=T('Ticket Missing')}}</h3>
{{pass}}
@@ -11,6 +11,8 @@ import copy
import gluon.contenttype import gluon.contenttype
import gluon.fileutils import gluon.fileutils
response.subtitle = 'Database Administration (appadmin)'
# ## critical --- make a copy of the environment # ## critical --- make a copy of the environment
global_env = copy.copy(globals()) global_env = copy.copy(globals())
Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

-116
View File
@@ -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%" <a class="meta" href="http://twitter.com/%user_screen_name%/status/%id%">%time%</a>'});
}
// 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
File diff suppressed because one or more lines are too long
@@ -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:'<div class="pp_pic_holder"> \
<div class="ppt">&nbsp;</div> \
<div class="pp_top"> \
<div class="pp_left"></div> \
<div class="pp_middle"></div> \
<div class="pp_right"></div> \
</div> \
<div class="pp_content_container"> \
<div class="pp_left"> \
<div class="pp_right"> \
<div class="pp_content"> \
<div class="pp_loaderIcon"></div> \
<div class="pp_fade"> \
<a href="#" class="pp_expand" title="Expand the image">Expand</a> \
<div class="pp_hoverContainer"> \
<a class="pp_next" href="#">next</a> \
<a class="pp_previous" href="#">previous</a> \
</div> \
<div id="pp_full_res"></div> \
<div class="pp_details clearfix"> \
<p class="pp_description"></p> \
<a class="pp_close" href="#">Close</a> \
<div class="pp_nav"> \
<a href="#" class="pp_arrow_previous">Previous</a> \
<p class="currentTextHolder">0/0</p> \
<a href="#" class="pp_arrow_next">Next</a> \
</div> \
</div> \
</div> \
</div> \
</div> \
</div> \
</div> \
<div class="pp_bottom"> \
<div class="pp_left"></div> \
<div class="pp_middle"></div> \
<div class="pp_right"></div> \
</div> \
</div> \
<div class="pp_overlay"></div>',gallery_markup:'<div class="pp_gallery"> \
<a href="#" class="pp_arrow_previous">Previous</a> \
<ul> \
{gallery} \
</ul> \
<a href="#" class="pp_arrow_next">Next</a> \
</div>',image_markup:'<img id="fullResImage" src="{path}" />',flash_markup:'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="{width}" height="{height}"><param name="wmode" value="{wmode}" /><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="{path}" /><embed src="{path}" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="{width}" height="{height}" wmode="{wmode}"></embed></object>',quicktime_markup:'<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" codebase="http://www.apple.com/qtactivex/qtplugin.cab" height="{height}" width="{width}"><param name="src" value="{path}"><param name="autoplay" value="{autoplay}"><param name="type" value="video/quicktime"><embed src="{path}" height="{height}" width="{width}" autoplay="{autoplay}" type="video/quicktime" pluginspage="http://www.apple.com/quicktime/download/"></embed></object>',iframe_markup:'<iframe src ="{path}" width="{width}" height="{height}" frameborder="no"></iframe>',inline_markup:'<div class="pp_inline clearfix">{content}</div>',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('&nbsp;');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&amp;byline=0&amp;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('<div id="pp_full_res"><div class="pp_inline clearfix"></div></div>').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<pp_images.length)?itemsPerPage:pp_images.length;totalPage=Math.ceil(pp_images.length/itemsPerPage)-1;if(totalPage==0){navWidth=0;$pp_pic_holder.find('.pp_gallery .pp_arrow_next,.pp_gallery .pp_arrow_previous').hide();}else{$pp_pic_holder.find('.pp_gallery .pp_arrow_next,.pp_gallery .pp_arrow_previous').show();};galleryWidth=itemsPerPage*itemWidth+navWidth;$pp_pic_holder.find('.pp_gallery').width(galleryWidth).css('margin-left',-(galleryWidth/2));$pp_pic_holder.find('.pp_gallery ul').width(itemsPerPage*itemWidth).find('li.selected').removeClass('selected');goToPage=(Math.ceil((set_position+1)/itemsPerPage)<totalPage)?Math.ceil((set_position+1)/itemsPerPage):totalPage;$.prettyPhoto.changeGalleryPage(goToPage);$pp_pic_holder.find('.pp_gallery ul li:eq('+set_position+')').addClass('selected');}else{$pp_pic_holder.find('.pp_content').unbind('mouseenter mouseleave');$pp_pic_holder.find('.pp_gallery').hide();}}
function _buildOverlay(caller){$('body').append(settings.markup);$pp_pic_holder=$('.pp_pic_holder'),$ppt=$('.ppt'),$pp_overlay=$('div.pp_overlay');if(isSet&&settings.overlay_gallery){currentGalleryPage=0;toInject="";for(var i=0;i<pp_images.length;i++){if(!pp_images[i].match(/\b(jpg|jpeg|png|gif)\b/gi)){classname='default';}else{classname='';}
toInject+="<li class='"+classname+"'><a href='#'><img src='"+pp_images[i]+"' width='50' alt='' /></a></li>";};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('<a href="#" class="pp_play">Play</a>')
$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);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

+26 -21
View File
@@ -10,8 +10,9 @@
//--></script> //--></script>
{{if request.function=='index':}} {{if request.function=='index':}}
<h2>{{=T("Available databases and tables")}}</h2> <h2>{{=T("Available Databases and Tables")}}</h2>
{{if not databases:}}{{=T("No databases in this application")}}{{pass}} {{if not databases:}}{{=T("No databases in this application")}}{{pass}}
<table>
{{for db in sorted(databases):}} {{for db in sorted(databases):}}
{{for table in databases[db].tables:}} {{for table in databases[db].tables:}}
{{qry='%s.%s.id>0'%(db,table)}} {{qry='%s.%s.id>0'%(db,table)}}
@@ -28,20 +29,24 @@
{{qry=''}} {{qry=''}}
{{pass}} {{pass}}
{{pass}} {{pass}}
<h3> <tr>
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}} <th style="font-size: 1.75em;">
</h3> {{=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]))}} ] </th>
<br /><br /> <td>
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
</td>
</tr>
{{pass}} {{pass}}
</table>
{{pass}} {{pass}}
{{elif request.function=='select':}} {{elif request.function=='select':}}
<h2>{{=XML(str(T("database %s select"))%A(request.args[0],_href=URL('index'))) }} <h2>{{=XML(str(T("Database %s select"))%A(request.args[0],_href=URL('index'))) }}
</h2> </h2>
{{if table:}} {{if table:}}
[ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]<br/><br/> {{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
<h3>{{=T("Rows in table")}}</h3><br/> <h3>{{=T("Rows in Table")}}</h3><br/>
{{else:}} {{else:}}
<h3>{{=T("Rows selected")}}</h3><br/> <h3>{{=T("Rows selected")}}</h3><br/>
{{pass}} {{pass}}
@@ -51,8 +56,8 @@
{{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}</p> {{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}</p>
<br/><br/> <br/><br/>
<h4>{{=T("%s selected", nrows)}}</h4> <h4>{{=T("%s selected", nrows)}}</h4>
{{if start>0:}}[ {{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)))}} ]{{pass}} {{if start>0:}}{{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)),_class="btn")}}{{pass}}
{{if stop<nrows:}}[ {{=A(T('next 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start+100)))}} ]{{pass}} {{if stop<nrows:}}{{=A(T('next 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start+100)),_class="btn")}}{{pass}}
{{if rows:}} {{if rows:}}
<div style="overflow: auto;" width="80%"> <div style="overflow: auto;" width="80%">
{{linkto=URL('update',args=request.args[0])}} {{linkto=URL('update',args=request.args[0])}}
@@ -61,35 +66,35 @@
</div> </div>
{{pass}} {{pass}}
<br/><br/><h3>{{=T("Import/Export")}}</h3><br/> <br/><br/><h3>{{=T("Import/Export")}}</h3><br/>
[ <a href="{{=URL('csv',args=request.args[0],vars=dict(query=query))}}">{{=T("export as csv file")}}</a> ] <a href="{{=URL('csv',args=request.args[0],vars=dict(query=query))}}" class="btn">{{=T("export as csv file")}}</a>
{{=formcsv or ''}} {{=formcsv or ''}}
{{elif request.function=='insert':}} {{elif request.function=='insert':}}
<h2>{{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} <h2>{{=T("Database")}} {{=A(request.args[0],_href=URL('index'))}}
{{if hasattr(table,'_primarykey'):}} {{if hasattr(table,'_primarykey'):}}
{{fieldname=table._primarykey[0]}} {{fieldname=table._primarykey[0]}}
{{dbname=request.args[0]}} {{dbname=request.args[0]}}
{{tablename=request.args[1]}} {{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} {{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:}} {{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}} {{pass}}
</h2> </h2>
<h3>{{=T("New Record")}}</h3><br/> <h3>{{=T("New Record")}}</h3><br/>
{{=form}} {{=form}}
{{elif request.function=='update':}} {{elif request.function=='update':}}
<h2>{{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} <h2>{{=T("Database")}} {{=A(request.args[0],_href=URL('index'))}}
{{if hasattr(table,'_primarykey'):}} {{if hasattr(table,'_primarykey'):}}
{{fieldname=request.vars.keys()[0]}} {{fieldname=request.vars.keys()[0]}}
{{dbname=request.args[0]}} {{dbname=request.args[0]}}
{{tablename=request.args[1]}} {{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} {{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))))}}
{{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}} {{=T("Record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
{{else:}} {{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]))))}}
{{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}} {{=T("Record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
{{pass}} {{pass}}
</h2> </h2>
<h3>{{=T("Edit current record")}}</h3><br/><br/>{{=form}} <h3>{{=T("Edit current record")}}</h3><br/><br/>{{=form}}
@@ -11,6 +11,8 @@ import copy
import gluon.contenttype import gluon.contenttype
import gluon.fileutils import gluon.fileutils
response.subtitle = 'Database Administration (appadmin)'
# ## critical --- make a copy of the environment # ## critical --- make a copy of the environment
global_env = copy.copy(globals()) global_env = copy.copy(globals())
+7 -7
View File
@@ -10,7 +10,7 @@
'%s selected': '%s označených', '%s selected': '%s označených',
'Administrative interface': 'pro administrátorské rozhranie kliknite sem', 'Administrative interface': 'pro administrátorské rozhranie kliknite sem',
'Are you sure you want to delete this object?': 'Opravdu chceš odstranit tento objekt?', '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é', 'Cannot be empty': 'Nemůže být prázdné',
'Change password': 'Změna hesla', 'Change password': 'Změna hesla',
'Check to delete': 'Označit ke smazání', 'Check to delete': 'Označit ke smazání',
@@ -68,7 +68,7 @@
'Reset Password key': 'Nastavit registrační kľíč', 'Reset Password key': 'Nastavit registrační kľíč',
'Retrieve username': 'Retrieve username', 'Retrieve username': 'Retrieve username',
'Role': 'Role', 'Role': 'Role',
'Rows in table': 'řádků v tabulce', 'Rows in Table': 'řádků v tabulce',
'Rows selected': 'označených řádků', 'Rows selected': 'označených řádků',
'Stylesheet': 'CSS', 'Stylesheet': 'CSS',
'Submit': 'Odeslat', 'Submit': 'Odeslat',
@@ -100,8 +100,8 @@
'cache': 'cache', 'cache': 'cache',
'customize me!': 'uprav mě!', 'customize me!': 'uprav mě!',
'data uploaded': 'data nahrána', 'data uploaded': 'data nahrána',
'database': 'databáze', 'Database': 'databáze',
'database %s select': 'databáze %s výber', 'Database %s select': 'databáze %s výber',
'db': 'db', 'db': 'db',
'design': 'návrh', 'design': 'návrh',
'done!': 'hotovo!', 'done!': 'hotovo!',
@@ -121,11 +121,11 @@
'password': 'heslo', 'password': 'heslo',
'previous 100 rows': 'předchádzajících 100 řádků', 'previous 100 rows': 'předchádzajících 100 řádků',
'profile': 'profil', 'profile': 'profil',
'record': 'záznam', 'Record': 'záznam',
'record does not exist': 'záznam neexistuje', 'record does not exist': 'záznam neexistuje',
'record id': 'id záznamu', 'Record id': 'id záznamu',
'register': 'registrovat', 'register': 'registrovat',
'state': 'stav', 'state': 'stav',
'table': 'tabulka', 'Table': 'tabulka',
'unable to parse csv file': 'nedá sa zpracovat csv soubor', 'unable to parse csv file': 'nedá sa zpracovat csv soubor',
} }
+11
View File
@@ -2,4 +2,15 @@
{ {
'!langcode!': 'en-us', '!langcode!': 'en-us',
'!langname!': 'English (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',
} }
+7 -7
View File
@@ -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"',
'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', '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 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.', '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', 'Change Password': 'Cambie Contraseña',
@@ -99,7 +99,7 @@
'Reset Password key': 'Reset Password key', 'Reset Password key': 'Reset Password key',
'Resolve Conflict file': 'archivo Resolución de Conflicto', 'Resolve Conflict file': 'archivo Resolución de Conflicto',
'Role': 'Rol', 'Role': 'Rol',
'Rows in table': 'Filas en la tabla', 'Rows in Table': 'Filas en la tabla',
'Rows selected': 'Filas seleccionadas', 'Rows selected': 'Filas seleccionadas',
'Saved file hash:': 'Hash del archivo guardado:', 'Saved file hash:': 'Hash del archivo guardado:',
'Static files': 'Archivos estáticos', 'Static files': 'Archivos estáticos',
@@ -165,8 +165,8 @@
'currently saved or': 'actualmente guardado o', 'currently saved or': 'actualmente guardado o',
'customize me!': 'Adaptame!', 'customize me!': 'Adaptame!',
'data uploaded': 'datos subidos', 'data uploaded': 'datos subidos',
'database': 'base de datos', 'Database': 'base de datos',
'database %s select': 'selección en base de datos %s', 'Database %s select': 'selección en base de datos %s',
'database administration': 'administración base de datos', 'database administration': 'administración base de datos',
'db': 'db', 'db': 'db',
'defines tables': 'define tablas', 'defines tables': 'define tablas',
@@ -218,9 +218,9 @@
'pack all': 'empaquetar todo', 'pack all': 'empaquetar todo',
'pack compiled': 'empaquete compiladas', 'pack compiled': 'empaquete compiladas',
'previous 100 rows': '100 filas anteriores', 'previous 100 rows': '100 filas anteriores',
'record': 'registro', 'Record': 'registro',
'record does not exist': 'el registro no existe', 'record does not exist': 'el registro no existe',
'record id': 'id de registro', 'Record id': 'id de registro',
'register': 'registrese', 'register': 'registrese',
'remove compiled': 'eliminar compiladas', 'remove compiled': 'eliminar compiladas',
'restore': 'restaurar', 'restore': 'restaurar',
@@ -232,7 +232,7 @@
'some files could not be removed': 'algunos archivos no pudieron ser removidos', 'some files could not be removed': 'algunos archivos no pudieron ser removidos',
'state': 'estado', 'state': 'estado',
'static': 'estáticos', 'static': 'estáticos',
'table': 'tabla', 'Table': 'tabla',
'test': 'probar', '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 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', 'the data representation, define database tables and sets': 'la representación de datos, define tablas y conjuntos de base de datos',
+7 -7
View File
@@ -14,7 +14,7 @@
'Ajax Recipes': 'Recettes Ajax', 'Ajax Recipes': 'Recettes Ajax',
'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?', 'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
'Authentication': 'Authentification', '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', 'Buy this book': 'Acheter ce livre',
'Cannot be empty': 'Ne peut pas être vide', 'Cannot be empty': 'Ne peut pas être vide',
'Check to delete': 'Cliquez pour supprimer', 'Check to delete': 'Cliquez pour supprimer',
@@ -95,7 +95,7 @@
'Reset Password key': 'Réinitialiser le mot clé', 'Reset Password key': 'Réinitialiser le mot clé',
'Resources': 'Ressources', 'Resources': 'Ressources',
'Role': 'Rôle', 'Role': 'Rôle',
'Rows in table': 'Lignes du tableau', 'Rows in Table': 'Lignes du tableau',
'Rows selected': 'Lignes sélectionnées', 'Rows selected': 'Lignes sélectionnées',
'Semantic': 'Sémantique', 'Semantic': 'Sémantique',
'Services': 'Services', 'Services': 'Services',
@@ -135,8 +135,8 @@
'change password': 'changer le mot de passe', 'change password': 'changer le mot de passe',
'customize me!': 'personnalisez-moi!', 'customize me!': 'personnalisez-moi!',
'data uploaded': 'données téléchargées', '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 %s select': 'base de données %s select',
'db': 'db', 'db': 'db',
'design': 'design', 'design': 'design',
'done!': 'fait!', 'done!': 'fait!',
@@ -157,12 +157,12 @@
'please input your password again': "S'il vous plaît entrer votre mot de passe", 'please input your password again': "S'il vous plaît entrer votre mot de passe",
'previous 100 rows': '100 lignes précédentes', 'previous 100 rows': '100 lignes précédentes',
'profile': 'profile', 'profile': 'profile',
'record': 'enregistrement', 'Record': 'enregistrement',
'record does not exist': "l'archive n'existe pas", 'record does not exist': "l'archive n'existe pas",
'record id': "id d'enregistrement", 'Record id': "id d'enregistrement",
'register': "s'inscrire", 'register': "s'inscrire",
'state': 'état', 'state': 'état',
'table': 'tableau', 'Table': 'tableau',
'unable to parse csv file': "incapable d'analyser le fichier cvs", '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', 'value already in database or empty': 'valeur déjà dans la base ou vide',
} }
+7 -7
View File
@@ -16,7 +16,7 @@
'appadmin is disabled because insecure channel': "appadmin est désactivée parce que le canal n'est pas sécurisé", '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?', 'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
'Authentication': 'Authentification', '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', 'Buy this book': 'Acheter ce livre',
'cache': 'cache', 'cache': 'cache',
'Cannot be empty': 'Ne peut pas être vide', 'Cannot be empty': 'Ne peut pas être vide',
@@ -34,8 +34,8 @@
'customize me!': 'personnalisez-moi!', 'customize me!': 'personnalisez-moi!',
'data uploaded': 'données téléchargées', 'data uploaded': 'données téléchargées',
'Database': 'Base de données', 'Database': 'Base de données',
'database': 'base de données', 'Database': 'base de données',
'database %s select': 'base de données %s select', 'Database %s select': 'base de données %s select',
'db': 'db', 'db': 'db',
'DB Model': 'Modèle DB', 'DB Model': 'Modèle DB',
'Delete:': 'Supprimer:', 'Delete:': 'Supprimer:',
@@ -114,9 +114,9 @@
'Quick Examples': 'Examples Rapides', 'Quick Examples': 'Examples Rapides',
'Readme': 'Lisez-moi', 'Readme': 'Lisez-moi',
'Recipes': 'Recettes', 'Recipes': 'Recettes',
'record': 'enregistrement', 'Record': 'enregistrement',
'record does not exist': "l'archive n'existe pas", 'record does not exist': "l'archive n'existe pas",
'record id': "id d'enregistrement", 'Record id': "id d'enregistrement",
'Record ID': "ID d'enregistrement", 'Record ID': "ID d'enregistrement",
'Register': "S'inscrire", 'Register': "S'inscrire",
'register': "s'inscrire", 'register': "s'inscrire",
@@ -127,7 +127,7 @@
'Reset Password key': 'Réinitialiser le mot clé', 'Reset Password key': 'Réinitialiser le mot clé',
'Resources': 'Ressources', 'Resources': 'Ressources',
'Role': 'Rôle', 'Role': 'Rôle',
'Rows in table': 'Lignes du tableau', 'Rows in Table': 'Lignes du tableau',
'Rows selected': 'Lignes sélectionnées', 'Rows selected': 'Lignes sélectionnées',
'Semantic': 'Sémantique', 'Semantic': 'Sémantique',
'Services': 'Services', 'Services': 'Services',
@@ -136,7 +136,7 @@
'Submit': 'Soumettre', 'Submit': 'Soumettre',
'Support': 'Support', 'Support': 'Support',
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?', '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', 'Table name': 'Nom du tableau',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "query" est une condition comme "db.table1.champ1==\'valeur\'". Quelque chose comme "db.table1.champ1==db.table2.champ2" résulte en un JOIN SQL.', 'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "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', 'The Core': 'Le noyau',
+7 -7
View File
@@ -9,7 +9,7 @@
'%s %%{row} updated': '%s पंक्तियाँ अद्यतन', '%s %%{row} updated': '%s पंक्तियाँ अद्यतन',
'%s selected': '%s चुना हुआ', '%s selected': '%s चुना हुआ',
'Administrative interface': 'प्रशासनिक इंटरफेस के लिए यहाँ क्लिक करें', 'Administrative interface': 'प्रशासनिक इंटरफेस के लिए यहाँ क्लिक करें',
'Available databases and tables': 'उपलब्ध डेटाबेस और तालिका', 'Available Databases and Tables': 'उपलब्ध डेटाबेस और तालिका',
'Cannot be empty': 'खाली नहीं हो सकता', 'Cannot be empty': 'खाली नहीं हो सकता',
'Change Password': 'पासवर्ड बदलें', 'Change Password': 'पासवर्ड बदलें',
'Check to delete': 'हटाने के लिए चुनें', 'Check to delete': 'हटाने के लिए चुनें',
@@ -43,7 +43,7 @@
'Powered by': 'Powered by', 'Powered by': 'Powered by',
'Query:': 'प्रश्न:', 'Query:': 'प्रश्न:',
'Register': 'पंजीकृत (रजिस्टर) करना ', 'Register': 'पंजीकृत (रजिस्टर) करना ',
'Rows in table': 'तालिका में पंक्तियाँ ', 'Rows in Table': 'तालिका में पंक्तियाँ ',
'Rows selected': 'चयनित (चुने गये) पंक्तियाँ ', 'Rows selected': 'चयनित (चुने गये) पंक्तियाँ ',
'Stylesheet': 'Stylesheet', 'Stylesheet': 'Stylesheet',
'Sure you want to delete this object?': 'सुनिश्चित हैं कि आप इस वस्तु को हटाना चाहते हैं?', 'Sure you want to delete this object?': 'सुनिश्चित हैं कि आप इस वस्तु को हटाना चाहते हैं?',
@@ -61,8 +61,8 @@
'change password': 'change password', 'change password': 'change password',
'customize me!': 'मुझे अनुकूलित (कस्टमाइज़) करें!', 'customize me!': 'मुझे अनुकूलित (कस्टमाइज़) करें!',
'data uploaded': 'डाटा अपलोड सम्पन्न ', 'data uploaded': 'डाटा अपलोड सम्पन्न ',
'database': 'डेटाबेस', 'Database': 'डेटाबेस',
'database %s select': 'डेटाबेस %s चुनी हुई', 'Database %s select': 'डेटाबेस %s चुनी हुई',
'db': 'db', 'db': 'db',
'design': 'रचना करें', 'design': 'रचना करें',
'done!': 'हो गया!', 'done!': 'हो गया!',
@@ -77,11 +77,11 @@
'next 100 rows': 'अगले 100 पंक्तियाँ', 'next 100 rows': 'अगले 100 पंक्तियाँ',
'or import from csv file': 'या csv फ़ाइल से आयात', 'or import from csv file': 'या csv फ़ाइल से आयात',
'previous 100 rows': 'पिछले 100 पंक्तियाँ', 'previous 100 rows': 'पिछले 100 पंक्तियाँ',
'record': 'record', 'Record': 'Record',
'record does not exist': 'रिकॉर्ड मौजूद नहीं है', 'record does not exist': 'रिकॉर्ड मौजूद नहीं है',
'record id': 'रिकॉर्ड पहचानकर्ता (आईडी)', 'Record id': 'रिकॉर्ड पहचानकर्ता (आईडी)',
'register': 'register', 'register': 'register',
'state': 'स्थिति', 'state': 'स्थिति',
'table': 'तालिका', 'Table': 'तालिका',
'unable to parse csv file': 'csv फ़ाइल पार्स करने में असमर्थ', 'unable to parse csv file': 'csv फ़ाइल पार्स करने में असमर्थ',
} }
+7 -7
View File
@@ -9,7 +9,7 @@
'%s %%{row} updated': '%s sorok frissítődtek', '%s %%{row} updated': '%s sorok frissítődtek',
'%s selected': '%s kiválasztott', '%s selected': '%s kiválasztott',
'Administrative interface': 'az adminisztrációs felületért kattints ide', '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', 'Cannot be empty': 'Nem lehet üres',
'Check to delete': 'Törléshez válaszd ki', 'Check to delete': 'Törléshez válaszd ki',
'Client IP': 'Client IP', 'Client IP': 'Client IP',
@@ -50,7 +50,7 @@
'Registration key': 'Registration key', 'Registration key': 'Registration key',
'Reset Password key': 'Reset Password key', 'Reset Password key': 'Reset Password key',
'Role': 'Role', 'Role': 'Role',
'Rows in table': 'Sorok a táblában', 'Rows in Table': 'Sorok a táblában',
'Rows selected': 'Kiválasztott sorok', 'Rows selected': 'Kiválasztott sorok',
'Stylesheet': 'Stylesheet', 'Stylesheet': 'Stylesheet',
'Sure you want to delete this object?': 'Biztos törli ezt az objektumot?', 'Sure you want to delete this object?': 'Biztos törli ezt az objektumot?',
@@ -71,8 +71,8 @@
'change password': 'jelszó megváltoztatása', 'change password': 'jelszó megváltoztatása',
'customize me!': 'változtass meg!', 'customize me!': 'változtass meg!',
'data uploaded': 'adat feltöltve', 'data uploaded': 'adat feltöltve',
'database': 'adatbázis', 'Database': 'adatbázis',
'database %s select': 'adatbázis %s kiválasztás', 'Database %s select': 'adatbázis %s kiválasztás',
'db': 'db', 'db': 'db',
'design': 'design', 'design': 'design',
'done!': 'kész!', 'done!': 'kész!',
@@ -88,11 +88,11 @@
'next 100 rows': 'következő 100 sor', 'next 100 rows': 'következő 100 sor',
'or import from csv file': 'vagy betöltés csv fájlból', 'or import from csv file': 'vagy betöltés csv fájlból',
'previous 100 rows': 'előző 100 sor', 'previous 100 rows': 'előző 100 sor',
'record': 'bejegyzés', 'Record': 'bejegyzés',
'record does not exist': 'bejegyzés nem létezik', 'record does not exist': 'bejegyzés nem létezik',
'record id': 'bejegyzés id', 'Record id': 'bejegyzés id',
'register': 'regisztráció', 'register': 'regisztráció',
'state': 'állapot', 'state': 'állapot',
'table': 'tábla', 'Table': 'tábla',
'unable to parse csv file': 'nem lehet a csv fájlt beolvasni', 'unable to parse csv file': 'nem lehet a csv fájlt beolvasni',
} }
+86 -7
View File
@@ -9,99 +9,178 @@
'%s selected': '%s selezionato', '%s selected': '%s selezionato',
'%Y-%m-%d': '%d/%m/%Y', '%Y-%m-%d': '%d/%m/%Y',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S', '%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
'@markmin\x01Number of entries: **%s**': 'Number of entries: **%s**',
'About': 'About',
'Access Control': 'Access Control',
'Administrative Interface': 'Administrative Interface',
'Administrative interface': 'Interfaccia amministrativa', 'Administrative interface': 'Interfaccia amministrativa',
'Ajax Recipes': 'Ajax Recipes',
'appadmin is disabled because insecure channel': 'Amministrazione (appadmin) disabilitata: comunicazione non sicura', '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', 'cache': 'cache',
'Cannot be empty': 'Non può essere vuoto', 'Cannot be empty': 'Non può essere vuoto',
'change password': 'Cambia password', 'change password': 'Cambia password',
'Check to delete': 'Seleziona per cancellare', 'Check to delete': 'Seleziona per cancellare',
'Clear CACHE?': 'Clear CACHE?',
'Clear DISK': 'Clear DISK',
'Clear RAM': 'Clear RAM',
'Client IP': 'Client IP', 'Client IP': 'Client IP',
'Community': 'Community',
'Components and Plugins': 'Components and Plugins',
'Controller': 'Controller', 'Controller': 'Controller',
'Copyright': 'Copyright', 'Copyright': 'Copyright',
'Created By': 'Created By',
'Created On': 'Created On',
'Current request': 'Richiesta (request) corrente', 'Current request': 'Richiesta (request) corrente',
'Current response': 'Risposta (response) corrente', 'Current response': 'Risposta (response) corrente',
'Current session': 'Sessione (session) corrente', 'Current session': 'Sessione (session) corrente',
'customize me!': 'Personalizzami!', 'customize me!': 'Personalizzami!',
'data uploaded': 'dati caricati', 'data uploaded': 'dati caricati',
'Database': 'Database', 'Database': 'Database',
'database': 'database', 'Database %s select': 'Database %s select',
'database %s select': 'database %s select',
'db': 'db', 'db': 'db',
'DB Model': 'Modello di DB', 'DB Model': 'Modello di DB',
'Delete': 'Delete', 'Delete': 'Delete',
'Delete:': 'Cancella:', 'Delete:': 'Cancella:',
'Demo': 'Demo',
'Deployment Recipes': 'Deployment Recipes',
'Description': 'Descrizione', 'Description': 'Descrizione',
'design': 'progetta', 'design': 'progetta',
'DISK': 'DISK',
'Disk Cleared': 'Disk Cleared',
'Documentation': 'Documentazione', 'Documentation': 'Documentazione',
"Don't know what to do?": "Don't know what to do?",
'done!': 'fatto!', 'done!': 'fatto!',
'Download': 'Download',
'E-mail': 'E-mail', 'E-mail': 'E-mail',
'Edit': 'Modifica', 'Edit': 'Modifica',
'Edit current record': 'Modifica record corrente', 'Edit current record': 'Modifica record corrente',
'edit profile': 'modifica profilo', 'edit profile': 'modifica profilo',
'Edit This App': 'Modifica questa applicazione', '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', 'export as csv file': 'esporta come file CSV',
'FAQ': 'FAQ',
'First name': 'Nome', '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 ID': 'ID Gruppo',
'Group uniquely assigned to user %(id)s': 'Group uniquely assigned to user %(id)s',
'Groups': 'Groups',
'hello': 'hello', 'hello': 'hello',
'hello world': 'salve mondo', 'hello world': 'salve mondo',
'Hello World': 'Salve Mondo', 'Hello World': 'Salve Mondo',
'Hello World in a flash!': 'Salve Mondo in un flash!', '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', 'Import/Export': 'Importa/Esporta',
'Index': 'Indice', 'Index': 'Indice',
'insert new': 'inserisci nuovo', 'insert new': 'inserisci nuovo',
'insert new %s': 'inserisci nuovo %s', 'insert new %s': 'inserisci nuovo %s',
'Internal State': 'Stato interno', 'Internal State': 'Stato interno',
'Introduction': 'Introduction',
'Invalid email': 'Email non valida', 'Invalid email': 'Email non valida',
'Invalid login': 'Invalid login',
'Invalid Query': 'Richiesta (query) non valida', 'Invalid Query': 'Richiesta (query) non valida',
'invalid request': 'richiesta non valida', 'invalid request': 'richiesta non valida',
'Is Active': 'Is Active',
'Last name': 'Cognome', 'Last name': 'Cognome',
'Layout': 'Layout', 'Layout': 'Layout',
'Layout Plugins': 'Layout Plugins',
'Layouts': 'Layouts',
'Live Chat': 'Live Chat',
'Logged in': 'Logged in',
'login': 'accesso', 'login': 'accesso',
'Login': 'Login',
'logout': 'uscita', 'logout': 'uscita',
'Logout': 'Logout',
'Lost Password': 'Lost Password',
'Lost password?': 'Lost password?',
'lost password?': 'dimenticato la password?', 'lost password?': 'dimenticato la password?',
'Main Menu': 'Menu principale', 'Main Menu': 'Menu principale',
'Manage Cache': 'Manage Cache',
'Menu Model': 'Menu Modelli', 'Menu Model': 'Menu Modelli',
'Modified By': 'Modified By',
'Modified On': 'Modified On',
'My Sites': 'My Sites',
'Name': 'Nome', 'Name': 'Nome',
'New Record': 'Nuovo elemento (record)', 'New Record': 'Nuovo elemento (record)',
'new record inserted': 'nuovo record inserito', 'new record inserted': 'nuovo record inserito',
'next 100 rows': 'prossime 100 righe', 'next 100 rows': 'prossime 100 righe',
'No databases in this application': 'Nessun database presente in questa applicazione', 'No databases in this application': 'Nessun database presente in questa applicazione',
'not authorized': 'non autorizzato', 'not authorized': 'non autorizzato',
'Object or table name': 'Object or table name',
'Online examples': 'Vedere gli esempi', 'Online examples': 'Vedere gli esempi',
'or import from csv file': 'oppure importa da file CSV', 'or import from csv file': 'oppure importa da file CSV',
'Origin': 'Origine', 'Origin': 'Origine',
'Other Plugins': 'Other Plugins',
'Other Recipes': 'Other Recipes',
'Overview': 'Overview',
'Password': 'Password', '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', 'Powered by': 'Powered by',
'Preface': 'Preface',
'previous 100 rows': '100 righe precedenti', 'previous 100 rows': '100 righe precedenti',
'Profile': 'Profile',
'Python': 'Python',
'Query:': 'Richiesta (query):', '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 does not exist': 'il record non esiste',
'record id': 'record id',
'Record ID': 'Record ID', 'Record ID': 'Record ID',
'Record id': 'Record id',
'Register': 'Register',
'register': 'registrazione', 'register': 'registrazione',
'Registration identifier': 'Registration identifier',
'Registration key': 'Chiave di Registazione', '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 ', 'Reset Password key': 'Resetta chiave Password ',
'Role': 'Ruolo', 'Role': 'Ruolo',
'Rows in table': 'Righe nella tabella', 'Rows in Table': 'Righe nella tabella',
'Rows selected': 'Righe selezionate', 'Rows selected': 'Righe selezionate',
'Semantic': 'Semantic',
'Services': 'Services',
'state': 'stato', 'state': 'stato',
'Stylesheet': 'Foglio di stile (stylesheet)', 'Stylesheet': 'Foglio di stile (stylesheet)',
'submit': 'submit',
'Submit': 'Submit',
'Support': 'Support',
'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?', 'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?',
'table': 'tabella', 'Table': 'tabella',
'Table name': 'Nome 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 "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 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)", 'This is a copy of the scaffolding application': "Questa è una copia dell'applicazione di base (scaffold)",
'Timestamp': 'Ora (timestamp)', 'Timestamp': 'Ora (timestamp)',
'Twitter': 'Twitter',
'unable to parse csv file': 'non riesco a decodificare questo file CSV', 'unable to parse csv file': 'non riesco a decodificare questo file CSV',
'Update': 'Update', 'Update': 'Update',
'Update:': 'Aggiorna:', '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).', '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', 'User ID': 'ID Utente',
'Verify Password': 'Verify Password',
'Videos': 'Videos',
'View': 'Vista', 'View': 'Vista',
'Welcome': 'Welcome',
'Welcome %s': 'Benvenuto %s', 'Welcome %s': 'Benvenuto %s',
'Welcome to web2py': 'Benvenuto su web2py', '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', '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 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à', 'You can modify this application and adapt it to your needs': 'Puoi modificare questa applicazione adattandola alle tue necessità',
+7 -7
View File
@@ -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: 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!', 'ATTENTION: you cannot edit the running application!': 'LET OP: je kan de applicatie die nu draait niet editen!',
'Authentication': 'Authenticatie', 'Authentication': 'Authenticatie',
'Available databases and tables': 'Beschikbare databases en tabellen', 'Available Databases and Tables': 'Beschikbare databases en tabellen',
'Back': 'Terug', 'Back': 'Terug',
'Buy this book': 'Koop dit boek', 'Buy this book': 'Koop dit boek',
'Cache': 'Cache', 'Cache': 'Cache',
@@ -95,8 +95,8 @@
'customize me!': 'pas me aan!', 'customize me!': 'pas me aan!',
'data uploaded': 'data geupload', 'data uploaded': 'data geupload',
'Database': 'Database', 'Database': 'Database',
'database': 'database', 'Database': 'Database',
'database %s select': 'database %s select', 'Database %s select': 'Database %s select',
'database administration': 'database administratie', 'database administration': 'database administratie',
'Date and Time': 'Datum en Tijd', 'Date and Time': 'Datum en Tijd',
'db': 'db', 'db': 'db',
@@ -260,9 +260,9 @@
'RAM Cache Keys': 'RAM Cache Keys', 'RAM Cache Keys': 'RAM Cache Keys',
'Ram Cleared': 'Ram Geleegd', 'Ram Cleared': 'Ram Geleegd',
'Recipes': 'Recepten', 'Recipes': 'Recepten',
'record': 'record', 'Record': 'Record',
'record does not exist': 'record bestaat niet', 'record does not exist': 'record bestaat niet',
'record id': 'record id', 'Record id': 'Record id',
'Record ID': 'Record ID', 'Record ID': 'Record ID',
'register': 'registreer', 'register': 'registreer',
'Register': 'Registreer', 'Register': 'Registreer',
@@ -277,7 +277,7 @@
'restore': 'herstel', 'restore': 'herstel',
'revert': 'herstel', 'revert': 'herstel',
'Role': 'Rol', 'Role': 'Rol',
'Rows in table': 'Rijen in tabel', 'Rows in Table': 'Rijen in tabel',
'Rows selected': 'Rijen geselecteerd', 'Rows selected': 'Rijen geselecteerd',
'save': 'bewaar', 'save': 'bewaar',
'Save profile': 'Bewaar profiel', 'Save profile': 'Bewaar profiel',
@@ -300,7 +300,7 @@
'submit': 'submit', 'submit': 'submit',
'Support': 'Support', 'Support': 'Support',
'Sure you want to delete this object?': 'Weet je zeker dat je dit object wilt verwijderen?', 'Sure you want to delete this object?': 'Weet je zeker dat je dit object wilt verwijderen?',
'table': 'Tabel', 'Table': 'Tabel',
'Table name': 'Tabelnaam', 'Table name': 'Tabelnaam',
'test': 'test', 'test': 'test',
'Testing application': 'Applicatie testen', 'Testing application': 'Applicatie testen',
+7 -7
View File
@@ -10,7 +10,7 @@
'%s selected': '%s wybranych', '%s selected': '%s wybranych',
'Administrative interface': 'Kliknij aby przejść do panelu administracyjnego', 'Administrative interface': 'Kliknij aby przejść do panelu administracyjnego',
'Authentication': 'Uwierzytelnienie', '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', 'Cannot be empty': 'Nie może być puste',
'Change Password': 'Zmień hasło', 'Change Password': 'Zmień hasło',
'Check to delete': 'Zaznacz aby usunąć', 'Check to delete': 'Zaznacz aby usunąć',
@@ -59,7 +59,7 @@
'Register': 'Zarejestruj', 'Register': 'Zarejestruj',
'Registration key': 'Klucz rejestracji', 'Registration key': 'Klucz rejestracji',
'Role': 'Rola', 'Role': 'Rola',
'Rows in table': 'Wiersze w tabeli', 'Rows in Table': 'Wiersze w tabeli',
'Rows selected': 'Wybrane wiersze', 'Rows selected': 'Wybrane wiersze',
'Stylesheet': 'Arkusz stylów', 'Stylesheet': 'Arkusz stylów',
'Submit': 'Wyślij', 'Submit': 'Wyślij',
@@ -83,8 +83,8 @@
'change password': 'change password', 'change password': 'change password',
'customize me!': 'dostosuj mnie!', 'customize me!': 'dostosuj mnie!',
'data uploaded': 'dane wysłane', 'data uploaded': 'dane wysłane',
'database': 'baza danych', 'Database': 'baza danych',
'database %s select': 'wybór z bazy danych %s', 'Database %s select': 'wybór z bazy danych %s',
'db': 'baza danych', 'db': 'baza danych',
'design': 'projektuj', 'design': 'projektuj',
'done!': 'zrobione!', 'done!': 'zrobione!',
@@ -99,11 +99,11 @@
'next 100 rows': 'następne 100 wierszy', 'next 100 rows': 'następne 100 wierszy',
'or import from csv file': 'lub zaimportuj z pliku csv', 'or import from csv file': 'lub zaimportuj z pliku csv',
'previous 100 rows': 'poprzednie 100 wierszy', 'previous 100 rows': 'poprzednie 100 wierszy',
'record': 'rekord', 'Record': 'rekord',
'record does not exist': 'rekord nie istnieje', 'record does not exist': 'rekord nie istnieje',
'record id': 'id rekordu', 'Record id': 'id rekordu',
'register': 'register', 'register': 'register',
'state': 'stan', 'state': 'stan',
'table': 'tabela', 'Table': 'tabela',
'unable to parse csv file': 'nie można sparsować pliku csv', 'unable to parse csv file': 'nie można sparsować pliku csv',
} }
+7 -7
View File
@@ -12,7 +12,7 @@
'Access Control': 'Access Control', 'Access Control': 'Access Control',
'Administrative interface': 'Interface administrativa', 'Administrative interface': 'Interface administrativa',
'Ajax Recipes': 'Ajax Recipes', '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', 'Buy this book': 'Buy this book',
'Cannot be empty': 'Não pode ser vazio', 'Cannot be empty': 'Não pode ser vazio',
'Check to delete': 'Marque para apagar', 'Check to delete': 'Marque para apagar',
@@ -79,7 +79,7 @@
'Reset Password key': 'Reset Password key', 'Reset Password key': 'Reset Password key',
'Resources': 'Resources', 'Resources': 'Resources',
'Role': 'Role', 'Role': 'Role',
'Rows in table': 'Linhas na tabela', 'Rows in Table': 'Linhas na tabela',
'Rows selected': 'Linhas selecionadas', 'Rows selected': 'Linhas selecionadas',
'Semantic': 'Semantic', 'Semantic': 'Semantic',
'Services': 'Services', 'Services': 'Services',
@@ -115,8 +115,8 @@
'change password': 'modificar senha', 'change password': 'modificar senha',
'customize me!': 'Personalize-me!', 'customize me!': 'Personalize-me!',
'data uploaded': 'dados enviados', 'data uploaded': 'dados enviados',
'database': 'banco de dados', 'Database': 'banco de dados',
'database %s select': 'Selecionar banco de dados %s', 'Database %s select': 'Selecionar banco de dados %s',
'db': 'bd', 'db': 'bd',
'design': 'design', 'design': 'design',
'done!': 'concluído!', 'done!': 'concluído!',
@@ -132,11 +132,11 @@
'next 100 rows': 'próximas 100 linhas', 'next 100 rows': 'próximas 100 linhas',
'or import from csv file': 'ou importar de um arquivo csv', 'or import from csv file': 'ou importar de um arquivo csv',
'previous 100 rows': '100 linhas anteriores', 'previous 100 rows': '100 linhas anteriores',
'record': 'registro', 'Record': 'registro',
'record does not exist': 'registro não existe', 'record does not exist': 'registro não existe',
'record id': 'id do registro', 'Record id': 'id do registro',
'register': 'Registre-se', 'register': 'Registre-se',
'state': 'estado', 'state': 'estado',
'table': 'tabela', 'Table': 'tabela',
'unable to parse csv file': 'não foi possível analisar arquivo csv', 'unable to parse csv file': 'não foi possível analisar arquivo csv',
} }
+7 -7
View File
@@ -12,7 +12,7 @@
'Administrative interface': 'Painel administrativo', 'Administrative interface': 'Painel administrativo',
'Author Reference Auth User': 'Author Reference Auth User', 'Author Reference Auth User': 'Author Reference Auth User',
'Author Reference Auth User.username': 'Author Reference Auth User.username', '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', 'Cannot be empty': 'não pode ser vazio',
'Category Create': 'Category Create', 'Category Create': 'Category Create',
'Category Select': 'Category Select', 'Category Select': 'Category Select',
@@ -58,7 +58,7 @@
'Powered by': 'Suportado por', 'Powered by': 'Suportado por',
'Query:': 'Interrogação:', 'Query:': 'Interrogação:',
'Replyto Reference Post': 'Replyto Reference Post', 'Replyto Reference Post': 'Replyto Reference Post',
'Rows in table': 'Linhas numa tabela', 'Rows in Table': 'Linhas numa tabela',
'Rows selected': 'Linhas seleccionadas', 'Rows selected': 'Linhas seleccionadas',
'Stylesheet': 'Folha de estilo', 'Stylesheet': 'Folha de estilo',
'Sure you want to delete this object?': 'Tem a certeza que deseja eliminar este objecto?', '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', 'create new post': 'create new post',
'customize me!': 'Personaliza-me!', 'customize me!': 'Personaliza-me!',
'data uploaded': 'informação enviada', 'data uploaded': 'informação enviada',
'database': 'base de dados', 'Database': 'base de dados',
'database %s select': 'selecção de base de dados %s', 'Database %s select': 'selecção de base de dados %s',
'db': 'bd', 'db': 'bd',
'design': 'design', 'design': 'design',
'done!': 'concluído!', 'done!': 'concluído!',
@@ -102,9 +102,9 @@
'next 100 rows': 'próximas 100 linhas', 'next 100 rows': 'próximas 100 linhas',
'or import from csv file': 'ou importe a partir de ficheiro csv', 'or import from csv file': 'ou importe a partir de ficheiro csv',
'previous 100 rows': '100 linhas anteriores', 'previous 100 rows': '100 linhas anteriores',
'record': 'registo', 'Record': 'registo',
'record does not exist': 'registo inexistente', 'record does not exist': 'registo inexistente',
'record id': 'id de registo', 'Record id': 'id de registo',
'register': 'register', 'register': 'register',
'search category': 'search category', 'search category': 'search category',
'search comment': 'search comment', 'search comment': 'search comment',
@@ -116,6 +116,6 @@
'show comment': 'show comment', 'show comment': 'show comment',
'show post': 'show post', 'show post': 'show post',
'state': 'estado', 'state': 'estado',
'table': 'tabela', 'Table': 'tabela',
'unable to parse csv file': 'não foi possível carregar ficheiro csv', 'unable to parse csv file': 'não foi possível carregar ficheiro csv',
} }
+7 -7
View File
@@ -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: 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!', 'ATTENTION: you cannot edit the running application!': 'ATENȚIE: nu puteți edita o aplicație în curs de execuție!',
'Authentication': 'Autentificare', 'Authentication': 'Autentificare',
'Available databases and tables': 'Baze de date și tabele disponibile', 'Available Databases and Tables': 'Baze de date și tabele disponibile',
'Back': 'Înapoi', 'Back': 'Înapoi',
'Buy this book': 'Cumpără această carte', 'Buy this book': 'Cumpără această carte',
'cache': 'cache', 'cache': 'cache',
@@ -92,8 +92,8 @@
'customize me!': 'Personalizează-mă!', 'customize me!': 'Personalizează-mă!',
'data uploaded': 'date încărcate', 'data uploaded': 'date încărcate',
'Database': 'Baza de date', 'Database': 'Baza de date',
'database': 'bază de date', 'Database': 'bază de date',
'database %s select': 'selectare bază de date %s', 'Database %s select': 'selectare bază de date %s',
'database administration': 'administrare bază de date', 'database administration': 'administrare bază de date',
'Date and Time': 'Data și ora', 'Date and Time': 'Data și ora',
'db': 'db', 'db': 'db',
@@ -247,9 +247,9 @@
'Quick Examples': 'Exemple rapide', 'Quick Examples': 'Exemple rapide',
'RAM Cache Keys': 'Chei cache RAM', 'RAM Cache Keys': 'Chei cache RAM',
'Recipes': 'Rețete', 'Recipes': 'Rețete',
'record': 'înregistrare', 'Record': 'înregistrare',
'record does not exist': 'înregistrare inexistentă', 'record does not exist': 'înregistrare inexistentă',
'record id': 'id înregistrare', 'Record id': 'id înregistrare',
'Record ID': 'ID înregistrare', 'Record ID': 'ID înregistrare',
'register': 'înregistrare', 'register': 'înregistrare',
'Register': 'Înregistrare', 'Register': 'Înregistrare',
@@ -264,7 +264,7 @@
'restore': 'restaurare', 'restore': 'restaurare',
'revert': 'revenire', 'revert': 'revenire',
'Role': 'Rol', 'Role': 'Rol',
'Rows in table': 'Linii în tabel', 'Rows in Table': 'Linii în tabel',
'Rows selected': 'Linii selectate', 'Rows selected': 'Linii selectate',
'save': 'salvare', 'save': 'salvare',
'Save profile': 'Salvează profil', 'Save profile': 'Salvează profil',
@@ -284,7 +284,7 @@
'Submit': 'Înregistrează', 'Submit': 'Înregistrează',
'Support': 'Suport', 'Support': 'Suport',
'Sure you want to delete this object?': 'Sigur ștergeți acest obiect?', 'Sure you want to delete this object?': 'Sigur ștergeți acest obiect?',
'table': 'tabel', 'Table': 'tabel',
'Table name': 'Nume tabel', 'Table name': 'Nume tabel',
'test': 'test', 'test': 'test',
'Testing application': 'Testare aplicație', 'Testing application': 'Testare aplicație',
+6 -6
View File
@@ -28,7 +28,7 @@
'Administrative interface': 'административный интерфейс', 'Administrative interface': 'административный интерфейс',
'Ajax Recipes': 'Ajax Recipes', 'Ajax Recipes': 'Ajax Recipes',
'Are you sure you want to delete this object?': 'Вы уверены, что хотите удалить этот объект?', 'Are you sure you want to delete this object?': 'Вы уверены, что хотите удалить этот объект?',
'Available databases and tables': 'Базы данных и таблицы', 'Available Databases and Tables': 'Базы данных и таблицы',
'Buy this book': 'Buy this book', 'Buy this book': 'Buy this book',
'cache': 'cache', 'cache': 'cache',
'Cannot be empty': 'Пустое значение недопустимо', 'Cannot be empty': 'Пустое значение недопустимо',
@@ -45,9 +45,9 @@
'Current session': 'Текущая сессия', 'Current session': 'Текущая сессия',
'customize me!': 'настройте внешний вид!', 'customize me!': 'настройте внешний вид!',
'data uploaded': 'данные загружены', 'data uploaded': 'данные загружены',
'database': 'база данных', 'Database': 'база данных',
'Database': 'Database', 'Database': 'Database',
'database %s select': 'выбор базы данных %s', 'Database %s select': 'выбор базы данных %s',
'db': 'БД', 'db': 'БД',
'DB Model': 'DB Model', 'DB Model': 'DB Model',
'Delete:': 'Удалить:', 'Delete:': 'Удалить:',
@@ -129,7 +129,7 @@
'Quick Examples': 'Quick Examples', 'Quick Examples': 'Quick Examples',
'Recipes': 'Recipes', 'Recipes': 'Recipes',
'record does not exist': 'запись не найдена', 'record does not exist': 'запись не найдена',
'record id': 'id записи', 'Record id': 'id записи',
'Record ID': 'ID записи', 'Record ID': 'ID записи',
'Register': 'Зарегистрироваться', 'Register': 'Зарегистрироваться',
'Registration identifier': 'Registration identifier', 'Registration identifier': 'Registration identifier',
@@ -137,7 +137,7 @@
'Remember me (for 30 days)': 'Запомнить меня (на 30 дней)', 'Remember me (for 30 days)': 'Запомнить меня (на 30 дней)',
'Reset Password key': 'Сбросить ключ пароля', 'Reset Password key': 'Сбросить ключ пароля',
'Role': 'Роль', 'Role': 'Роль',
'Rows in table': 'Строк в таблице', 'Rows in Table': 'Строк в таблице',
'Rows selected': 'Выделено строк', 'Rows selected': 'Выделено строк',
'Semantic': 'Semantic', 'Semantic': 'Semantic',
'Services': 'Services', 'Services': 'Services',
@@ -146,7 +146,7 @@
'Submit': 'Отправить', 'Submit': 'Отправить',
'Support': 'Support', 'Support': 'Support',
'Sure you want to delete this object?': 'Подтвердите удаление объекта', 'Sure you want to delete this object?': 'Подтвердите удаление объекта',
'table': 'таблица', 'Table': 'таблица',
'Table name': 'Имя таблицы', '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 "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', 'The Core': 'The Core',
+7 -7
View File
@@ -9,7 +9,7 @@
'%s %%{row} updated': '%s upravených záznamov', '%s %%{row} updated': '%s upravených záznamov',
'%s selected': '%s označených', '%s selected': '%s označených',
'Administrative interface': 'pre administrátorské rozhranie kliknite sem', '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', 'Cannot be empty': 'Nemôže byť prázdne',
'Check to delete': 'Označiť na zmazanie', 'Check to delete': 'Označiť na zmazanie',
'Controller': 'Controller', 'Controller': 'Controller',
@@ -56,7 +56,7 @@
'Remember me (for 30 days)': 'Zapamätaj si ma (na 30 dní)', 'Remember me (for 30 days)': 'Zapamätaj si ma (na 30 dní)',
'Reset Password key': 'Nastaviť registračný kľúč', 'Reset Password key': 'Nastaviť registračný kľúč',
'Role': 'Rola', 'Role': 'Rola',
'Rows in table': 'riadkov v tabuľke', 'Rows in Table': 'riadkov v tabuľke',
'Rows selected': 'označených riadkov', 'Rows selected': 'označených riadkov',
'Stylesheet': 'Stylesheet', 'Stylesheet': 'Stylesheet',
'Submit': 'Odoslať', 'Submit': 'Odoslať',
@@ -85,8 +85,8 @@
'cache': 'cache', 'cache': 'cache',
'customize me!': 'prispôsob ma!', 'customize me!': 'prispôsob ma!',
'data uploaded': 'údaje naplnené', 'data uploaded': 'údaje naplnené',
'database': 'databáza', 'Database': 'databáza',
'database %s select': 'databáza %s výber', 'Database %s select': 'databáza %s výber',
'db': 'db', 'db': 'db',
'design': 'návrh', 'design': 'návrh',
'done!': 'hotovo!', 'done!': 'hotovo!',
@@ -102,11 +102,11 @@
'or import from csv file': 'alebo naimportovať z csv súboru', 'or import from csv file': 'alebo naimportovať z csv súboru',
'password': 'heslo', 'password': 'heslo',
'previous 100 rows': 'predchádzajúcich 100 riadkov', 'previous 100 rows': 'predchádzajúcich 100 riadkov',
'record': 'záznam', 'Record': 'záznam',
'record does not exist': 'záznam neexistuje', 'record does not exist': 'záznam neexistuje',
'record id': 'id záznamu', 'Record id': 'id záznamu',
'register': 'registrovať', 'register': 'registrovať',
'state': 'stav', 'state': 'stav',
'table': 'tabuľka', 'Table': 'tabuľka',
'unable to parse csv file': 'nedá sa načítať csv súbor', 'unable to parse csv file': 'nedá sa načítať csv súbor',
} }
+7 -7
View File
@@ -36,7 +36,7 @@
'Ajax Recipes': 'Рецепти для Ajax', 'Ajax Recipes': 'Рецепти для Ajax',
'appadmin is disabled because insecure channel': 'використовується незахищенний канал (HTTP). Appadmin вимкнено', 'appadmin is disabled because insecure channel': 'використовується незахищенний канал (HTTP). Appadmin вимкнено',
'Are you sure you want to delete this object?': "Ви впевнені, що хочете вилучити цей об'єкт?", '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': 'кеш',
'Cache': 'Кеш', 'Cache': 'Кеш',
@@ -58,9 +58,9 @@
'Current session': 'Поточна сесія (current session)', 'Current session': 'Поточна сесія (current session)',
'customize me!': 'причепуріть мене!', 'customize me!': 'причепуріть мене!',
'data uploaded': 'дані завантажено', 'data uploaded': 'дані завантажено',
'database': 'база даних', 'Database': 'база даних',
'Database': 'База даних', 'Database': 'База даних',
'database %s select': 'Вибірка з бази даних %s', 'Database %s select': 'Вибірка з бази даних %s',
'db': 'база даних', 'db': 'база даних',
'DB Model': 'Модель БД', 'DB Model': 'Модель БД',
'Delete:': 'Вилучити:', 'Delete:': 'Вилучити:',
@@ -157,10 +157,10 @@
'RAM Cache Keys': 'Ключі ОЗП-кешу', 'RAM Cache Keys': 'Ключі ОЗП-кешу',
'Ram Cleared': 'ОЗП-кеш очищено', 'Ram Cleared': 'ОЗП-кеш очищено',
'Recipes': 'Рецепти', 'Recipes': 'Рецепти',
'record': 'запис', 'Record': 'запис',
'Record %(id)s updated': 'Запис %(id)s змінено', 'Record %(id)s updated': 'Запис %(id)s змінено',
'record does not exist': 'запису не існує', 'record does not exist': 'запису не існує',
'record id': 'ід. запису', 'Record id': 'ід. запису',
'Record ID': 'Ід.запису', 'Record ID': 'Ід.запису',
'Record Updated': 'Запис змінено', 'Record Updated': 'Запис змінено',
'Register': 'Реєстрація', 'Register': 'Реєстрація',
@@ -171,7 +171,7 @@
'Request reset password': 'Запит на зміну пароля', 'Request reset password': 'Запит на зміну пароля',
'Reset Password key': 'Ключ скидання пароля', 'Reset Password key': 'Ключ скидання пароля',
'Role': 'Роль', 'Role': 'Роль',
'Rows in table': 'Рядки в таблиці', 'Rows in Table': 'Рядки в таблиці',
'Rows selected': 'Відмічено рядків', 'Rows selected': 'Відмічено рядків',
'Save profile': 'Зберегти параметри', 'Save profile': 'Зберегти параметри',
'Semantic': 'Семантика', 'Semantic': 'Семантика',
@@ -183,7 +183,7 @@
'submit': 'submit', 'submit': 'submit',
'Submit': 'Застосувати', 'Submit': 'Застосувати',
'Support': 'Підтримка', '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 "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': 'Ядро',
'The output of the file is a dictionary that was rendered by the view %s': 'Результат функції - словник пар (назва=значення) було відображено з допомогою відображення (view) %s', 'The output of the file is a dictionary that was rendered by the view %s': 'Результат функції - словник пар (назва=значення) було відображено з допомогою відображення (view) %s',
+7 -7
View File
@@ -24,7 +24,7 @@
'Are you sure you want to uninstall application "%s"': '確定要移除應用程式 "%s"', 'Are you sure you want to uninstall application "%s"': '確定要移除應用程式 "%s"',
'Are you sure you want to uninstall application "%s"?': '確定要移除應用程式 "%s"', 'Are you sure you want to uninstall application "%s"?': '確定要移除應用程式 "%s"',
'Authentication': '驗證', 'Authentication': '驗證',
'Available databases and tables': '可提供的資料庫和資料表', 'Available Databases and Tables': '可提供的資料庫和資料表',
'Cannot be empty': '不可空白', 'Cannot be empty': '不可空白',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': '無法編譯:應用程式中含有錯誤,請除錯後再試一次.', 'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': '無法編譯:應用程式中含有錯誤,請除錯後再試一次.',
'Change Password': '變更密碼', 'Change Password': '變更密碼',
@@ -101,7 +101,7 @@
'Reset Password key': '重設密碼', 'Reset Password key': '重設密碼',
'Resolve Conflict file': '解決衝突檔案', 'Resolve Conflict file': '解決衝突檔案',
'Role': '角色', 'Role': '角色',
'Rows in table': '在資料表裏的資料', 'Rows in Table': '在資料表裏的資料',
'Rows selected': '筆資料被選擇', 'Rows selected': '筆資料被選擇',
'Saved file hash:': '檔案雜湊值已紀錄:', 'Saved file hash:': '檔案雜湊值已紀錄:',
'Static files': '靜態檔案', 'Static files': '靜態檔案',
@@ -144,8 +144,8 @@
'change password': '變更密碼', 'change password': '變更密碼',
'customize me!': '請調整我!', 'customize me!': '請調整我!',
'data uploaded': '資料已上傳', 'data uploaded': '資料已上傳',
'database': '資料庫', 'Database': '資料庫',
'database %s select': '已選擇 %s 資料庫', 'Database %s select': '已選擇 %s 資料庫',
'db': 'db', 'db': 'db',
'design': '設計', 'design': '設計',
'done!': '完成!', 'done!': '完成!',
@@ -160,11 +160,11 @@
'next 100 rows': '往後 100 筆', 'next 100 rows': '往後 100 筆',
'or import from csv file': '或是從逗號分隔檔(CSV)匯入', 'or import from csv file': '或是從逗號分隔檔(CSV)匯入',
'previous 100 rows': '往前 100 筆', 'previous 100 rows': '往前 100 筆',
'record': '紀錄', 'Record': '紀錄',
'record does not exist': '紀錄不存在', 'record does not exist': '紀錄不存在',
'record id': '紀錄編號', 'Record id': '紀錄編號',
'register': '註冊', 'register': '註冊',
'state': '狀態', 'state': '狀態',
'table': '資料表', 'Table': '資料表',
'unable to parse csv file': '無法解析逗號分隔檔(csv)', 'unable to parse csv file': '無法解析逗號分隔檔(csv)',
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

+26 -21
View File
@@ -10,8 +10,9 @@
//--></script> //--></script>
{{if request.function=='index':}} {{if request.function=='index':}}
<h2>{{=T("Available databases and tables")}}</h2> <h2>{{=T("Available Databases and Tables")}}</h2>
{{if not databases:}}{{=T("No databases in this application")}}{{pass}} {{if not databases:}}{{=T("No databases in this application")}}{{pass}}
<table>
{{for db in sorted(databases):}} {{for db in sorted(databases):}}
{{for table in databases[db].tables:}} {{for table in databases[db].tables:}}
{{qry='%s.%s.id>0'%(db,table)}} {{qry='%s.%s.id>0'%(db,table)}}
@@ -28,20 +29,24 @@
{{qry=''}} {{qry=''}}
{{pass}} {{pass}}
{{pass}} {{pass}}
<h3> <tr>
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}} <th style="font-size: 1.75em;">
</h3> {{=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]))}} ] </th>
<br /><br /> <td>
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
</td>
</tr>
{{pass}} {{pass}}
</table>
{{pass}} {{pass}}
{{elif request.function=='select':}} {{elif request.function=='select':}}
<h2>{{=XML(str(T("database %s select"))%A(request.args[0],_href=URL('index'))) }} <h2>{{=XML(str(T("Database %s select"))%A(request.args[0],_href=URL('index'))) }}
</h2> </h2>
{{if table:}} {{if table:}}
[ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]<br/><br/> {{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
<h3>{{=T("Rows in table")}}</h3><br/> <h3>{{=T("Rows in Table")}}</h3><br/>
{{else:}} {{else:}}
<h3>{{=T("Rows selected")}}</h3><br/> <h3>{{=T("Rows selected")}}</h3><br/>
{{pass}} {{pass}}
@@ -51,8 +56,8 @@
{{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}</p> {{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}</p>
<br/><br/> <br/><br/>
<h4>{{=T("%s selected", nrows)}}</h4> <h4>{{=T("%s selected", nrows)}}</h4>
{{if start>0:}}[ {{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)))}} ]{{pass}} {{if start>0:}}{{=A(T('previous 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start-100)),_class="btn")}}{{pass}}
{{if stop<nrows:}}[ {{=A(T('next 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start+100)))}} ]{{pass}} {{if stop<nrows:}}{{=A(T('next 100 rows'),_href=URL('select',args=request.args[0],vars=dict(start=start+100)),_class="btn")}}{{pass}}
{{if rows:}} {{if rows:}}
<div style="overflow: auto;" width="80%"> <div style="overflow: auto;" width="80%">
{{linkto=URL('update',args=request.args[0])}} {{linkto=URL('update',args=request.args[0])}}
@@ -61,35 +66,35 @@
</div> </div>
{{pass}} {{pass}}
<br/><br/><h3>{{=T("Import/Export")}}</h3><br/> <br/><br/><h3>{{=T("Import/Export")}}</h3><br/>
[ <a href="{{=URL('csv',args=request.args[0],vars=dict(query=query))}}">{{=T("export as csv file")}}</a> ] <a href="{{=URL('csv',args=request.args[0],vars=dict(query=query))}}" class="btn">{{=T("export as csv file")}}</a>
{{=formcsv or ''}} {{=formcsv or ''}}
{{elif request.function=='insert':}} {{elif request.function=='insert':}}
<h2>{{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} <h2>{{=T("Database")}} {{=A(request.args[0],_href=URL('index'))}}
{{if hasattr(table,'_primarykey'):}} {{if hasattr(table,'_primarykey'):}}
{{fieldname=table._primarykey[0]}} {{fieldname=table._primarykey[0]}}
{{dbname=request.args[0]}} {{dbname=request.args[0]}}
{{tablename=request.args[1]}} {{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} {{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:}} {{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}} {{pass}}
</h2> </h2>
<h3>{{=T("New Record")}}</h3><br/> <h3>{{=T("New Record")}}</h3><br/>
{{=form}} {{=form}}
{{elif request.function=='update':}} {{elif request.function=='update':}}
<h2>{{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} <h2>{{=T("Database")}} {{=A(request.args[0],_href=URL('index'))}}
{{if hasattr(table,'_primarykey'):}} {{if hasattr(table,'_primarykey'):}}
{{fieldname=request.vars.keys()[0]}} {{fieldname=request.vars.keys()[0]}}
{{dbname=request.args[0]}} {{dbname=request.args[0]}}
{{tablename=request.args[1]}} {{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}} {{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))))}}
{{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}} {{=T("Record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
{{else:}} {{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]))))}}
{{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}} {{=T("Record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
{{pass}} {{pass}}
</h2> </h2>
<h3>{{=T("Edit current record")}}</h3><br/><br/>{{=form}} <h3>{{=T("Edit current record")}}</h3><br/><br/>{{=form}}
+1 -1
View File
@@ -298,7 +298,7 @@ class CacheOnDisk(CacheAbstract):
try: try:
storage = self._open_shelf_with_lock() storage = self._open_shelf_with_lock()
try: try:
if not storage.has_key(CacheAbstract.cache_stats_name): if not CacheAbstract.cache_stats_name in storage:
storage[CacheAbstract.cache_stats_name] = { storage[CacheAbstract.cache_stats_name] = {
'hit_total': 0, 'hit_total': 0,
'misses': 0, 'misses': 0,
+41 -40
View File
@@ -50,6 +50,8 @@ is_pypy = settings.global_settings.is_pypy
is_gae = settings.global_settings.web2py_runtime_gae is_gae = settings.global_settings.web2py_runtime_gae
is_jython = settings.global_settings.is_jython is_jython = settings.global_settings.is_jython
pjoin = os.path.join
TEST_CODE = \ TEST_CODE = \
r""" r"""
def _TEST(): def _TEST():
@@ -319,7 +321,7 @@ def local_import_aux(name, reload_force=False, app='welcome'):
""" """
OLD IMPLEMENTATION: OLD IMPLEMENTATION:
items = name.replace('/','.').split('.') 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() imp.acquire_lock()
try: try:
file=None 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. Build the environment dictionary into which web2py files are executed.
""" """
h,v = html,validators
_validators = validators environment = dict((k,getattr(h,k)) for k in h.__all__)
_html = html environment.update((k,getattr(v, k)) for k in v.__all__)
environment = dict(map(lambda key: (key, getattr(_html, key)), _html.__all__))
environment.update(map(lambda key: (key, getattr(_validators, key)), _validators.__all__))
if not request.env: if not request.env:
request.env = Storage() request.env = Storage()
@@ -389,7 +389,7 @@ def build_environment(request, response, session, store_current=True):
environment['local_import'] = \ environment['local_import'] = \
lambda name, reload=False, app=request.application:\ lambda name, reload=False, app=request.application:\
local_import_aux(name,reload,app) 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) response._view_environment = copy.copy(environment)
return environment return environment
@@ -419,11 +419,11 @@ def compile_views(folder):
Compiles all the views in the application specified by `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+)+$'): for file in listdir(path, '^[\w/\-]+(\.\w+)+$'):
data = parse_template(file, path) data = parse_template(file, path)
filename = ('views/%s.py' % file).replace('/', '_').replace('\\', '_') filename = ('views/%s.py' % file).replace('/', '_').replace('\\', '_')
filename = os.path.join(folder, 'compiled', filename) filename = pjoin(folder, 'compiled', filename)
write_file(filename, data) write_file(filename, data)
save_pyc(filename) save_pyc(filename)
os.unlink(filename) os.unlink(filename)
@@ -434,10 +434,10 @@ def compile_models(folder):
Compiles all the models in the application specified by `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$'): for file in listdir(path, '.+\.py$'):
data = read_file(os.path.join(path, file)) data = read_file(pjoin(path, file))
filename = os.path.join(folder, 'compiled','models',file) filename = pjoin(folder, 'compiled','models',file)
mktree(filename) mktree(filename)
write_file(filename, data) write_file(filename, data)
save_pyc(filename) save_pyc(filename)
@@ -449,15 +449,15 @@ def compile_controllers(folder):
Compiles all the controllers in the application specified by `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$'): for file in listdir(path, '.+\.py$'):
### why is this here? save_pyc(os.path.join(path, file)) ### why is this here? save_pyc(pjoin(path, file))
data = read_file(os.path.join(path,file)) data = read_file(pjoin(path,file))
exposed = regex_expose.findall(data) exposed = regex_expose.findall(data)
for function in exposed: for function in exposed:
command = data + "\nresponse._vars=response._caller(%s)\n" % \ command = data + "\nresponse._vars=response._caller(%s)\n" % \
function function
filename = os.path.join(folder, 'compiled', ('controllers/' filename = pjoin(folder, 'compiled', ('controllers/'
+ file[:-3]).replace('/', '_') + file[:-3]).replace('/', '_')
+ '_' + function + '.py') + '_' + function + '.py')
write_file(filename, command) write_file(filename, command)
@@ -474,18 +474,18 @@ def run_models_in(environment):
folder = environment['request'].folder folder = environment['request'].folder
c = environment['request'].controller c = environment['request'].controller
f = environment['request'].function f = environment['request'].function
cpath = os.path.join(folder, 'compiled') cpath = pjoin(folder, 'compiled')
if os.path.exists(cpath): if os.path.exists(cpath):
for model in listdir(cpath, '^models_\w+\.pyc$', 0): for model in listdir(cpath, '^models_\w+\.pyc$', 0):
restricted(read_pyc(model), environment, layer=model) 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) models = listdir(path, '^\w+\.pyc$',0,sort=False)
compiled=True compiled=True
else: else:
path = os.path.join(folder, 'models') path = pjoin(folder, 'models')
models = listdir(path, '^\w+\.py$',0,sort=False) models = listdir(path, '^\w+\.py$',0,sort=False)
compiled=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: for model in models:
if not os.path.split(model)[0] in paths and c!='appadmin': if not os.path.split(model)[0] in paths and c!='appadmin':
continue continue
@@ -509,11 +509,11 @@ def run_controller_in(controller, function, environment):
# if compiled should run compiled! # if compiled should run compiled!
folder = environment['request'].folder folder = environment['request'].folder
path = os.path.join(folder, 'compiled') path = pjoin(folder, 'compiled')
badc = 'invalid controller (%s/%s)' % (controller, function) badc = 'invalid controller (%s/%s)' % (controller, function)
badf = 'invalid function (%s/%s)' % (controller, function) badf = 'invalid function (%s/%s)' % (controller, function)
if os.path.exists(path): if os.path.exists(path):
filename = os.path.join(path, 'controllers_%s_%s.pyc' filename = pjoin(path, 'controllers_%s_%s.pyc'
% (controller, function)) % (controller, function))
if not os.path.exists(filename): if not os.path.exists(filename):
raise HTTP(404, raise HTTP(404,
@@ -528,7 +528,7 @@ def run_controller_in(controller, function, environment):
[add_path_first(path) for path in paths] [add_path_first(path) for path in paths]
# TESTING END # TESTING END
filename = os.path.join(folder, 'controllers/%s.py' filename = pjoin(folder, 'controllers/%s.py'
% controller) % controller)
if not os.path.exists(filename): if not os.path.exists(filename):
raise HTTP(404, raise HTTP(404,
@@ -539,7 +539,7 @@ def run_controller_in(controller, function, environment):
code += TEST_CODE code += TEST_CODE
restricted(code, environment, layer=filename) restricted(code, environment, layer=filename)
else: else:
filename = os.path.join(folder, 'controllers/%s.py' filename = pjoin(folder, 'controllers/%s.py'
% controller) % controller)
if not os.path.exists(filename): if not os.path.exists(filename):
raise HTTP(404, raise HTTP(404,
@@ -576,19 +576,20 @@ def run_view_in(environment):
request = environment['request'] request = environment['request']
response = environment['response'] response = environment['response']
view = response.view
folder = request.folder folder = request.folder
path = os.path.join(folder, 'compiled') path = pjoin(folder, 'compiled')
badv = 'invalid view (%s)' % response.view badv = 'invalid view (%s)' % view
patterns = response.generic_patterns or [] patterns = response.generic_patterns or []
regex = re.compile('|'.join(map(fnmatch.translate, patterns))) regex = re.compile('|'.join(map(fnmatch.translate, patterns)))
short_action = '%(controller)s/%(function)s.%(extension)s' % request short_action = '%(controller)s/%(function)s.%(extension)s' % request
allow_generic = patterns and regex.search(short_action) allow_generic = patterns and regex.search(short_action)
if not isinstance(response.view, str): if not isinstance(view, str):
ccode = parse_template(response.view, os.path.join(folder, 'views'), ccode = parse_template(view, pjoin(folder, 'views'),
context=environment) context=environment)
restricted(ccode, environment, 'file stream') restricted(ccode, environment, 'file stream')
elif os.path.exists(path): elif os.path.exists(path):
x = response.view.replace('/', '_') x = view.replace('/', '_')
files = ['views_%s.pyc' % x] files = ['views_%s.pyc' % x]
if allow_generic: if allow_generic:
files.append('views_generic.%s.pyc' % request.extension) files.append('views_generic.%s.pyc' % request.extension)
@@ -599,7 +600,7 @@ def run_view_in(environment):
files.append('views_generic.pyc') files.append('views_generic.pyc')
# end backward compatibility code # end backward compatibility code
for f in files: for f in files:
filename = os.path.join(path,f) filename = pjoin(path,f)
if os.path.exists(filename): if os.path.exists(filename):
code = read_pyc(filename) code = read_pyc(filename)
restricted(code, environment, layer=filename) restricted(code, environment, layer=filename)
@@ -608,10 +609,10 @@ def run_view_in(environment):
rewrite.thread.routes.error_message % badv, rewrite.thread.routes.error_message % badv,
web2py_error=badv) web2py_error=badv)
else: else:
filename = os.path.join(folder, 'views', response.view) filename = pjoin(folder, 'views', view)
if not os.path.exists(filename) and allow_generic: if not os.path.exists(filename) and allow_generic:
response.view = 'generic.' + request.extension view = 'generic.' + request.extension
filename = os.path.join(folder, 'views', response.view) filename = pjoin(folder, 'views', view)
if not os.path.exists(filename): if not os.path.exists(filename):
raise HTTP(404, raise HTTP(404,
rewrite.thread.routes.error_message % badv, rewrite.thread.routes.error_message % badv,
@@ -619,12 +620,12 @@ def run_view_in(environment):
layer = filename layer = filename
if is_gae: if is_gae:
ccode = getcfs(layer, filename, ccode = getcfs(layer, filename,
lambda: compile2(parse_template(response.view, lambda: compile2(parse_template(view,
os.path.join(folder, 'views'), pjoin(folder, 'views'),
context=environment),layer)) context=environment),layer))
else: else:
ccode = parse_template(response.view, ccode = parse_template(view,
os.path.join(folder, 'views'), pjoin(folder, 'views'),
context=environment) context=environment)
restricted(ccode, environment, layer) restricted(ccode, environment, layer)
@@ -633,8 +634,8 @@ def remove_compiled_application(folder):
Deletes the folder `compiled` containing the compiled application. Deletes the folder `compiled` containing the compiled application.
""" """
try: try:
shutil.rmtree(os.path.join(folder, 'compiled')) shutil.rmtree(pjoin(folder, 'compiled'))
path = os.path.join(folder, 'controllers') path = pjoin(folder, 'controllers')
for file in listdir(path,'.*\.pyc$',drop=False): for file in listdir(path,'.*\.pyc$',drop=False):
os.unlink(file) os.unlink(file)
except OSError: except OSError:
@@ -646,7 +647,7 @@ def compile_application(folder):
Compiles all models, views, controller for the application in `folder`. Compiles all models, views, controller for the application in `folder`.
""" """
remove_compiled_application(folder) remove_compiled_application(folder)
os.mkdir(os.path.join(folder, 'compiled')) os.mkdir(pjoin(folder, 'compiled'))
compile_models(folder) compile_models(folder)
compile_controllers(folder) compile_controllers(folder)
compile_views(folder) compile_views(folder)
+61 -48
View File
@@ -117,7 +117,8 @@ class Request(Storage):
user_agent_parser.detect(self.env.http_user_agent) user_agent_parser.detect(self.env.http_user_agent)
user_agent = Storage(user_agent) user_agent = Storage(user_agent)
for key,value in user_agent.items(): 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 return user_agent
def requires_https(self): def requires_https(self):
@@ -222,9 +223,9 @@ class Response(Storage):
return page return page
def include_meta(self): def include_meta(self):
s = '\n' s = '\n'.join(
for key,value in (self.meta or {}).items(): '<meta name="%s" content="%s" />\n' % (k,xmlescape(v))
s += '<meta name="%s" content="%s" />\n' % (key,xmlescape(value)) for k,v in (self.meta or {}).iteritems())
self.write(s,escape=False) self.write(s,escape=False)
def include_files(self): def include_files(self):
@@ -297,14 +298,15 @@ class Response(Storage):
default to the last request argument otherwise) default to the last request argument otherwise)
""" """
headers = self.headers
# for attachment settings and backward compatibility # for attachment settings and backward compatibility
keys = [item.lower() for item in self.headers] keys = [item.lower() for item in headers]
if attachment: if attachment:
if filename is None: if filename is None:
attname = "" attname = ""
else: else:
attname = filename attname = filename
self.headers["Content-Disposition"] = \ headers["Content-Disposition"] = \
"attachment;filename=%s" % attname "attachment;filename=%s" % attname
if not request: if not request:
@@ -313,30 +315,31 @@ class Response(Storage):
stream_file_or_304_or_206(stream, stream_file_or_304_or_206(stream,
chunk_size=chunk_size, chunk_size=chunk_size,
request=request, request=request,
headers=self.headers) headers=headers)
# ## the following is for backward compatibility # ## the following is for backward compatibility
if hasattr(stream, 'name'): if hasattr(stream, 'name'):
filename = stream.name filename = stream.name
if filename and not 'content-type' in keys: 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: if filename and not 'content-length' in keys:
try: try:
self.headers['Content-Length'] = \ headers['Content-Length'] = \
os.path.getsize(filename) os.path.getsize(filename)
except OSError: except OSError:
pass pass
env = request.env
# Internet Explorer < 9.0 will not allow downloads over SSL unless caching is enabled # 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 \ if request.is_https and isinstance(env.http_user_agent,str) and \
not re.search(r'Opera', request.env.http_user_agent) and \ not re.search(r'Opera', env.http_user_agent) and \
re.search(r'MSIE [5-8][^0-9]', request.env.http_user_agent): re.search(r'MSIE [5-8][^0-9]', env.http_user_agent):
self.headers['Pragma'] = 'cache' headers['Pragma'] = 'cache'
self.headers['Cache-Control'] = 'private' headers['Cache-Control'] = 'private'
if request and request.env.web2py_use_wsgi_file_wrapper: if request and env.web2py_use_wsgi_file_wrapper:
wrapped = request.env.wsgi_file_wrapper(stream, chunk_size) wrapped = env.wsgi_file_wrapper(stream, chunk_size)
else: else:
wrapped = streamer(stream, chunk_size=chunk_size) wrapped = streamer(stream, chunk_size=chunk_size)
return wrapped return wrapped
@@ -364,11 +367,13 @@ class Response(Storage):
(filename, stream) = field.retrieve(name) (filename, stream) = field.retrieve(name)
except IOError: except IOError:
raise HTTP(404) raise HTTP(404)
self.headers['Content-Type'] = contenttype(name) headers = self.headers
headers['Content-Type'] = contenttype(name)
if attachment: if attachment:
self.headers['Content-Disposition'] = \ headers['Content-Disposition'] = \
"attachment; filename=%s" % filename 'attachment; filename=%s' % filename
return self.stream(stream, chunk_size = chunk_size, request=request) return self.stream(stream, chunk_size=chunk_size, request=request)
def json(self, data, default=None): def json(self, data, default=None):
return json(data, default = default or custom_json) 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)) \ dbstats = [TABLE(*[TR(PRE(row[0]),'%.2fms' % (row[1]*1000)) \
for row in i.db._timings]) \ for row in i.db._timings]) \
for i in thread.instances] 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: else:
dbstats = [] # if no db or on GAE dbstats = [] # if no db or on GAE
dbtables = {}
u = web2py_uuid() u = web2py_uuid()
return DIV( return DIV(
BUTTON('design',_onclick="document.location='%s'" % admin), BUTTON('design',_onclick="document.location='%s'" % admin),
@@ -416,6 +428,8 @@ class Response(Storage):
DIV(BEAUTIFY(current.session),_class="hidden",_id="session-%s"%u), DIV(BEAUTIFY(current.session),_class="hidden",_id="session-%s"%u),
BUTTON('response',_onclick="jQuery('#response-%s').slideToggle()"%u), BUTTON('response',_onclick="jQuery('#response-%s').slideToggle()"%u),
DIV(BEAUTIFY(current.response),_class="hidden",_id="response-%s"%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), BUTTON('db stats',_onclick="jQuery('#db-stats-%s').slideToggle()"%u),
DIV(BEAUTIFY(dbstats),_class="hidden",_id="db-stats-%s"%u), DIV(BEAUTIFY(dbstats),_class="hidden",_id="db-stats-%s"%u),
SCRIPT("jQuery('.hidden').hide()") SCRIPT("jQuery('.hidden').hide()")
@@ -452,14 +466,15 @@ class Session(Storage):
response.session_id_name = 'session_id_%s' % masterapp.lower() response.session_id_name = 'session_id_%s' % masterapp.lower()
# Load session data from cookie # Load session data from cookie
cookies = request.cookies
if cookie_key: if cookie_key:
response.session_cookie_key = cookie_key response.session_cookie_key = cookie_key
response.session_cookie_key2 = hashlib.md5(cookie_key).digest() 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 response.session_cookie_name = cookie_name
if cookie_data in request.cookies: if cookie_name in cookies:
cookie_value = request.cookies[cookie_name].value cookie_value = cookies[cookie_name].value
cookie_parts = cookie_value.split(":") cookie_parts = cookie_value.split(":")
enc = cookie_parts[2] enc = cookie_parts[2]
cipher = AES.new(cookie_key) cipher = AES.new(cookie_key)
@@ -477,9 +492,9 @@ class Session(Storage):
return return
response.session_new = False response.session_new = False
client = request.client and request.client.replace(':', '.') 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 = \ response.session_id = \
request.cookies[response.session_id_name].value cookies[response.session_id_name].value
if regex_session_id.match(response.session_id): if regex_session_id.match(response.session_id):
response.session_filename = \ response.session_filename = \
os.path.join(up(request.folder), masterapp, os.path.join(up(request.folder), masterapp,
@@ -532,38 +547,35 @@ class Session(Storage):
table_migrate = False table_migrate = False
tname = tablename + '_' + masterapp tname = tablename + '_' + masterapp
table = db.get(tname, None) table = db.get(tname, None)
Field = db.Field
if table is None: if table is None:
table = db.define_table( db.define_table(
tname, tname,
db.Field('locked', 'boolean', default=False), Field('locked', 'boolean', default=False),
db.Field('client_ip', length=64), Field('client_ip', length=64),
db.Field('created_datetime', 'datetime', Field('created_datetime', 'datetime',
default=request.now), default=request.now),
db.Field('modified_datetime', 'datetime'), Field('modified_datetime', 'datetime'),
db.Field('unique_key', length=64), Field('unique_key', length=64),
db.Field('session_data', 'blob'), Field('session_data', 'blob'),
migrate=table_migrate, migrate=table_migrate,
) )
table = db[tname] # to allow for lazy table
try: try:
# Get session data out of the database # Get session data out of the database
# Key comes from the cookie
# Key comes from the cookie key = cookies[response.session_id_name].value
key = request.cookies[response.session_id_name].value
(record_id, unique_key) = key.split(':') (record_id, unique_key) = key.split(':')
if record_id == '0': if record_id == '0':
raise Exception, 'record_id == 0' raise Exception, 'record_id == 0'
# Select from database. # Select from database
rows = db(table.id == record_id).select() 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: if len(rows) == 0 or rows[0].unique_key != unique_key:
raise Exception, 'No record' raise Exception, 'No record'
# rows[0].update_record(locked=True)
# rows[0].update_record(locked=True) # Unpickle the data
# Unpickle the data
session_data = cPickle.loads(rows[0].session_data) session_data = cPickle.loads(rows[0].session_data)
self.update(session_data) self.update(session_data)
except Exception: except Exception:
@@ -573,8 +585,9 @@ class Session(Storage):
response._dbtable_and_field = \ response._dbtable_and_field = \
(response.session_id_name, table, record_id, unique_key) (response.session_id_name, table, record_id, unique_key)
response.session_id = '%s:%s' % (record_id, unique_key) response.session_id = '%s:%s' % (record_id, unique_key)
response.cookies[response.session_id_name] = response.session_id rcookies = response.cookies
response.cookies[response.session_id_name]['path'] = '/' rcookies[response.session_id_name] = response.session_id
rcookies[response.session_id_name]['path'] = '/'
self.__hash = hashlib.md5(str(self)).digest() self.__hash = hashlib.md5(str(self)).digest()
if self.flash: if self.flash:
(response.flash, self.flash) = (self.flash, None) (response.flash, self.flash) = (self.flash, None)
+39 -33
View File
@@ -296,7 +296,8 @@ def URL(
if other.endswith('/'): if other.endswith('/'):
other += '/' # add trailing slash to make last trailing empty arg explicit 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 = [] list_vars = []
for (key, vals) in sorted(vars.items()): for (key, vals) in sorted(vars.items()):
if not isinstance(vals, (list, tuple)): 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 return False # no signature in the request URL
# check if user_signature requires # check if user_signature requires
@@ -484,15 +485,17 @@ class XmlComponent(object):
components += [other] components += [other]
return CAT(*components) return CAT(*components)
def add_class(self, name): def add_class(self, name):
""" add a class to _class attribute """ """ 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 self['_class'] = ' '.join(classes) if classes else None
return self return self
def remove_class(self, name): def remove_class(self, name):
""" remove a class from _class attribute """ """ 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 self['_class'] = ' '.join(classes) if classes else None
return self return self
@@ -656,17 +659,17 @@ class DIV(XmlComponent):
self.attributes = attributes self.attributes = attributes
self._fixup() self._fixup()
# converts special attributes in components attributes # converts special attributes in components attributes
self._postprocessing()
self.parent = None self.parent = None
for c in self.components: for c in self.components:
self._setnode(c) self._setnode(c)
self._postprocessing()
def update(self, **kargs): def update(self, **kargs):
""" """
dictionary like updating of the tag attributes dictionary like updating of the tag attributes
""" """
for (key, value) in kargs.items(): for (key, value) in kargs.iteritems():
self[key] = value self[key] = value
return self return self
@@ -811,7 +814,9 @@ class DIV(XmlComponent):
c.latest = self.latest c.latest = self.latest
c.session = self.session c.session = self.session
c.formname = self.formname 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 newstatus = c._traverse(status,hideerror) and newstatus
# for input, textarea, select, option # for input, textarea, select, option
@@ -1038,7 +1043,7 @@ class DIV(XmlComponent):
tag = getattr(self,'tag').replace('/', '') tag = getattr(self,'tag').replace('/', '')
if args and tag not in args: if args and tag not in args:
check = False check = False
for (key, value) in kargs.items(): for (key, value) in kargs.iteritems():
if key not in ['first_only', 'replace', 'find_text']: if key not in ['first_only', 'replace', 'find_text']:
if isinstance(value, (str, int)): if isinstance(value, (str, int)):
if self[key] != str(value): if self[key] != str(value):
@@ -1109,16 +1114,15 @@ class DIV(XmlComponent):
sibs = [s for s in self.parent.components if not s == self] sibs = [s for s in self.parent.components if not s == self]
matches = [] matches = []
first_only = False first_only = False
if kargs.has_key("first_only"): if 'first_only' in kargs:
first_only = kargs["first_only"] first_only = kargs.pop('first_only')
del kargs["first_only"]
for c in sibs: for c in sibs:
try: try:
check = True check = True
tag = getattr(c,'tag').replace("/","") tag = getattr(c,'tag').replace("/","")
if args and tag not in args: if args and tag not in args:
check = False check = False
for (key, value) in kargs.items(): for (key, value) in kargs.iteritems():
if c[key] != value: if c[key] != value:
check = False check = False
if check: if check:
@@ -1682,14 +1686,14 @@ class INPUT(DIV):
if name is None or name == '': if name is None or name == '':
return True return True
name = str(name) name = str(name)
request_vars_get = self.request_vars.get
if self['_type'] != 'checkbox': if self['_type'] != 'checkbox':
self['old_value'] = self['value'] or self['_value'] or '' self['old_value'] = self['value'] or self['_value'] or ''
value = self.request_vars.get(name, '') value = request_vars_get(name, '')
self['value'] = value self['value'] = value
else: else:
self['old_value'] = self['value'] or False self['old_value'] = self['value'] or False
value = self.request_vars.get(name) value = request_vars_get(name)
if isinstance(value, (tuple, list)): if isinstance(value, (tuple, list)):
self['value'] = self['_value'] in value self['value'] = self['_value'] in value
else: else:
@@ -1932,14 +1936,15 @@ class FORM(DIV):
# check formname and formkey # check formname and formkey
status = True status = True
if self.session: request_vars = self.request_vars
formkey = self.session.get('_formkey[%s]' % self.formname, None) if session:
formkey = session.get('_formkey[%s]' % formname, None)
# check if user tampering with form and void CSRF # check if user tampering with form and void CSRF
if formkey != self.request_vars._formkey: if formkey != request_vars._formkey:
status = False status = False
if self.formname != self.request_vars._formname: if formname != request_vars._formname:
status = False status = False
if status and self.session: if status and session:
# check if editing a record that has been modified by the server # check if editing a record that has been modified by the server
if hasattr(self,'record_hash') and self.record_hash != formkey: if hasattr(self,'record_hash') and self.record_hash != formkey:
status = False status = False
@@ -1983,10 +1988,10 @@ class FORM(DIV):
def hidden_fields(self): def hidden_fields(self):
c = [] c = []
attr = self.attributes.get('hidden',{})
if 'hidden' in self.attributes: if 'hidden' in self.attributes:
for (key, value) in self.attributes.get('hidden',{}).items(): c = [INPUT(_type='hidden', _name=key, _value=value)
c.append(INPUT(_type='hidden', _name=key, _value=value)) for (key, value) in attr.iteritems()]
if hasattr(self, 'formkey') and self.formkey: if hasattr(self, 'formkey') and self.formkey:
c.append(INPUT(_type='hidden', _name='_formkey', c.append(INPUT(_type='hidden', _name='_formkey',
_value=self.formkey)) _value=self.formkey))
@@ -2055,7 +2060,7 @@ class FORM(DIV):
onsuccess(self) onsuccess(self)
if next: if next:
if self.vars: if self.vars:
for key,value in self.vars.items(): for key,value in self.vars.iteritems():
next = next.replace('[%s]' % key, next = next.replace('[%s]' % key,
urllib.quote(str(value))) urllib.quote(str(value)))
if not next.startswith('/'): if not next.startswith('/'):
@@ -2116,11 +2121,11 @@ class FORM(DIV):
inputs = [INPUT(_type='button', inputs = [INPUT(_type='button',
_value=name, _value=name,
_onclick=FORM.REDIRECT_JS % link) \ _onclick=FORM.REDIRECT_JS % link) \
for name,link in buttons.items()] for name,link in buttons.iteritems()]
inputs += [INPUT(_type='hidden', inputs += [INPUT(_type='hidden',
_name=name, _name=name,
_value=value) _value=value)
for name,value in hidden.items()] for name,value in hidden.iteritems()]
form = FORM(INPUT(_type='submit',_value=text),*inputs) form = FORM(INPUT(_type='submit',_value=text),*inputs)
form.process() form.process()
return form return form
@@ -2268,10 +2273,11 @@ class MENU(DIV):
select = SELECT(**self.attributes) select = SELECT(**self.attributes)
for item in data: for item in data:
if len(item) <= 4 or item[4] == True: if len(item) <= 4 or item[4] == True:
if item[2]: select.append(OPTION(CAT(prefix, item[0]),
select.append(OPTION(CAT(prefix, item[0]), _value=item[2], _selected=item[1])) _value=item[2], _selected=item[1]))
if len(item)>3 and len(item[3]): if len(item)>3 and len(item[3]):
self.serialize_mobile(item[3], select, prefix = CAT(prefix, item[0], '/')) self.serialize_mobile(
item[3], select, prefix = CAT(prefix, item[0], '/'))
select['_onchange'] = 'window.location=this.value' select['_onchange'] = 'window.location=this.value'
return select return select
@@ -2323,7 +2329,7 @@ def test():
>>> print form.accepts({'myvar':'34'}, formname=None) >>> print form.accepts({'myvar':'34'}, formname=None)
False False
>>> print form.xml() >>> print form.xml()
<form action="" enctype="multipart/form-data" method="post"><input class="invalidinput" name="myvar" type="text" value="34" /><div class="error" id="myvar__error">invalid expression</div></form> <form action="" enctype="multipart/form-data" method="post"><input class="invalidinput" name="myvar" type="text" value="34" /><div class="error_wrapper"><div class="error" id="myvar__error">invalid expression</div></div></form>
>>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True) >>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
True True
>>> print form.xml() >>> print form.xml()
@@ -2337,7 +2343,7 @@ def test():
>>> print form.accepts({'myvar':'as df'}, formname=None) >>> print form.accepts({'myvar':'as df'}, formname=None)
False False
>>> print form.xml() >>> print form.xml()
<form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input class=\"invalidinput\" name=\"myvar\" type=\"text\" value=\"as df\" /><div class=\"error\" id=\"myvar__error\">only alphanumeric!</div></form> <form action="" enctype="multipart/form-data" method="post"><input class="invalidinput" name="myvar" type="text" value="as df" /><div class="error_wrapper"><div class="error" id="myvar__error">only alphanumeric!</div></div></form>
>>> session={} >>> session={}
>>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$'))) >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$')))
>>> if form.accepts({}, session,formname=None): print 'passed' >>> if form.accepts({}, session,formname=None): print 'passed'
+22 -18
View File
@@ -77,41 +77,45 @@ class HTTP(BaseException):
str(cookie)[11:] for cookie in cookies.values()] str(cookie)[11:] for cookie in cookies.values()]
def to(self, responder): def to(self, responder):
if self.status in defined_status: status = self.status
status = '%d %s' % (self.status, defined_status[self.status]) headers = self.headers
if status in defined_status:
status = '%d %s' % (status, defined_status[status])
else: else:
status = str(self.status) + ' ' status = str(status) + ' '
if not 'Content-Type' in self.headers: if not 'Content-Type' in headers:
self.headers['Content-Type'] = 'text/html; charset=UTF-8' headers['Content-Type'] = 'text/html; charset=UTF-8'
body = self.body body = self.body
if status[:1] == '4': if status[:1] == '4':
if not body: if not body:
body = status body = status
if isinstance(body, str): 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 += '<!-- %s //-->' % ('x'*512) ### trick IE body += '<!-- %s //-->' % ('x'*512) ### trick IE
self.headers['Content-Length'] = len(body) headers['Content-Length'] = len(body)
headers = [] rheaders = []
for (k, v) in self.headers.items(): for k, v in headers.iteritems():
if isinstance(v, list): if isinstance(v, list):
for item in v: rheaders += [(k, str(item)) for item in v]
headers.append((k, str(item)))
else: else:
headers.append((k, str(v))) rheaders.append((k, str(v)))
responder(status, headers) responder(status, rheaders)
if hasattr(body, '__iter__') and not isinstance(self.body, str): if isinstance(body,str):
return [body]
elif hasattr(body, '__iter__'):
return body return body
return [str(body)] else:
return [str(body)]
@property @property
def message(self): def message(self):
''' """
compose a message describing this exception compose a message describing this exception
"status defined_status [web2py_error]" "status defined_status [web2py_error]"
message elements that are not defined are omitted message elements that are not defined are omitted
''' """
msg = '%(status)d' msg = '%(status)d'
if self.status in defined_status: if self.status in defined_status:
msg = '%(status)d %(defined_status)s' msg = '%(status)d %(defined_status)s'
+283 -357
View File
@@ -10,7 +10,7 @@ Plural subsystem is created by Vladyslav Kozlovskyy (Ukraine)
<dbdevelop@gmail.com> <dbdevelop@gmail.com>
""" """
from os import path as ospath, stat as ostat, sep as osep import os
import re import re
from utf8 import Utf8 from utf8 import Utf8
from cgi import escape from cgi import escape
@@ -20,7 +20,7 @@ import marshal
import copy_reg import copy_reg
from fileutils import abspath, listdir from fileutils import abspath, listdir
import settings import settings
from cfs import getcfs, cfs from cfs import getcfs
from thread import allocate_lock from thread import allocate_lock
from html import XML, xmlescape from html import XML, xmlescape
from contrib.markmin.markmin2html import render, markmin_escape from contrib.markmin.markmin2html import render, markmin_escape
@@ -28,10 +28,37 @@ from string import maketrans
__all__ = ['translator', 'findT', 'update_all_languages'] __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() # used as default filter in translator.M()
markmin = lambda s: render( regex_param.sub( def markmin_aux(m):
lambda m: '{' + markmin_escape(m.group('s')) + '}', return '{%s}' % markmin_escape(m.group('s'))
s ), sep='br', autolinks=None, id_prefix='' ) def markmin(s):
return render(regex_param.sub(markmin_aux,s),
sep='br', autolinks=None, id_prefix='')
NUMBERS = (int,long,float) NUMBERS = (int,long,float)
@@ -49,28 +76,24 @@ regex_param=re.compile(r'{(?P<s>.+?)}')
regex_language = \ regex_language = \
re.compile('^([a-zA-Z]{2})(\-[a-zA-Z]{2})?(\-[a-zA-Z]+)?$') 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_langfile = re.compile('^[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$')
regex_langinfo = re.compile("^[^'\"]*['\"]([^'\"]*)['\"]\s*:\s*['\"]([^'\"]*)['\"].*$")
regex_backslash = re.compile(r"\\([\\{}%])") regex_backslash = re.compile(r"\\([\\{}%])")
regex_plural = re.compile('%({.+?})') regex_plural = re.compile('%({.+?})')
regex_plural_dict = re.compile('^{(?P<w>[^()[\]][^()[\]]*?)\((?P<n>[^()\[\]]+)\)}$') # %%{word(varname or number)} regex_plural_dict = re.compile('^{(?P<w>[^()[\]][^()[\]]*?)\((?P<n>[^()\[\]]+)\)}$') # %%{word(varname or number)}
regex_plural_tuple = re.compile('^{(?P<w>[^[\]()]+)(?:\[(?P<i>\d+)\])?}$') # %%{word[index]} or %%{word} regex_plural_tuple = re.compile('^{(?P<w>[^[\]()]+)(?:\[(?P<i>\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$') 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') # UTF8 helper functions
title_fun = lambda s: unicode(s,'utf-8').title().encode('utf-8') def upper_fun(s):
cap_fun = lambda s: unicode(s,'utf-8').capitalize().encode('utf-8') return unicode(s,'utf-8').upper().encode('utf-8')
def title_fun(s):
# DEFAULT PLURAL-FORMS RULES: return unicode(s,'utf-8').title().encode('utf-8')
default_nplurals = 1 # language doesn't use plural forms def cap_fun(s):
default_get_plural_id = lambda n: 0 # only one singular/plural form is used return lambda s: unicode(s,'utf-8').capitalize().encode('utf-8')
default_construct_plural_form = lambda word, plural_id: word # word is unchangeable
ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f') ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f')
ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}") ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
# cache of translated messages: # cache of translated messages:
# of structure: # global_language_cache:
# { 'languages/xx.py': # { 'languages/xx.py':
# ( {"def-message": "xx-message", # ( {"def-message": "xx-message",
# ... # ...
@@ -78,35 +101,38 @@ ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
# 'languages/yy.py': ( {dict}, lock_object ) # 'languages/yy.py': ( {dict}, lock_object )
# ... # ...
# } # }
tcache={}
global_language_cache={}
def get_from_cache(cache, val, fun): def get_from_cache(cache, val, fun):
lock=cache[1] lang_dict, lock = cache
lock.acquire() lock.acquire()
try: try:
result=cache[0].get(val); result = lang_dict.get(val);
finally: finally:
lock.release() lock.release()
if result: if result:
return result return result
lock.acquire() lock.acquire()
try: try:
result=cache[0].setdefault(val, fun()) result = lang_dict.setdefault(val, fun())
finally: finally:
lock.release() lock.release()
return result return result
def clear_cache(cache): def clear_cache(filename):
lock=cache[1] cache = global_language_cache.setdefault(
filename, ({}, allocate_lock()))
lang_dict, lock = cache
lock.acquire() lock.acquire()
try: try:
cache[0].clear(); lang_dict.clear();
finally: finally:
lock.release() lock.release()
def lang_sampling(lang_tuple, langlist): def lang_sampling(lang_tuple, langlist):
""" search *lang_tuple* in *langlist* """
search *lang_tuple* in *langlist*
Args: Args:
lang_tuple (tuple of strings): ('aa'[[,'-bb'],'-cc']) lang_tuple (tuple of strings): ('aa'[[,'-bb'],'-cc'])
@@ -137,32 +163,28 @@ def lang_sampling(lang_tuple, langlist):
def read_dict_aux(filename): def read_dict_aux(filename):
fp = portalocker.LockedFile(filename, 'r') lang_text = portalocker.read_locked(filename).replace('\r\n', '\n')
lang_text = fp.read().replace('\r\n', '\n') clear_cache(filename)
fp.close()
# clear cache of processed messages:
clear_cache(tcache.setdefault(filename, ({}, allocate_lock())))
if not lang_text.strip():
return {}
try: try:
return eval(lang_text) return safe_eval(lang_text) or {}
except Exception, e: except Exception, e:
status='Syntax error in %s (%s)' % (filename, e) status = 'Syntax error in %s (%s)' % (filename, e)
logging.error(status) logging.error(status)
return {'__corrupted__':status} return {'__corrupted__':status}
def read_dict(filename): def read_dict(filename):
""" return dictionary with translation messages """
return dictionary with translation messages
""" """
return getcfs('lang:'+filename, filename, return getcfs('lang:'+filename, filename,
lambda: read_dict_aux(filename)) lambda: read_dict_aux(filename))
def get_lang_info(lang, langdir): def get_lang_info(lang, langdir):
"""retrieve lang information from *langdir*/*lang*.py file. """
Read few strings from lang.py file until keys !langname!, retrieve lang information from *langdir*/*lang*.py file.
!langcode! or keys greater then '!*' were found Read few strings from lang.py file until keys !langname!,
!langcode! or keys greater then '!*' were found
args: args:
lang (str): lang-code or 'default' lang (str): lang-code or 'default'
@@ -173,159 +195,78 @@ def get_lang_info(lang, langdir):
e.g.: ('en', 'English', 1338549043.0) e.g.: ('en', 'English', 1338549043.0)
""" """
filename = ospath.join(langdir, lang+'.py') filename = ospath.join(langdir, lang+'.py')
langcode=langname='' d = read_dict(filename)
f = portalocker.LockedFile(filename, 'r') langcode = d.get('!langcode!',DEFAULT_LANGUAGE)
try: langname = d.get('!langname!',langcode)
while not (langcode and langname): return (langcode, langname or langcode, ostat(filename).st_mtime)
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
def read_possible_languages_aux(langdir): def read_possible_languages(appdir):
langs = {} langs = {}
# scan languages directory for langfiles: # scan languages directory for langfiles:
for langfile in [f for f in langdir = ospath.join(appdir,'languages')
listdir(langdir, regex_langfile) + for filename in os.listdir(langdir):
listdir(langdir, '^default\.py$') if regex_langfile.match(filename) or filename=='default.py':
if osep not in f]: lang = filename[:-3]
lang=langfile[:-3] langs[lang] = get_lang_info(lang, langdir)
langs[lang]=get_lang_info(lang, langdir) if not 'en' in langs:
if 'default' not in langs:
# if default.py is not found, add default value: # if default.py is not found, add default value:
langs['default'] = ('en', 'English', 0) langs['en'] = ('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)
return langs return langs
def read_possible_languages(path): def read_global_plural_rules(filename):
lang_path = ospath.join(path, 'languages') """
return getcfs('langs:'+lang_path, lang_path, retrieve plural rules from rules/*plural_rules-lang*.py file.
lambda: read_possible_languages_aux(lang_path))
def read_plural_rules_aux(filename):
"""retrieve plural rules from rules/*plural_rules-lang*.py file.
args: args:
filename (str): plural_rules filename filename (str): plural_rules filename
returns: returns:
tuple(nplurals, get_plural_id, construct_plural_form) (nplurals, get_plural_id, construct_plural_form, status)
e.g.: (3, <function>, <function>) e.g.: (3, <function>, <function>, ok)
""" """
f = portalocker.LockedFile(filename, 'r') env = {}
plural_py=f.read().replace('\r\n','\n') data = portalocker.read_locked(filename)
f.close()
try: try:
exec(plural_py) exec(data) in env
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)
status='ok' status='ok'
except Exception, e: 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) status='Syntax error in %s (%s)' % (filename, e)
logging.error(status) 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) 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(): 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') pdir = abspath('gluon','contrib','rules')
plurals = {} plurals = {}
# scan rules directory for plural_rules-*.py files: # scan rules directory for plural_rules-*.py files:
for pname in [f for f in listdir(pdir, regex_plural_rules) for pname in os.listdir(pdir):
if osep not in f]: if not isdir(pname) and regex_plural_rules.match(pname):
lang = pname[13:-3]
lang=pname[13:-3] fname = ospath.join(pdir, pname)
fname=ospath.join(pdir, pname) n, f1, f2, status = read_global_plural_rules(fname)
mtime=ostat(fname).st_mtime if status == 'ok':
if lang in pcache and pcache[lang][2] == mtime: plurals[lang] = (lang, n, f1, f2, pname)
# if plural_file's mtime wasn't changed - use previous value: plurals['default'] = ('default',
plurals[lang]=pcache[lang] DEFAULT_NPLURALS,
else: DEFAULT_GET_PLURAL_ID,
# otherwise, reread plural_rules-file: DEFAULT_CONSTRUCTOR_PLURAL_FORM,
if 'plural_rules-'+lang in cfs: None)
n,f1,f2,status=read_plural_rules(lang) return plurals
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')
PLURAL_RULES = read_possible_plurals()
def read_plural_dict_aux(filename): def read_plural_dict_aux(filename):
fp = portalocker.LockedFile(filename, 'r') lang_text = portalocker.read_locked(filename).replace('\r\n', '\n')
lang_text = fp.read().replace('\r\n', '\n')
fp.close()
if not lang_text.strip():
return {}
try: try:
return eval(lang_text) return eval(lang_text) or {}
except Exception, e: except Exception, e:
status='Syntax error in %s (%s)' % (filename, e) status='Syntax error in %s (%s)' % (filename, e)
logging.error(status) logging.error(status)
@@ -335,7 +276,6 @@ def read_plural_dict(filename):
return getcfs('plurals:'+filename, filename, return getcfs('plurals:'+filename, filename,
lambda: read_plural_dict_aux(filename)) lambda: read_plural_dict_aux(filename))
def write_plural_dict(filename, contents): def write_plural_dict(filename, contents):
if '__corrupted__' in contents: if '__corrupted__' in contents:
return return
@@ -466,13 +406,10 @@ class lazyT(object):
return lazyT(self.m, symbols, self.T, self.f, self.t, self.M) return lazyT(self.m, symbols, self.T, self.f, self.t, self.M)
class translator(object): class translator(object):
""" """
this class is instantiated by gluon.compileapp.build_environment this class is instantiated by gluon.compileapp.build_environment
as the T object as the T object
:: ::
T.force(None) # turns off translation T.force(None) # turns off translation
T.force('fr, it') # forces web2py to translate using fr.py or it.py 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 notice 1: there is no need to force since, by default, T uses
http_accept_language to determine a translation file. http_accept_language to determine a translation file.
notice 2:
notice 2: en and en-en are considered different languages! en and en-en are considered different languages!
notice 3:
notice 3: if language xx-yy is not found force() probes other similar 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 languages using such algorithm:
xx-yy.py -> xx.py -> xx-yy*.py -> xx*.py
""" """
def __init__(self, request): def __init__(self, request):
global tcache
self.request = request self.request = request
self.folder = request.folder self.folder = request.folder
dfile = ospath.join(self.folder,'languages','default.py') self.langpath = ospath.join(self.folder,'languages')
if ospath.exists(dfile): self.filenames = set(os.listdir(self.langpath))
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.http_accept_language = request.env.http_accept_language self.http_accept_language = request.env.http_accept_language
# self.cache # filled in self.force()
# self.accepted_language = None # filled in self.force() # self.accepted_language = None # filled in self.force()
# self.language_file = None # filled in self.force() # self.language_file = None # filled in self.force()
# self.plural_language = 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_file = None # filled in self.force()
# self.plural_dict = None # filled in self.force() # self.plural_dict = None # filled in self.force()
# self.plural_status = 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.lazy = True
self.otherTs = {} self.otherTs = {}
self.filter = markmin self.filter = markmin
self.ftag = '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): def get_possible_languages_info(self, lang=None):
""" """
return info for selected language or dictionary with all return info for selected language or dictionary with all
@@ -533,90 +541,12 @@ class translator(object):
langname(from !langname! key), langname(from !langname! key),
langfile_mtime ) } langfile_mtime ) }
""" """
if lang: info = read_possible_languages(self.folder)
return read_possible_languages(self.folder).get(lang) if lang: info = info.get(lang)
return read_possible_languages(self.folder) return info
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
def force(self, *languages): def force(self, *languages):
""" """
select language(s) for translation select language(s) for translation
if a list of languages is passed as a parameter, if a list of languages is passed as a parameter,
@@ -627,49 +557,41 @@ class translator(object):
default language will be selected if none default language will be selected if none
of them matches possible_languages. of them matches possible_languages.
""" """
global tcache
language = '' language = ''
if isinstance(languages,str):
if not languages or languages[0] is None: languages = regex_language.findall(languages.lower())
elif not languages or languages[0] is None:
languages = [] languages = []
if len(languages) == 1 and isinstance(languages[0], (str, unicode)): for lang in languages:
languages = languages[0] if lang+'.py' in self.filenames:
language = lang
if languages: langfile = language+'.py'
if isinstance(languages, (str, unicode)): break
parts = languages.split(';') elif len(lang)>5 and lang[:5]+'.py' in self.filenames:
languages = [] language = lang[:5]
for al in parts: langfile = language+'.py'
languages.extend(al.split(',')) break
elif len(lang)>2 and lang[:2]+'.py' in self.filenames:
possible_languages = self.get_possible_languages() language = lang[:2]
for lang in languages: langfile = language+'.py'
match_language = regex_language.match(lang.strip().lower()) break
if match_language: else:
match_language = tuple(part if 'default.py' in self.filenames:
for part in match_language.groups() language = DEFAULT_LANGUAGE
if part) langfile = 'default.py'
language = lang_sampling(match_language, else:
self.current_languages) language = DEFAULT_LANGUAGE
if language: langfile = None
break self.accepted_language = language
language = lang_sampling(match_language, possible_languages) if langfile:
if language: self.language_file = ospath.join(self.langpath,langfile)
self.language_file = ospath.join(self.folder, self.t = read_dict(self.language_file)
'languages', else:
language + '.py') self.language_file = None
if ospath.exists(self.language_file): self.t = {}
self.t = read_dict(self.language_file) self.cache = global_language_cache.setdefault(
self.cache = tcache.setdefault(self.language_file, self.language_file,({},allocate_lock()))
({},allocate_lock())) self.set_plural(language)
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)
return languages return languages
def __call__(self, message, symbols={}, language=None, lazy=None): def __call__(self, message, symbols={}, language=None, lazy=None):
@@ -700,26 +622,30 @@ class translator(object):
prefix = '@'+(ftag or 'userdef')+'\x01' prefix = '@'+(ftag or 'userdef')+'\x01'
else: else:
prefix = '@'+self.ftag+'\x01' prefix = '@'+self.ftag+'\x01'
message = get_from_cache(self.cache, prefix+message, message = get_from_cache(
lambda: get_tr(message, prefix, filter)) self.cache, prefix+message,
lambda: get_tr(message, prefix, filter))
if symbols or symbols == 0 or symbols == "": if symbols or symbols == 0 or symbols == "":
if isinstance(symbols, dict): if isinstance(symbols, dict):
symbols.update( (key, xmlescape(value).translate(ttab_in)) symbols.update(
for key, value in symbols.iteritems() (key, xmlescape(value).translate(ttab_in))
if not isinstance(value, NUMBERS) ) for key, value in symbols.iteritems()
if not isinstance(value, NUMBERS) )
else: else:
if not isinstance(symbols, tuple): if not isinstance(symbols, tuple):
symbols = (symbols,) symbols = (symbols,)
symbols = tuple(value if isinstance(value, NUMBERS) symbols = tuple(
else xmlescape(value).translate(ttab_in) value if isinstance(value, NUMBERS)
for value in symbols) else xmlescape(value).translate(ttab_in)
for value in symbols)
message = self.params_substitution(message, symbols) message = self.params_substitution(message, symbols)
return XML(message.translate(ttab_out)) return XML(message.translate(ttab_out))
def M(self, message, symbols={}, language=None, lazy=None, filter=None, ftag=None): def M(self, message, symbols={}, language=None,
""" get cached translated markmin-message with inserted parametes lazy=None, filter=None, ftag=None):
"""
if lazy==True lazyT object is returned get cached translated markmin-message with inserted parametes
if lazy==True lazyT object is returned
""" """
if lazy is None: if lazy is None:
lazy = self.lazy lazy = self.lazy
@@ -751,18 +677,20 @@ class translator(object):
""" """
key = prefix+message key = prefix+message
mt = self.t.get(key, None) mt = self.t.get(key, None)
if mt is not None: if mt is None:
return mt # we did not find a translation
if not message.startswith('##') and not '\n' in message: if message.find('##')>0 and not '\n' in message:
tokens = message.rsplit('##', 1) # remove comments
else: message = message.rsplit('##', 1)[0]
# this allows markmin syntax in translations # guess translation same as original
tokens = [message] self.t[key] = mt = message
self.t[key] = mt = self.default_t.get(key, tokens[0]) # update language file for later translation
if (self.language_file != self.default_language_file and if self.language_file and not is_gae:
not settings.global_settings.web2py_runtime_gae): write_dict(self.language_file, self.t)
write_dict(self.language_file, self.t) # fix backslash escaping
return regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), mt) mt = regex_backslash.sub(
lambda m: m.group(1).translate(ttab_in), mt)
return mt
def params_substitution(self, message, symbols): def params_substitution(self, message, symbols):
""" """
@@ -866,19 +794,21 @@ class translator(object):
""" """
get cached translated message with inserted parameters(symbols) 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 symbols or symbols == 0 or symbols == "":
if isinstance(symbols, dict): if isinstance(symbols, dict):
symbols.update( (key, str(value).translate(ttab_in)) symbols.update(
for key, value in symbols.iteritems() (key, str(value).translate(ttab_in))
if not isinstance(value, NUMBERS) ) for key, value in symbols.iteritems()
if not isinstance(value, NUMBERS) )
else: else:
if not isinstance(symbols, tuple): if not isinstance(symbols, tuple):
symbols = (symbols,) symbols = (symbols,)
symbols = tuple(value if isinstance(value, NUMBERS) symbols = tuple(
else str(value).translate(ttab_in) value if isinstance(value, NUMBERS)
for value in symbols) else str(value).translate(ttab_in)
for value in symbols)
message = self.params_substitution(message, symbols) message = self.params_substitution(message, symbols)
return message.translate(ttab_out) return message.translate(ttab_out)
@@ -892,26 +822,25 @@ def findT(path, language='en'):
cp = ospath.join(path, 'controllers') cp = ospath.join(path, 'controllers')
vp = ospath.join(path, 'views') vp = ospath.join(path, 'views')
mop = ospath.join(path, 'modules') mop = ospath.join(path, 'modules')
for file in listdir(mp, '^.+\.py$', 0) + listdir(cp, '^.+\.py$', 0)\ for filename in \
+ listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0): listdir(mp, '^.+\.py$', 0)+listdir(cp, '^.+\.py$', 0)\
fp = portalocker.LockedFile(file, 'r') +listdir(vp, '^.+\.html$', 0)+listdir(mop, '^.+\.py$', 0):
data = fp.read() data = portalocker.read_locked(filename)
fp.close()
items = regex_translate.findall(data) items = regex_translate.findall(data)
for item in items: for item in items:
try: try:
message = eval(item) message = safe_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
except: 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: if not '!langcode!' in sentences:
sentences['!langcode!'] = ( sentences['!langcode!'] = (
'en' if language in ('default', 'en') else language) 'en' if language in ('default', 'en') else language)
@@ -937,6 +866,3 @@ def update_all_languages(application_path):
if __name__ == '__main__': if __name__ == '__main__':
import doctest import doctest
doctest.testmod() doctest.testmod()
+104 -96
View File
@@ -21,7 +21,6 @@ import re
import copy import copy
import sys import sys
import time import time
import thread
import datetime import datetime
import signal import signal
import socket import socket
@@ -29,6 +28,7 @@ import tempfile
import random import random
import string import string
import urllib2 import urllib2
from thread import allocate_lock
from fileutils import abspath, write_file, parse_version from fileutils import abspath, write_file, parse_version
from settings import global_settings from settings import global_settings
@@ -69,8 +69,11 @@ import logging.config
import gluon.messageboxhandler import gluon.messageboxhandler
logging.gluon = gluon logging.gluon = gluon
exists = os.path.exists
pjoin = os.path.join
logpath = abspath("logging.conf") logpath = abspath("logging.conf")
if os.path.exists(logpath): if exists(logpath):
logging.config.fileConfig(abspath("logging.conf")) logging.config.fileConfig(abspath("logging.conf"))
else: else:
logging.basicConfig() logging.basicConfig()
@@ -87,10 +90,10 @@ from dal import BaseAdapter
from settings import global_settings from settings import global_settings
from validators import CRYPT from validators import CRYPT
from cache import Cache from cache import Cache
from html import URL as Url, xmlescape from html import URL, xmlescape
from utils import is_valid_ip_address from utils import is_valid_ip_address
from rewrite import load, url_in, thread as rwthread, try_rewrite_on_error
import newcron import newcron
import rewrite
__all__ = ['wsgibase', 'save_password', 'appfactory', 'HttpServer'] __all__ = ['wsgibase', 'save_password', 'appfactory', 'HttpServer']
@@ -103,7 +106,7 @@ requests = 0 # gc timer
regex_client = re.compile('[\w\-:]+(\.[\w\-]+)*\.?') # ## to account for IPV6 regex_client = re.compile('[\w\-:]+(\.[\w\-]+)*\.?') # ## to account for IPV6
try: 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() raw_version_string = version_info.read().strip()
version_info.close() version_info.close()
global_settings.web2py_version = parse_version(raw_version_string) global_settings.web2py_version = parse_version(raw_version_string)
@@ -118,7 +121,7 @@ except:
if not global_settings.web2py_runtime_gae: if not global_settings.web2py_runtime_gae:
logger.warn('unable to import Rocket') logger.warn('unable to import Rocket')
rewrite.load() load()
def get_client(env): def get_client(env):
""" """
@@ -129,11 +132,16 @@ def get_client(env):
""" """
g = regex_client.search(env.get('http_x_forwarded_for', '')) g = regex_client.search(env.get('http_x_forwarded_for', ''))
if g: if g:
return g.group() client = g.group()
g = regex_client.search(env.get('remote_addr', '')) else:
if g: g = regex_client.search(env.get('remote_addr', ''))
return g.group() if g:
return '127.0.0.1' 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): def copystream_progress(request, chunk_size= 10**5):
""" """
@@ -253,7 +261,8 @@ def middleware_aux(request, response, *middleware_apps):
for item in middleware_apps: for item in middleware_apps:
app=item(app) app=item(app)
def caller(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 lambda caller=caller, app=app: caller(app)
return middleware return middleware
@@ -279,14 +288,14 @@ def parse_get_post_vars(request, environ):
# parse POST variables on POST, PUT, BOTH only in post_vars # parse POST variables on POST, PUT, BOTH only in post_vars
try: try:
request.body = copystream_progress(request) ### stores request body request.body = body = copystream_progress(request)
except IOError: except IOError:
raise HTTP(400,"Bad Request - HTTP body is incomplete") raise HTTP(400,"Bad Request - HTTP body is incomplete")
if (request.body and request.env.request_method in ('POST', 'PUT', 'BOTH')): if (body and request.env.request_method in ('POST', 'PUT', 'BOTH')):
dpost = cgi.FieldStorage(fp=request.body,environ=environ,keep_blank_values=1) dpost = cgi.FieldStorage(fp=body,environ=environ,keep_blank_values=1)
# The same detection used by FieldStorage to detect multipart POSTs # The same detection used by FieldStorage to detect multipart POSTs
is_multipart = dpost.type[:10] == 'multipart/' is_multipart = dpost.type[:10] == 'multipart/'
request.body.seek(0) body.seek(0)
isle25 = sys.version_info[1] <= 5 isle25 = sys.version_info[1] <= 5
def listify(a): def listify(a):
@@ -357,9 +366,10 @@ def wsgibase(environ, responder):
request = Request() request = Request()
response = Response() response = Response()
session = Session() session = Session()
request.env.web2py_path = global_settings.applications_parent env = request.env
request.env.web2py_version = web2py_version env.web2py_path = global_settings.applications_parent
request.env.update(global_settings) env.web2py_version = web2py_version
env.update(global_settings)
static_file = False static_file = False
try: try:
try: try:
@@ -373,79 +383,80 @@ def wsgibase(environ, responder):
# serve file if static # serve file if static
# ################################################## # ##################################################
if not environ.get('PATH_INFO',None) and \ eget = environ.get
environ.get('REQUEST_URI',None): if not eget('PATH_INFO',None) and eget('REQUEST_URI',None):
# for fcgi, get path_info and query_string from request_uri # for fcgi, get path_info and
# query_string from request_uri
items = environ['REQUEST_URI'].split('?') items = environ['REQUEST_URI'].split('?')
environ['PATH_INFO'] = items[0] environ['PATH_INFO'] = items[0]
if len(items) > 1: if len(items) > 1:
environ['QUERY_STRING'] = items[1] environ['QUERY_STRING'] = items[1]
else: else:
environ['QUERY_STRING'] = '' environ['QUERY_STRING'] = ''
if not environ.get('HTTP_HOST',None): if not eget('HTTP_HOST',None):
environ['HTTP_HOST'] = '%s:%s' % (environ.get('SERVER_NAME'), environ['HTTP_HOST'] = \
environ.get('SERVER_PORT')) 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 static_file:
if environ.get('QUERY_STRING', '')[:10] == 'attachment': if environ.get('QUERY_STRING','').startswith(
response.headers['Content-Disposition'] = 'attachment' 'attachment'):
response.headers['Content-Disposition'] \
= 'attachment'
response.stream(static_file, request=request) response.stream(static_file, request=request)
# ################################################## # ##################################################
# fill in request items # fill in request items
# ################################################## # ##################################################
app = request.application ## must go after url_in!
http_host = request.env.http_host.split(':',1)[0]
http_host = env.http_host.split(':',1)[0]
local_hosts = [http_host,'::1','127.0.0.1','::ffff:127.0.0.1'] local_hosts = [http_host,'::1','127.0.0.1',
'::ffff:127.0.0.1']
if not global_settings.web2py_runtime_gae: if not global_settings.web2py_runtime_gae:
local_hosts.append(socket.gethostname()) local_hosts.append(socket.gethostname())
try: local_hosts.append(socket.gethostbyname(http_host)) try:
except socket.gaierror: pass local_hosts.append(
request.client = get_client(request.env) socket.gethostbyname(http_host))
if not is_valid_ip_address(request.client): except socket.gaierror:
raise HTTP(400,"Bad Request (request.client=%s)" % \ pass
request.client) client = get_client(env)
request.folder = abspath('applications', x_req_with = str(env.http_x_requested_with).lower()
request.application) + os.sep
x_req_with = str(request.env.http_x_requested_with).lower() request.update(dict(
request.ajax = x_req_with == 'xmlhttprequest' client = client,
request.cid = request.env.http_web2py_component_element folder = abspath('applications',app) + os.sep,
request.is_local = request.env.remote_addr in local_hosts ajax = x_req_with == 'xmlhttprequest',
request.is_https = request.env.wsgi_url_scheme \ cid = env.http_web2py_component_element,
in ['https', 'HTTPS'] or request.env.https == 'on' is_local = env.remote_addr in local_hosts,
is_https = env.wsgi_url_scheme \
# ################################################## in ['https', 'HTTPS'] or env.https=='on'))
# compute a request.uuid to be used for tickets and toolbar request.uuid = request.compute_uuid() # requires client
# ##################################################
response.uuid = request.compute_uuid()
# ################################################## # ##################################################
# access the requested application # access the requested application
# ################################################## # ##################################################
if not os.path.exists(request.folder): if not exists(request.folder):
if request.application == \ if app == rwthread.routes.default_application \
rewrite.thread.routes.default_application \ and app != 'welcome':
and request.application != 'welcome': redirect(URL('welcome','default','index'))
request.application = 'welcome' elif rwthread.routes.error_handler:
redirect(Url(r=request)) _handler = rwthread.routes.error_handler
elif rewrite.thread.routes.error_handler: redirect(URL(_handler['application'],
_handler = rewrite.thread.routes.error_handler
redirect(Url(_handler['application'],
_handler['controller'], _handler['controller'],
_handler['function'], _handler['function'],
args=request.application)) args=app))
else: else:
raise HTTP(404, rewrite.thread.routes.error_message \ raise HTTP(404, rwthread.routes.error_message \
% 'invalid request', % 'invalid request',
web2py_error='invalid application') web2py_error='invalid application')
elif not request.is_local and \ elif not request.is_local and \
os.path.exists(os.path.join(request.folder,'DISABLED')): exists(pjoin(request.folder,'DISABLED')):
raise HTTP(503, "<html><body><h1>Temporarily down for maintenance</h1></body></html>") raise HTTP(503, "<html><body><h1>Temporarily down for maintenance</h1></body></html>")
request.url = Url(r=request, request.url = URL(r=request,
args=request.args, args=request.args,
extension=request.raw_extension) extension=request.raw_extension)
@@ -492,22 +503,23 @@ def wsgibase(environ, responder):
# ################################################## # ##################################################
# set no-cache headers # set no-cache headers
# ################################################## # ##################################################
response.headers['Content-Type'] = \ headers = response.headers
headers['Content-Type'] = \
contenttype('.'+request.extension) contenttype('.'+request.extension)
response.headers['Cache-Control'] = \ headers['Cache-Control'] = \
'no-store, no-cache, must-revalidate, post-check=0, pre-check=0' '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()) time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime())
response.headers['Pragma'] = 'no-cache' headers['Pragma'] = 'no-cache'
# ################################################## # ##################################################
# run controller # run controller
# ################################################## # ##################################################
if global_settings.debugging and request.application != "admin": if global_settings.debugging and app != "admin":
import gluon.debug import gluon.debug
# activate the debugger and wait to reach application code # activate the debugger
gluon.debug.dbg.do_debug(mainpyfile=request.folder) gluon.debug.dbg.do_debug(mainpyfile=request.folder)
serve_controller(request, response, session) serve_controller(request, response, session)
@@ -549,22 +561,24 @@ def wsgibase(environ, responder):
# ################################################## # ##################################################
if request.cid: if request.cid:
if response.flash and not 'web2py-component-flash' \ rheaders
in http_response.headers: if response.flash and \
http_response.headers['web2py-component-flash'] = \ not 'web2py-component-flash' in rheaders:
rheaders['web2py-component-flash'] = \
urllib2.quote(xmlescape(response.flash)\ urllib2.quote(xmlescape(response.flash)\
.replace('\n','')) .replace('\n',''))
if response.js and not 'web2py-component-command' \ if response.js and \
in http_response.headers: not 'web2py-component-command' in readers:
http_response.headers['web2py-component-command'] = \ readers['web2py-component-command'] = \
response.js.replace('\n','') response.js.replace('\n','')
rcookies = response.cookies
if session._forget and \ if session._forget and \
response.session_id_name in response.cookies: response.session_id_name in response.cookies:
del response.cookies[response.session_id_name] del rcookies[response.session_id_name]
elif session._secure: 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 ticket=None
except RestrictedError, e: except RestrictedError, e:
@@ -583,7 +597,7 @@ def wsgibase(environ, responder):
BaseAdapter.close_all_instances('rollback') BaseAdapter.close_all_instances('rollback')
http_response = \ http_response = \
HTTP(500, rewrite.thread.routes.error_message_ticket % \ HTTP(500, rwthread.routes.error_message_ticket % \
dict(ticket=ticket), dict(ticket=ticket),
web2py_error='ticket %s' % ticket) web2py_error='ticket %s' % ticket)
@@ -606,7 +620,7 @@ def wsgibase(environ, responder):
e = RestrictedError('Framework', '', '', locals()) e = RestrictedError('Framework', '', '', locals())
ticket = e.log(request) or 'unrecoverable' ticket = e.log(request) or 'unrecoverable'
http_response = \ http_response = \
HTTP(500, rewrite.thread.routes.error_message_ticket \ HTTP(500, rwthread.routes.error_message_ticket \
% dict(ticket=ticket), % dict(ticket=ticket),
web2py_error='ticket %s' % ticket) web2py_error='ticket %s' % ticket)
@@ -616,7 +630,7 @@ def wsgibase(environ, responder):
response.session_file.close() response.session_file.close()
session._unlock(response) 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) http_response, request, environ, ticket)
if not http_response: if not http_response:
return wsgibase(new_environ,responder) return wsgibase(new_environ,responder)
@@ -641,7 +655,7 @@ def save_password(password, port):
print '*********************************************************' print '*********************************************************'
elif password == '<recycle>': elif password == '<recycle>':
# reuse the current password if any # reuse the current password if any
if os.path.exists(password_file): if exists(password_file):
return return
else: else:
password = '' password = ''
@@ -672,9 +686,9 @@ def appfactory(wsgiapp=wsgibase,
[, profilerfilename='profiler.log']]]) [, profilerfilename='profiler.log']]])
""" """
if profilerfilename and os.path.exists(profilerfilename): if profilerfilename and exists(profilerfilename):
os.unlink(profilerfilename) os.unlink(profilerfilename)
locker = thread.allocate_lock() locker = allocate_lock()
def app_with_logging(environ, responder): def app_with_logging(environ, responder):
""" """
@@ -785,7 +799,7 @@ class HttpServer(object):
os.chdir(path) os.chdir(path)
[add_path_first(p) for p in (path, abspath('site-packages'), "")] [add_path_first(p) for p in (path, abspath('site-packages'), "")]
custom_import_install(web2py_path) custom_import_install(web2py_path)
if os.path.exists("logging.conf"): if exists("logging.conf"):
logging.config.fileConfig("logging.conf") logging.config.fileConfig("logging.conf")
save_password(password, port) save_password(password, port)
@@ -800,9 +814,9 @@ class HttpServer(object):
logger.info('SSL is off') logger.info('SSL is off')
elif not rocket.ssl: elif not rocket.ssl:
logger.warning('Python "ssl" module unavailable. SSL is OFF') 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') 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') logger.warning('unable to open SSL private key. SSL is OFF')
else: else:
sock_list.extend([ssl_private_key, ssl_certificate]) sock_list.extend([ssl_private_key, ssl_certificate])
@@ -848,9 +862,3 @@ class HttpServer(object):
except: except:
pass pass
+13 -1
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- 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. # Requires python 1.5.2 or better.
""" """
@@ -141,6 +142,17 @@ class LockedFile(object):
def __del__(self): def __del__(self):
self.close() 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__': if __name__=='__main__':
f = LockedFile('test.txt',mode='wb') f = LockedFile('test.txt',mode='wb')
f.write('test ok') f.write('test ok')
+13 -10
View File
@@ -88,16 +88,19 @@ class TicketStorage(Storage):
ticket_id, ticket_id,
): ):
if not self.db: 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: try:
return cPickle.load(ef) return cPickle.load(ef)
finally: finally:
ef.close() ef.close()
table = self._get_table(self.db, self.tablename, app) else:
rows = self.db(table.ticket_id == ticket_id).select() table = self._get_table(self.db, self.tablename, app)
if rows: rows = self.db(table.ticket_id == ticket_id).select()
return cPickle.loads(rows[0].ticket_data) return cPickle.loads(rows[0].ticket_data) if rows else {}
return None
class RestrictedError(Exception): class RestrictedError(Exception):
@@ -164,10 +167,10 @@ class RestrictedError(Exception):
ticket_storage = TicketStorage(db=request.tickets_db) ticket_storage = TicketStorage(db=request.tickets_db)
d = ticket_storage.load(request, app, ticket_id) d = ticket_storage.load(request, app, ticket_id)
self.layer = d['layer'] self.layer = d.get('layer')
self.code = d['code'] self.code = d.get('code')
self.output = d['output'] self.output = d.get('output')
self.traceback = d['traceback'] self.traceback = d.get('traceback')
self.snapshot = d.get('snapshot') self.snapshot = d.get('snapshot')
def __str__(self): def __str__(self):
+68 -49
View File
@@ -27,9 +27,17 @@ from http import HTTP
from fileutils import abspath, read_file from fileutils import abspath, read_file
from settings import global_settings 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'(?<!\\)\$[a-zA-Z]\w*')
regex_anything = re.compile(r'(?<!\\)\$anything')
regex_redirect = re.compile(r'(\d+)->(.*)')
def _router_default(): def _router_default():
"return new copy of default base router" "return new copy of default base router"
@@ -75,7 +83,7 @@ def _params_default(app=None):
params_apps = dict() params_apps = dict()
params = _params_default(app=None) # regex rewrite parameters 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 routers = None
def log_rewrite(string): def log_rewrite(string):
@@ -97,13 +105,18 @@ def log_rewrite(string):
else: else:
logger.debug(string) logger.debug(string)
ROUTER_KEYS = set(('default_application', 'applications', 'default_controller', 'controllers', ROUTER_KEYS = set(
'default_function', 'functions', 'default_language', 'languages', ('default_application', 'applications',
'domain', 'domains', 'root_static', 'path_prefix', 'default_controller', 'controllers',
'exclusive_domain', 'map_hyphen', 'map_static', 'default_function', 'functions',
'acfe_match', 'file_match', 'args_match')) '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: # The external interface to rewrite consists of:
# #
@@ -223,9 +236,7 @@ def try_redirect_on_error(http_object, request, ticket=None):
(redir,status,ticket, (redir,status,ticket,
urllib.quote_plus(request.env.request_uri), urllib.quote_plus(request.env.request_uri),
request.url) request.url)
return HTTP(303, return HTTP(303,'You are being redirected <a href="%s">here</a>' % url,Location=url)
'You are being redirected <a href="%s">here</a>' % url,
Location=url)
return http_object return http_object
@@ -258,7 +269,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
path = abspath(routes) path = abspath(routes)
else: else:
path = abspath('applications', app, routes) path = abspath('applications', app, routes)
if not os.path.exists(path): if not exists(path):
return return
data = read_file(path).replace('\r\n','\n') 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 # parse the app-specific routes.py if present
# #
all_apps = [] all_apps = []
for appname in [app for app in os.listdir(abspath('applications')) if not app.startswith('.')]: apppath = abspath('applications')
if os.path.isdir(abspath('applications', appname)) and \ for appname in os.listdir(apppath):
os.path.isdir(abspath('applications', appname, 'controllers')): if not appname.startswith('.') and \
isdir(abspath(apppath,appname)) and \
isdir(abspath(apppath,appname,'controllers')):
all_apps.append(appname) all_apps.append(appname)
if routers: if routers:
router = Storage(routers.BASE) # new copy 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) raise SyntaxError, "BASE-only key '%s' in router '%s'" % (key, appname)
router.update(routers[appname]) router.update(routers[appname])
routers[appname] = router routers[appname] = router
if os.path.exists(abspath('applications', appname, routes)): if exists(abspath('applications', appname, routes)):
load(routes, appname) load(routes, appname)
if routers: 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) log_rewrite('URL rewrite is on. configuration in %s' % path)
regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*')
regex_anything = re.compile(r'(?<!\\)\$anything')
regex_redirect = re.compile(r'(\d+)->(.*)')
def compile_regex(k, v): def compile_regex(k, v):
""" """
Preprocess and compile the regular expressions in routes_app/in/out Preprocess and compile the regular expressions in routes_app/in/out
The resulting regex will match a pattern of the form: The resulting regex will match a pattern of the form:
[remote address]:[protocol]://[host]:[method] [path] [remote address]:[protocol]://[host]:[method] [path]
@@ -380,8 +388,9 @@ def compile_regex(k, v):
def load_routers(all_apps): def load_routers(all_apps):
"load-time post-processing of routers" "load-time post-processing of routers"
for app in routers.keys(): for app in routers:
# initialize apps with routers that aren't present, on behalf of unit tests # initialize apps with routers that aren't present,
# on behalf of unit tests
if app not in all_apps: if app not in all_apps:
all_apps.append(app) all_apps.append(app)
router = Storage(routers.BASE) # new copy router = Storage(routers.BASE) # new copy
@@ -420,10 +429,10 @@ def load_routers(all_apps):
routers.BASE.domains[router.domain] = app routers.BASE.domains[router.domain] = app
if isinstance(router.controllers, str) and router.controllers == 'DEFAULT': if isinstance(router.controllers, str) and router.controllers == 'DEFAULT':
router.controllers = set() router.controllers = set()
if os.path.isdir(abspath('applications', app)): if isdir(abspath('applications', app)):
cpath = abspath('applications', app, 'controllers') cpath = abspath('applications', app, 'controllers')
for cname in os.listdir(cpath): 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]) router.controllers.add(cname[:-3])
if router.controllers: if router.controllers:
router.controllers.add('static') router.controllers.add('static')
@@ -458,16 +467,20 @@ def load_routers(all_apps):
# #
domains = dict() domains = dict()
if routers.BASE.domains: if routers.BASE.domains:
for (domain, app) in [(d.strip(':'), a.strip('/')) for (d, a) in routers.BASE.domains.items()]: for (d, a) in routers.BASE.domains.iteritems():
port = None (domain, app) = (d.strip(':'), a.strip('/'))
if ':' in domain: if ':' in domain:
(domain, port) = domain.split(':') (domain, port) = domain.split(':')
ctlr = None else:
fcn = None port = None
if '/' in app: if '/' in app:
(app, ctlr) = app.split('/', 1) (app, ctlr) = app.split('/', 1)
else:
ctlr = None
if ctlr and '/' in ctlr: if ctlr and '/' in ctlr:
(ctlr, fcn) = ctlr.split('/') (ctlr, fcn) = ctlr.split('/')
else:
fcn = None
if app not in all_apps and app not in routers: if app not in all_apps and app not in routers:
raise SyntaxError, "unknown app '%s' in domains" % app raise SyntaxError, "unknown app '%s' in domains" % app
domains[(domain, port)] = (app, ctlr, fcn) domains[(domain, port)] = (app, ctlr, fcn)
@@ -514,7 +527,8 @@ def regex_filter_in(e):
query = e.get('QUERY_STRING', None) query = e.get('QUERY_STRING', None)
e['WEB2PY_ORIGINAL_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '') e['WEB2PY_ORIGINAL_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '')
if thread.routes.routes_in: 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) rmatch = regex_redirect.match(path)
if rmatch: if rmatch:
raise HTTP(int(rmatch.group(1)),location=rmatch.group(2)) raise HTTP(int(rmatch.group(1)),location=rmatch.group(2))
@@ -581,6 +595,9 @@ regex_args = re.compile(r'''
/?$) # trailing slash /?$) # trailing slash
''', re.X) ''', re.X)
def sluggify(key):
return key.lower().replace('.','_')
def regex_url_in(request, environ): def regex_url_in(request, environ):
"rewrite and parse incoming URL" "rewrite and parse incoming URL"
@@ -594,9 +611,8 @@ def regex_url_in(request, environ):
if thread.routes.routes_in: if thread.routes.routes_in:
environ = regex_filter_in(environ) environ = regex_filter_in(environ)
for (key, value) in environ.items(): request.env.update((sluggify(k),v) for k,v in environ.iteritems())
request.env[key.lower().replace('.', '_')] = value
path = request.env.path_info.replace('\\', '/') path = request.env.path_info.replace('\\', '/')
@@ -606,7 +622,7 @@ def regex_url_in(request, environ):
match = regex_static.match(regex_space.sub('_', path)) match = regex_static.match(regex_space.sub('_', path))
if match and match.group('x'): 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'), 'applications', match.group('b'),
'static', match.group('x')) 'static', match.group('x'))
return (static_file, environ) return (static_file, environ)
@@ -908,7 +924,7 @@ class MapUrlIn(object):
''' '''
if len(self.args) == 1 and self.arg0 in self.router.root_static: if len(self.args) == 1 and self.arg0 in self.router.root_static:
self.controller = self.request.controller = '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, 'applications', self.application,
self.controller, self.arg0) self.controller, self.arg0)
log_rewrite("route: root static=%s" % root_static_file) 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) bad_static = bad_static or name in ('', '.', '..') or not self.router._file_match.match(name)
if bad_static: if bad_static:
log_rewrite('bad static path=%s' % file) 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') web2py_error='invalid static file')
# #
# support language-specific static subdirectories, # 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 language-specific file doesn't exist, try same file in static
# #
if self.language: if self.language:
static_file = os.path.join(self.request.env.applications_parent, static_file = pjoin(self.request.env.applications_parent,
'applications', self.application, 'applications', self.application,
'static', self.language, file) 'static', self.language, file)
if not self.language or not os.path.isfile(static_file): if not self.language or not isfile(static_file):
static_file = os.path.join(self.request.env.applications_parent, static_file = pjoin(self.request.env.applications_parent,
'applications', self.application, 'applications', self.application,
'static', file) 'static', file)
log_rewrite("route: static=%s" % static_file) log_rewrite("route: static=%s" % static_file)
return static_file return static_file
@@ -1040,12 +1057,14 @@ class MapUrlIn(object):
uri += '.' + self.extension uri += '.' + self.extension
if self.language: if self.language:
uri = '/%s%s' % (self.language, uri) uri = '/%s%s' % (self.language, uri)
uri = '/%s%s' % (app, uri) uri = '/%s%s%s%s' % (
uri += self.args and urllib.quote('/' + '/'.join([str(x) for x in self.args])) or '' app,
uri += (self.query and ('?' + self.query) or '') 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 self.env['REQUEST_URI'] = uri
for (key, value) in self.env.items(): self.request.env.update(
self.request.env[key.lower().replace('.', '_')] = value (sluggify(k),v) for k,v in self.env.iteritems())
@property @property
def arg0(self): def arg0(self):
+3 -2
View File
@@ -1407,7 +1407,7 @@ class Worker(Thread):
raise BadRequest raise BadRequest
req = match.groupdict() req = match.groupdict()
for k,v in req.items(): for k,v in req.iteritems():
if not v: if not v:
req[k] = "" req[k] = ""
if k == 'path': if k == 'path':
@@ -1694,7 +1694,8 @@ class FileSystemWorker(Worker):
try: try:
# Get our file path # 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('/') rpath = request.get('path', '').lstrip('/')
filepath = os.path.join(self.root, rpath) filepath = os.path.join(self.root, rpath)
filepath = os.path.abspath(filepath) filepath = os.path.abspath(filepath)
+9 -6
View File
@@ -18,7 +18,8 @@ if not hasattr(os, 'mkdir'):
if global_settings.db_sessions is not True: if global_settings.db_sessions is not True:
global_settings.db_sessions = set() 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 global_settings.applications_parent = global_settings.gluon_parent
@@ -26,12 +27,14 @@ global_settings.app_folders = set()
global_settings.debugging = False global_settings.debugging = False
global_settings.is_pypy = hasattr(platform,'python_implementation') and \ global_settings.is_pypy = \
platform.python_implementation() == 'PyPy' hasattr(platform,'python_implementation') and \
platform.python_implementation() == 'PyPy'
global_settings.is_jython = 'java' in sys.platform.lower() or \ global_settings.is_jython = \
hasattr(sys, 'JYTHON_JAR') or \ 'java' in sys.platform.lower() or \
str(sys.copyright).find('Jython') > 0 hasattr(sys, 'JYTHON_JAR') or \
str(sys.copyright).find('Jython') > 0
+4 -2
View File
@@ -36,6 +36,8 @@ import re
import cStringIO import cStringIO
from gluon import current, redirect, A, URL, DIV, H3, UL, LI, SPAN, INPUT from gluon import current, redirect, A, URL, DIV, H3, UL, LI, SPAN, INPUT
import inspect import inspect
import settings
is_gae = settings.global_settings.web2py_runtime_gae
table_field = re.compile('[\w_]+\.[\w_]+') table_field = re.compile('[\w_]+\.[\w_]+')
widget_class = re.compile('^\w*') widget_class = re.compile('^\w*')
@@ -581,7 +583,7 @@ class AutocompleteWidget(object):
def callback(self): def callback(self):
if self.keyword in self.request.vars: if self.keyword in self.request.vars:
field = self.fields[0] 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) 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: else:
rows = self.db(field.like(self.request.vars[self.keyword]+'%')).select(orderby=self.orderby,limitby=self.limitby,distinct=self.distinct,*self.fields) 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: else:
rows = dbset.select(left=left,orderby=orderby,*columns) rows = dbset.select(left=left,orderby=orderby,*columns)
if exportManager.has_key(export_type): if export_type in exportManager:
value = exportManager[export_type] value = exportManager[export_type]
clazz = value[0] if hasattr(value, '__getitem__') else value clazz = value[0] if hasattr(value, '__getitem__') else value
oExp = clazz(rows) oExp = clazz(rows)
+60 -129
View File
@@ -15,10 +15,10 @@ Contributors:
""" """
import os import os
import re
import cgi import cgi
import cStringIO import cStringIO
import logging import logging
from re import compile, sub, escape, DOTALL
try: try:
# have web2py # have web2py
from restricted import RestrictedError from restricted import RestrictedError
@@ -57,6 +57,18 @@ class SuperNode(Node):
def __repr__(self): def __repr__(self):
return "%s->%s" % (self.name, self.value) 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): class BlockNode(Node):
""" """
Block Container. Block Container.
@@ -81,8 +93,7 @@ class BlockNode(Node):
def __repr__(self): def __repr__(self):
lines = ['%sblock %s%s' % (self.left,self.name,self.right)] lines = ['%sblock %s%s' % (self.left,self.name,self.right)]
for node in self.nodes: lines += [str(node) for node in self.nodes]
lines.append(str(node))
lines.append('%send%s' % (self.left, self.right)) lines.append('%send%s' % (self.left, self.right))
return ''.join(lines) return ''.join(lines)
@@ -90,11 +101,8 @@ class BlockNode(Node):
""" """
Get this BlockNodes content, not including child Nodes Get this BlockNodes content, not including child Nodes
""" """
lines = [] return ''.join(str(node) for node in self.nodes \
for node in self.nodes: if not isinstance(node, BlockNode))
if not isinstance(node, BlockNode):
lines.append(str(node))
return ''.join(lines)
def append(self, node): def append(self, node):
""" """
@@ -122,30 +130,14 @@ class BlockNode(Node):
else: else:
raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other) raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other)
def output(self, blocks): def output(self, blocks):
""" """
Merges all nodes into a single string. Merges all nodes into a single string.
blocks -- Dictionary of blocks that are extending blocks -- Dictionary of blocks that are extending
from this template. from this template.
""" """
lines = [] return ''.join(output_aux(node,blocks) for node in self.nodes)
# 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)
class Content(BlockNode): class Content(BlockNode):
""" """
@@ -165,29 +157,13 @@ class Content(BlockNode):
self.pre_extend = pre_extend self.pre_extend = pre_extend
def __str__(self): def __str__(self):
lines = [] return ''.join(output_aux(node,self.blocks) for node in self.nodes)
# 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)
def _insert(self, other, index = 0): def _insert(self, other, index = 0):
""" """
Inserts object at index. Inserts object at index.
""" """
if isinstance(other, str) or isinstance(other, Node): if isinstance(other, (str, Node)):
self.nodes.insert(index, other) self.nodes.insert(index, other)
else: else:
raise TypeError("Invalid type, must be instance of ``str`` or ``Node``.") raise TypeError("Invalid type, must be instance of ``str`` or ``Node``.")
@@ -201,8 +177,7 @@ class Content(BlockNode):
if isinstance(other, (list, tuple)): if isinstance(other, (list, tuple)):
# Must reverse so the order stays the same. # Must reverse so the order stays the same.
other.reverse() other.reverse()
for item in other: (self._insert(item, index) for item in other)
self._insert(item, index)
else: else:
self._insert(other, index) 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. 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) self.nodes.append(node)
if isinstance(node, BlockNode): if isinstance(node, BlockNode):
self.blocks[node.name] = node self.blocks[node.name] = node
@@ -233,18 +208,18 @@ class Content(BlockNode):
class TemplateParser(object): class TemplateParser(object):
default_delimiters = ('{{','}}') 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. # These are used for re-indentation.
# Indent + 1 # Indent + 1
re_block = re.compile('^(elif |else:|except:|except |finally:).*$', re_block = compile('^(elif |else:|except:|except |finally:).*$',DOTALL)
re.DOTALL)
# Indent - 1 # Indent - 1
re_unblock = re.compile('^(return|continue|break|raise)( .*)?$', re.DOTALL) re_unblock = compile('^(return|continue|break|raise)( .*)?$', DOTALL)
# Indent - 1 # Indent - 1
re_pass = re.compile('^pass( .*)?$', re.DOTALL) re_pass = compile('^pass( .*)?$', DOTALL)
def __init__(self, text, def __init__(self, text,
name = "ParserContainer", name = "ParserContainer",
@@ -291,13 +266,16 @@ class TemplateParser(object):
# allow optional alternative delimiters # allow optional alternative delimiters
self.delimiters = delimiters self.delimiters = delimiters
if delimiters != self.default_delimiters: if delimiters != self.default_delimiters:
escaped_delimiters = (re.escape(delimiters[0]),re.escape(delimiters[1])) escaped_delimiters = (escape(delimiters[0]),
self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters, re.DOTALL) escape(delimiters[1]))
elif context.has_key('response') and hasattr(context['response'],'delimiters'): self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, DOTALL)
elif hasattr(context.get('response',None),'delimiters'):
if context['response'].delimiters != self.default_delimiters: if context['response'].delimiters != self.default_delimiters:
escaped_delimiters = (re.escape(context['response'].delimiters[0]), escaped_delimiters = (
re.escape(context['response'].delimiters[1])) escape(context['response'].delimiters[0]),
self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters,re.DOTALL) 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. # Create a root level Content that everything will go into.
self.content = Content(name=name) self.content = Content(name=name)
@@ -524,17 +502,19 @@ class TemplateParser(object):
# the parent nodes. # the parent nodes.
self.content.nodes = [] self.content.nodes = []
t_content = t.content
# Set our include, unique by filename # 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 # Make sure our pre_extended nodes go first
t.content.insert(pre) t_content.insert(pre)
# Then we extend our blocks # Then we extend our blocks
t.content.extend(self.content) t_content.extend(self.content)
# Work off the parent node. # Work off the parent node.
self.content = t.content self.content = t_content
def parse(self, text): def parse(self, text):
@@ -553,69 +533,20 @@ class TemplateParser(object):
ij = self.r_tag.split(text) ij = self.r_tag.split(text)
# j = current index # j = current index
# i = current item # i = current item
stack = self.stack
for j in range(len(ij)): for j in range(len(ij)):
i = ij[j] i = ij[j]
if i: 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') self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag')
# Our current element in the stack. # Our current element in the stack.
top = self.stack[-1] top = stack[-1]
if in_tag: if in_tag:
line = i 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 '}}' # Get rid of '{{' and '}}'
line = line[2:-2].strip() line = line[2:-2].strip()
@@ -633,9 +564,9 @@ class TemplateParser(object):
# Perform block comment escaping. # Perform block comment escaping.
# This performs escaping ON anything # This performs escaping ON anything
# in between """ and """ # in between """ and """
line = re.sub(TemplateParser.r_multiline, line = sub(TemplateParser.r_multiline,
remove_newline, remove_newline,
line) line)
if line.startswith('='): if line.startswith('='):
# IE: {{=response.title}} # IE: {{=response.title}}
@@ -672,7 +603,7 @@ class TemplateParser(object):
self.lexers[name](parser = self, self.lexers[name](parser = self,
value = value, value = value,
top = top, top = top,
stack = self.stack,) stack = stack)
elif name == '=': elif name == '=':
# So we have a variable to insert into # So we have a variable to insert into
@@ -693,7 +624,7 @@ class TemplateParser(object):
# so anything after this gets added # so anything after this gets added
# to this node. This allows us to # to this node. This allows us to
# "nest" nodes. # "nest" nodes.
self.stack.append(node) stack.append(node)
elif name == 'end' and not value.startswith('='): elif name == 'end' and not value.startswith('='):
# We are done with this node. # We are done with this node.
@@ -702,7 +633,7 @@ class TemplateParser(object):
self.blocks[top.name] = top self.blocks[top.name] = top
# Pop it. # Pop it.
self.stack.pop() stack.pop()
elif name == 'super' and not value.startswith('='): elif name == 'super' and not value.startswith('='):
# Get our correct target name # Get our correct target name
@@ -757,17 +688,17 @@ class TemplateParser(object):
# So we can properly put a response.write() in place. # So we can properly put a response.write() in place.
continuation = False continuation = False
len_parsed = 0 len_parsed = 0
for k in range(len(tokens)): for k, token in enumerate(tokens):
tokens[k] = tokens[k].strip() token = tokens[k] = token.strip()
len_parsed += len(tokens[k]) len_parsed += len(token)
if tokens[k].startswith('='): if token.startswith('='):
if tokens[k].endswith('\\'): if token.endswith('\\'):
continuation = True continuation = True
tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip()) tokens[k] = "\n%s(%s" % (self.writer, token[1:].strip())
else: else:
tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip()) tokens[k] = "\n%s(%s)" % (self.writer, token[1:].strip())
elif continuation: elif continuation:
tokens[k] += ')' tokens[k] += ')'
continuation = False continuation = False
@@ -912,7 +843,7 @@ def render(content = "hello world",
# Add it to the context so we can use it. # Add it to the context so we can use it.
if not 'NOESCAPE' in context: if not 'NOESCAPE' in context:
context['NOESCAPE'] = XML context['NOESCAPE'] = NOESCAPE
# save current response class # save current response class
if context and 'response' in context: if context and 'response' in context:
+31
View File
@@ -129,6 +129,26 @@ class TestFields(unittest.TestCase):
) )
self.assertEqual(db.t.insert(a=t0), 1) self.assertEqual(db.t.insert(a=t0), 1)
self.assertEqual(db().select(db.t.a)[0].a, t0) 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.t.drop()
db.define_table('t', Field('a', 'time', default='11:30')) db.define_table('t', Field('a', 'time', default='11:30'))
t0 = datetime.time(10, 30, 55) t0 = datetime.time(10, 30, 55)
@@ -369,6 +389,17 @@ class TestJoin(unittest.TestCase):
db.t1.drop() db.t1.drop()
db.t2.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): class TestMinMaxSum(unittest.TestCase):
+34
View File
@@ -17,6 +17,7 @@ import languages
import tempfile import tempfile
import threading import threading
import logging import logging
from storage import Storage
try: try:
import multiprocessing import multiprocessing
@@ -53,6 +54,39 @@ try:
for result in results: for result in results:
self.assertTrue(result) 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**')),
'<strong>Hello World</strong>')
T.force('it')
self.assertEqual(str(T('Hello World')),
'Salve Mondo')
except ImportError: except ImportError:
logging.warning("Skipped test case, no multiprocessing module.") logging.warning("Skipped test case, no multiprocessing module.")
+336 -320
View File
@@ -31,6 +31,7 @@ from utils import web2py_uuid
from fileutils import read_file, check_credentials from fileutils import read_file, check_credentials
from gluon import * from gluon import *
from gluon.contrib.autolinks import expand_one from gluon.contrib.autolinks import expand_one
from gluon.dal import Row
import serializers import serializers
@@ -798,6 +799,141 @@ def addrow(form, a, b, c, style, _id, position=-1):
class Auth(object): 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. Class for authentication, authorization, role based access control.
@@ -894,10 +1030,11 @@ class Auth(object):
open(filename,'w').write(key) open(filename,'w').write(key)
return 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 args is None: args=[]
if vars is None: vars={} 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): def here(self):
return URL(args=current.request.args,vars=current.request.vars) return URL(args=current.request.args,vars=current.request.vars)
@@ -933,221 +1070,67 @@ class Auth(object):
else: else:
self.user = None self.user = None
session.auth = None session.auth = None
settings = self.settings = Settings()
# ## what happens after login? # ## what happens after login?
self.next = current.request.vars._next self.next = current.request.vars._next
if isinstance(self.next,(list,tuple)): if isinstance(self.next,(list,tuple)):
self.next = self.next[0] self.next = self.next[0]
url_index = URL(controller,'index')
url_login = URL(controller,function,args='login')
# ## what happens after registration? # ## what happens after registration?
settings.hideerror = False settings = self.settings = Settings()
settings.password_min_length = 4 settings.update(Auth.default_settings)
settings.cas_domains = [request.env.http_host] settings.update({
settings.cas_provider = cas_provider 'cas_domains': [request.env.http_host],
settings.cas_actions = {'login':'login', 'cas_provider': cas_provider,
'validate':'validate', 'cas_actions': {'login':'login',
'servicevalidate':'serviceValidate', 'validate':'validate',
'proxyvalidate':'proxyValidate', 'servicevalidate':'serviceValidate',
'logout':'logout'} 'proxyvalidate':'proxyValidate',
settings.cas_maps = None 'logout':'logout'},
settings.extra_fields = {} 'extra_fields': {},
settings.actions_disabled = [] 'actions_disabled': [],
settings.reset_password_requires_verification = False 'controller': controller,
settings.registration_requires_verification = False 'function': function,
settings.registration_requires_approval = False 'login_url': url_login,
settings.login_after_registration = False 'logged_url': URL(controller, function, args='profile'),
settings.alternate_requires_registration = False 'download_url': URL(controller,'download'),
settings.create_user_groups = "user_%(id)s" 'mailer': (mailer==True) and Mail() or mailer,
settings.everybody_group_id = None 'on_failed_authorization': \
URL(controller,function, args='not_authorized'),
settings.controller = controller 'login_next': url_index,
settings.function = function 'login_onvalidation': [],
settings.login_url = self.url(function, args='login') 'login_onaccept': [],
settings.logged_url = self.url(function, args='profile') 'login_methods': [self],
settings.download_url = self.url('download') 'login_form': self,
settings.mailer = (mailer==True) and Mail() or mailer 'logout_next': url_index,
settings.login_captcha = None 'logout_onlogout': None,
settings.register_captcha = None 'register_next': url_index,
settings.retrieve_username_captcha = None 'register_onvalidation': [],
settings.retrieve_password_captcha = None 'register_onaccept': [],
settings.captcha = None 'verify_email_next': url_login,
settings.expiration = 3600 # one hour 'verify_email_onaccept': [],
settings.long_expiration = 3600*30*24 # one month 'profile_next': url_index,
settings.remember_me_form = True 'profile_onvalidation': [],
settings.allow_basic_login = False 'profile_onaccept': [],
settings.allow_basic_login_only = False 'retrieve_username_next': url_index,
settings.on_failed_authorization = \ 'retrieve_password_next': url_index,
self.url(function, args='not_authorized') 'request_reset_password_next': url_login,
'reset_password_next': url_index,
settings.on_failed_authentication = lambda x: redirect(x) 'change_password_next': url_index,
'change_password_onvalidation': [],
settings.formstyle = 'table3cols' 'change_password_onaccept': [],
settings.label_separator = ': ' 'retrieve_password_onvalidation': [],
'reset_password_onvalidation': [],
# ## table names to be used 'reset_password_onaccept': [],
'hmac_key': hmac_key,
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.lock_keys = True settings.lock_keys = True
# ## these are messages that can be customized # ## these are messages that can be customized
messages = self.messages = Messages(current.T) messages = self.messages = Messages(current.T)
messages.login_button = 'Login' messages.update(Auth.default_messages)
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.lock_keys = True messages.lock_keys = True
# for "remember me" option # for "remember me" option
@@ -1336,37 +1319,39 @@ class Auth(object):
settings = self.settings settings = self.settings
request = current.request request = current.request
T = current.T T = current.T
def lazy_user (auth = self): return auth.user_id
reference_user = 'reference %s' % settings.table_user_name reference_user = 'reference %s' % settings.table_user_name
def lazy_user (auth = self):
return auth.user_id
def represent(id,record=None,s=settings): def represent(id,record=None,s=settings):
try: try:
user = s.table_user(id) user = s.table_user(id)
return '%(first_name)s %(last_name)s' % user return '%(first_name)s %(last_name)s' % user
except: return id except:
self.signature = db.Table(self.db,'auth_signature', return id
Field('is_active','boolean', self.signature = db.Table(
default=True, self.db,'auth_signature',
readable=False, writable=False, Field('is_active','boolean',
label=T('Is Active')), default=True,
Field('created_on','datetime', readable=False, writable=False,
default=request.now, label=T('Is Active')),
writable=False, readable=False, Field('created_on','datetime',
label=T('Created On')), default=request.now,
Field('created_by', writable=False, readable=False,
reference_user, label=T('Created On')),
default=lazy_user, represent=represent, Field('created_by',
writable=False, readable=False, reference_user,
label=T('Created By')), default=lazy_user, represent=represent,
Field('modified_on','datetime', writable=False, readable=False,
update=request.now,default=request.now, label=T('Created By')),
writable=False,readable=False, Field('modified_on','datetime',
label=T('Modified On')), update=request.now,default=request.now,
Field('modified_by', writable=False,readable=False,
reference_user,represent=represent, label=T('Modified On')),
default=lazy_user,update=lazy_user, Field('modified_by',
writable=False,readable=False, reference_user,represent=represent,
label=T('Modified By'))) default=lazy_user,update=lazy_user,
writable=False,readable=False,
label=T('Modified By')))
def define_tables(self, username=False, signature=None, def define_tables(self, username=False, signature=None,
migrate=True, fake_migrate=False): migrate=True, fake_migrate=False):
@@ -1396,8 +1381,7 @@ class Auth(object):
elif isinstance(signature,self.db.Table): elif isinstance(signature,self.db.Table):
signature_list = [signature] signature_list = [signature]
else: else:
signature_list = signature signature_list = signature
lazy_tables, db._lazy_tables = db._lazy_tables, False
is_not_empty = IS_NOT_EMPTY(error_message=self.messages.is_empty) is_not_empty = IS_NOT_EMPTY(error_message=self.messages.is_empty)
is_crypted = CRYPT(key=settings.hmac_key, is_crypted = CRYPT(key=settings.hmac_key,
min_length=settings.password_min_length) min_length=settings.password_min_length)
@@ -1600,7 +1584,6 @@ class Auth(object):
actions=actions, actions=actions,
maps=maps) maps=maps)
def log_event(self, description, vars=None, origin='auth'): def log_event(self, description, vars=None, origin='auth'):
""" """
usage: usage:
@@ -1667,6 +1650,11 @@ class Auth(object):
return user return user
def basic(self): 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: if not self.settings.allow_basic_login:
return (False,False,False) return (False,False,False)
basic = current.request.env.http_authorization basic = current.request.env.http_authorization
@@ -1675,11 +1663,23 @@ class Auth(object):
(username, password) = base64.b64decode(basic[6:]).split(':') (username, password) = base64.b64decode(basic[6:]).split(':')
return (True, True, self.login_bare(username, password)) 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): def login_bare(self, username, password):
""" """
logins user logins user as specified by usernname (or email) and password
""" """
request = current.request request = current.request
session = current.session session = current.session
table_user = self.table_user() table_user = self.table_user()
@@ -1694,12 +1694,7 @@ class Auth(object):
if user and user.get(passfield,False): if user and user.get(passfield,False):
password = table_user[passfield].validate(password)[0] password = table_user[passfield].validate(password)[0]
if not user.registration_key and password == user[passfield]: if not user.registration_key and password == user[passfield]:
user = Storage(table_user._filter_fields(user, id=True)) self.login_user(user)
session.auth = Storage(user=user, last_visit=request.now,
expiration=self.settings.expiration,
hmac_key = web2py_uuid())
self.user = user
self.update_groups()
return user return user
else: else:
# user not in database try other login methods # user not in database try other login methods
@@ -1738,15 +1733,15 @@ class Auth(object):
renew=interactivelogin) renew=interactivelogin)
service = session._cas_service service = session._cas_service
del 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 response.headers['refresh'] = "5;URL=%s"%service+"?ticket="+ticket
return A("Continue to %s"%service, return A("Continue to %s"%service,
_href=service+"?ticket="+ticket) _href=service+"?ticket="+ticket)
else: else:
redirect(service+"?ticket="+ticket) 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() 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) redirect(service)
def cas_onaccept(form, onaccept=onaccept): def cas_onaccept(form, onaccept=onaccept):
if not onaccept is DEFAULT: onaccept(form) if not onaccept is DEFAULT: onaccept(form)
@@ -1759,7 +1754,7 @@ class Auth(object):
db, table = self.db, self.table_cas() db, table = self.db, self.table_cas()
current.response.headers['Content-Type']='text' current.response.headers['Content-Type']='text'
ticket = request.vars.ticket ticket = request.vars.ticket
renew = True if request.vars.has_key('renew') else False renew = 'renew' in request.vars
row = table(ticket=ticket) row = table(ticket=ticket)
success = False success = False
if row: if row:
@@ -1973,25 +1968,18 @@ class Auth(object):
# process authenticated users # process authenticated users
if user: if user:
user = Storage(table_user._filter_fields(user, id=True)) user = Row(table_user._filter_fields(user, id=True))
# process authenticated users # process authenticated users
# user wants to be logged in for longer # user wants to be logged in for longer
session.auth = Storage( self.login_user(user)
user = user, session.auth.expiration = \
last_visit = request.now, request.vars.get('remember',False) and \
expiration = request.vars.get("remember",False) and \ self.settings.long_expiration or \
self.settings.long_expiration or self.settings.expiration, self.settings.expiration
remember = request.vars.has_key("remember"), session.auth.remember = 'remember' in request.vars
hmac_key = web2py_uuid()
)
self.user = user
self.log_event(log, user) self.log_event(log, user)
session.flash = self.messages.logged_in session.flash = self.messages.logged_in
self.update_groups()
# how to continue # how to continue
if self.settings.login_form == self: if self.settings.login_form == self:
if accepted_form: if accepted_form:
@@ -2131,11 +2119,14 @@ class Auth(object):
if self.settings.everybody_group_id: if self.settings.everybody_group_id:
self.add_membership(self.settings.everybody_group_id, form.vars.id) self.add_membership(self.settings.everybody_group_id, form.vars.id)
if self.settings.registration_requires_verification: if self.settings.registration_requires_verification:
link = self.url('user',args=('verify_email',key),scheme=True)
if not self.settings.mailer or \ if not self.settings.mailer or \
not self.settings.mailer.send(to=form.vars.email, not self.settings.mailer.send(
subject=self.messages.verify_email_subject, to=form.vars.email,
message=self.messages.verify_email subject=self.messages.verify_email_subject,
% dict(key=key)): message=self.messages.verify_email \
% dict(key=key,link=link)):
self.db.rollback() self.db.rollback()
response.flash = self.messages.unable_send_email response.flash = self.messages.unable_send_email
return form return form
@@ -2149,13 +2140,10 @@ class Auth(object):
if not self.settings.registration_requires_verification: if not self.settings.registration_requires_verification:
table_user[form.vars.id] = dict(registration_key='') table_user[form.vars.id] = dict(registration_key='')
session.flash = self.messages.registration_successful session.flash = self.messages.registration_successful
user = self.db(table_user[username] == form.vars[username]).select().first() user = self.db(
user = Storage(table_user._filter_fields(user, id=True)) table_user[username] == form.vars[username]
session.auth = Storage(user=user, last_visit=request.now, ).select().first()
expiration=self.settings.expiration, self.login_user(user)
hmac_key = web2py_uuid())
self.user = user
self.update_groups()
session.flash = self.messages.logged_in session.flash = self.messages.logged_in
self.log_event(log, form.vars) self.log_event(log, form.vars)
callback(onaccept,form) callback(onaccept,form)
@@ -2192,7 +2180,7 @@ class Auth(object):
key = getarg(-1) key = getarg(-1)
table_user = self.table_user() table_user = self.table_user()
user = self.db(table_user.registration_key == key).select().first() user = table_user(registration_key=key)
if not user: if not user:
redirect(self.settings.login_url) redirect(self.settings.login_url)
if self.settings.registration_requires_approval: if self.settings.registration_requires_approval:
@@ -2267,7 +2255,7 @@ class Auth(object):
if form.accepts(request, session, if form.accepts(request, session,
formname='retrieve_username', dbio=False, formname='retrieve_username', dbio=False,
onvalidation=onvalidation,hideerror=self.settings.hideerror): 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: if not user:
current.session.flash = \ current.session.flash = \
self.messages.invalid_email self.messages.invalid_email
@@ -2345,7 +2333,7 @@ class Auth(object):
if form.accepts(request, session, if form.accepts(request, session,
formname='retrieve_password', dbio=False, formname='retrieve_password', dbio=False,
onvalidation=onvalidation,hideerror=self.settings.hideerror): 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: if not user:
current.session.flash = \ current.session.flash = \
self.messages.invalid_email self.messages.invalid_email
@@ -2398,12 +2386,12 @@ class Auth(object):
session = current.session session = current.session
if next is DEFAULT: if next is DEFAULT:
next = self.next or self.settings.reset_password_next next = self.settings.reset_password_next
try: try:
key = request.vars.key or getarg(-1) key = request.vars.key or getarg(-1)
t0 = int(key.split('-')[0]) t0 = int(key.split('-')[0])
if time.time()-t0 > 60*60*24: raise Exception 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 if not user: raise Exception
except Exception: except Exception:
session.flash = self.messages.invalid_reset_password session.flash = self.messages.invalid_reset_password
@@ -2422,11 +2410,15 @@ class Auth(object):
formstyle=self.settings.formstyle, formstyle=self.settings.formstyle,
separator=self.settings.label_separator separator=self.settings.label_separator
) )
if form.accepts(request,session,hideerror=self.settings.hideerror): if form.accepts(request,session,
user.update_record(**{passfield:str(form.vars.new_password), hideerror=self.settings.hideerror):
'registration_key':'', user.update_record(
'reset_password_key':''}) **{passfield:str(form.vars.new_password),
'registration_key':'',
'reset_password_key':''})
session.flash = self.messages.password_changed session.flash = self.messages.password_changed
if self.settings.login_after_password_change:
self.login_user(user)
redirect(next) redirect(next)
return form return form
@@ -2481,7 +2473,7 @@ class Auth(object):
formname='reset_password', dbio=False, formname='reset_password', dbio=False,
onvalidation=onvalidation, onvalidation=onvalidation,
hideerror=self.settings.hideerror): 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: if not user:
session.flash = self.messages.invalid_email session.flash = self.messages.invalid_email
redirect(self.url(args=request.args)) redirect(self.url(args=request.args))
@@ -2504,11 +2496,14 @@ class Auth(object):
def email_reset_password(self,user): def email_reset_password(self,user):
reset_password_key = str(int(time.time()))+'-' + web2py_uuid() 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( if self.settings.mailer.send(
to=user.email, to=user.email,
subject=self.messages.reset_password_subject, subject=self.messages.reset_password_subject,
message=self.messages.reset_password % \ 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) user.update_record(reset_password_key=reset_password_key)
return True return True
return False return False
@@ -2684,7 +2679,8 @@ class Auth(object):
self.user = auth.user self.user = auth.user
if self.settings.login_onaccept: if self.settings.login_onaccept:
form = Storage(dict(vars=self.user)) 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 log = self.messages.impersonate_log
self.log_event(log,dict(id=current_id, other_id=auth.user.id)) self.log_event(log,dict(id=current_id, other_id=auth.user.id))
elif user_id in (0, '0') and self.is_impersonating(): 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: elif form.record and fieldname in form.record:
new_record[fieldname]=form.record[fieldname] new_record[fieldname]=form.record[fieldname]
if fields: if fields:
for key,value in fields.items(): new_record.update(fields)
new_record[key] = value
id = archive_table.insert(**new_record) id = archive_table.insert(**new_record)
return id 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'): if not hasattr(self,'_wiki'):
self._wiki = Wiki(self, self._wiki = Wiki(self,
manage_permissions=manage_permissions, manage_permissions=manage_permissions,
force_prefix=force_prefix,env=env) force_prefix=force_prefix,env=env)
else: else:
self._wiki.env.update(env or {}) 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): 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. # 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. # If a argument already is populated with values we simply replaces them.
for arg_name in pos_args[len(arg_dict):]: 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] arg_dict[arg_name] = b[arg_name]
if len(arg_dict) >= len(pos_args): if len(arg_dict) >= len(pos_args):
# All the positional arguments is found. The function may now be called. # 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. # However, we need to update the arg_dict with the values from the named arguments as well.
for arg_name in named_args: for arg_name in named_args:
if b.has_key(arg_name): if arg_name in b:
arg_dict[arg_name] = b[arg_name] arg_dict[arg_name] = b[arg_name]
return f(**arg_dict) return f(**arg_dict)
@@ -4136,7 +4135,7 @@ class Service(object):
prefix='pys', prefix='pys',
documentation = documentation, documentation = documentation,
ns = True) 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) dispatcher.register_function(method, function, returns, args, doc)
if request.env.request_method == 'POST': if request.env.request_method == 'POST':
# Process normal Soap Operation # Process normal Soap Operation
@@ -4392,8 +4391,9 @@ class PluginManager(object):
self.__dict__.clear() self.__dict__.clear()
settings = self.__getattr__(plugin) settings = self.__getattr__(plugin)
settings.installed = True settings.installed = True
[settings.update({key:value}) for key,value in defaults.items() \ settings.update(
if not key in settings] (k,v) for k,v in defaults.items() if not k in settings)
def __getattr__(self, key): def __getattr__(self, key):
if not key in self.__dict__: if not key in self.__dict__:
self.__dict__[key] = Storage() self.__dict__[key] = Storage()
@@ -4494,32 +4494,48 @@ class Wiki(object):
self.host = current.request.env.http_host self.host = current.request.env.http_host
perms = self.manage_permissions = manage_permissions perms = self.manage_permissions = manage_permissions
db = auth.db db = auth.db
db.define_table( table_definitions = {
'wiki_page', 'wiki_page':{
Field('slug', 'args':[
requires=[IS_SLUG(),IS_NOT_IN_DB(db,'wiki_page.slug')], Field('slug',
readable=False,writable=False), requires=[IS_SLUG(),
Field('title',unique=True), IS_NOT_IN_DB(db,'wiki_page.slug')],
Field('body','text',notnull=True), readable=False,writable=False),
Field('tags','list:string'), Field('title',unique=True),
Field('can_read','list:string',writable=perms,readable=perms, Field('body','text',notnull=True),
default=[Wiki.everybody]), Field('tags','list:string'),
Field('can_edit','list:string',writable=perms,readable=perms, Field('can_read','list:string',
default=[Wiki.everybody]), writable=perms,
Field('changelog'), readable=perms,
Field('html','text',readable=False,writable=False,compute=render), default=[Wiki.everybody]),
auth.signature,format='%(title)s') Field('can_edit', 'list:string',
db.define_table( writable=perms,readable=perms,
'wiki_tag', default=[Wiki.everybody]),
Field('name'), Field('changelog'),
Field('wiki_page',db.wiki_page), Field('html','text',compute=render,
auth.signature,format='%(name)s') readable=False, writable=False),
db.define_table( auth.signature],
'wiki_media', 'vars':{'format':'%(title)s'}},
Field('wiki_page',db.wiki_page), 'wiki_tag':{
Field('title',required=True), 'args':[
Field('file','upload',required=True), Field('name'),
auth.signature,format='%(title)s') 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): def update_tags_insert(page,id,db=db):
for tag in page.tags or []: for tag in page.tags or []:
tag = tag.strip().lower() tag = tag.strip().lower()