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
- Support for DAL(lazy_tables=True) and db.define_table(on_define=lambda table:), thanks Jonathan
- db(...).select(cacheable=True) make select 30% faster
- db(...).select(cache=(cache.ram,3600)) now caches parsed data 100x faster
- db(...).count(cache=(cache.ram,3600)) now supported
- MongoDB support in DAL (experimental), thanks Mark Breedveld
- geodal and spatialite, thanks Denes and Fran (experimental)
- db.mytable._before_insert, _after_insert, _before_update, _after_update, _before_delete. _after_delete (list of callbacks)
+1 -1
View File
@@ -29,7 +29,7 @@ update:
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
echo "remember that pymysql was tweaked"
src:
echo 'Version 2.00.0 ('`date +%Y-%m-%d\ %H:%M:%S`') dev' > VERSION
echo 'Version 2.00.1 ('`date +%Y-%m-%d\ %H:%M:%S`') rc4' > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
+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.fileutils
response.subtitle = 'Database Administration (appadmin)'
# ## critical --- make a copy of the environment
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'
from gluon.languages import (regex_language, read_possible_languages,
read_possible_plurals, lang_sampling,
lang_sampling,
read_dict, write_dict, read_plural_dict,
write_plural_dict)
write_plural_dict, PLURAL_RULES)
if DEMO_MODE and request.function in ['change_password','pack','pack_plugin','upgrade_web2py','uninstall','cleanup','compile_app','remove_compiled_app','delete','delete_plugin','create_file','upload_file','update_languages','reload_routes','git_push','git_pull']:
@@ -949,8 +949,8 @@ def design():
# get only existed files
languages = sorted(all_languages)
plural_rules={}
all_plurals=read_possible_plurals()
plural_rules = {}
all_plurals = PLURAL_RULES
for langfile,lang in all_languages.iteritems():
lang=lang.strip()
match_language = regex_language.match(lang)
@@ -961,7 +961,7 @@ def design():
plang = lang_sampling(match_language, all_plurals.keys())
if plang:
plural=all_plurals[plang]
plural_rules[langfile]=(plural[0],plang,plural[1],plural[3])
plural_rules[langfile]=(plural[0],plang,plural[4],plural[3])
else:
plural_rules[langfile]=(0,lang,'plural_rules-%s.py'%lang,'')
+122
View File
@@ -4,5 +4,127 @@
'!langname!': 'English (US)',
'%Y-%m-%d': '%m-%d-%Y',
'%Y-%m-%d %H:%M:%S': '%m-%d-%Y %H:%M:%S',
'(requires internet access)': '(requires internet access)',
'(something like "it-it")': '(something like "it-it")',
'About': 'About',
'Additional code for your application': 'Additional code for your application',
'Admin language': 'Admin language',
'administrative interface': 'administrative interface',
'Administrator Password:': 'Administrator Password:',
'and rename it:': 'and rename it:',
'Application name:': 'Application name:',
'are not used yet': 'are not used yet',
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
'arguments': 'arguments',
'back': '<<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>
{{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}}
<table>
{{for db in sorted(databases):}}
{{for table in databases[db].tables:}}
{{qry='%s.%s.id>0'%(db,table)}}
@@ -28,20 +29,24 @@
{{qry=''}}
{{pass}}
{{pass}}
<h3>
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
</h3>
[ {{=A(str(T('insert new'))+' '+table,_href=URL('insert',args=[db,table]))}} ]
<br /><br />
<tr>
<th style="font-size: 1.75em;">
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
</th>
<td>
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
</td>
</tr>
{{pass}}
</table>
{{pass}}
{{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>
{{if table:}}
[ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]<br/><br/>
<h3>{{=T("Rows in table")}}</h3><br/>
{{if table:}}
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
<h3>{{=T("Rows in Table")}}</h3><br/>
{{else:}}
<h3>{{=T("Rows selected")}}</h3><br/>
{{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>
<br/><br/>
<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 stop<nrows:}}[ {{=A(T('next 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)),_class="btn")}}{{pass}}
{{if rows:}}
<div style="overflow: auto;" width="80%">
{{linkto=URL('update',args=request.args[0])}}
@@ -61,35 +66,35 @@
</div>
{{pass}}
<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 ''}}
{{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'):}}
{{fieldname=table._primarykey[0]}}
{{dbname=request.args[0]}}
{{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}}
{{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{=T("Table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{else:}}
{{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{=T("Table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{pass}}
</h2>
<h3>{{=T("New Record")}}</h3><br/>
{{=form}}
{{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'):}}
{{fieldname=request.vars.keys()[0]}}
{{dbname=request.args[0]}}
{{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}}
{{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
{{=T("Table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{=T("Record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
{{else:}}
{{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
{{=T("Table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{=T("Record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
{{pass}}
</h2>
<h3>{{=T("Edit current record")}}</h3><br/><br/>{{=form}}
+7 -1
View File
@@ -13,12 +13,16 @@
<th>web2py&trade;</th>
<td>{{=myversion}}</td>
</tr>
{{if snapshot:}}
<tr>
<th>Python</th>
<td>{{=snapshot.get('pyver','')}}</td>
</tr>
{{pass}}
</tbody>
</table>
{{if traceback or code or layer:}}
<h3>{{=T('Traceback')}}</h3>
<div class="inspect">
{{=traceback}}
@@ -130,4 +134,6 @@
<h3>In file: {{=layer}}</h3>
{{=CODE(code.replace('\r',''),language='python',link='/examples/global/vars/')}}
</div>
{{else:}}
<h3>{{=T('Ticket Missing')}}</h3>
{{pass}}
@@ -11,6 +11,8 @@ import copy
import gluon.contenttype
import gluon.fileutils
response.subtitle = 'Database Administration (appadmin)'
# ## critical --- make a copy of the environment
global_env = copy.copy(globals())
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>
{{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}}
<table>
{{for db in sorted(databases):}}
{{for table in databases[db].tables:}}
{{qry='%s.%s.id>0'%(db,table)}}
@@ -28,20 +29,24 @@
{{qry=''}}
{{pass}}
{{pass}}
<h3>
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
</h3>
[ {{=A(str(T('insert new'))+' '+table,_href=URL('insert',args=[db,table]))}} ]
<br /><br />
<tr>
<th style="font-size: 1.75em;">
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
</th>
<td>
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
</td>
</tr>
{{pass}}
</table>
{{pass}}
{{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>
{{if table:}}
[ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]<br/><br/>
<h3>{{=T("Rows in table")}}</h3><br/>
{{if table:}}
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
<h3>{{=T("Rows in Table")}}</h3><br/>
{{else:}}
<h3>{{=T("Rows selected")}}</h3><br/>
{{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>
<br/><br/>
<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 stop<nrows:}}[ {{=A(T('next 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)),_class="btn")}}{{pass}}
{{if rows:}}
<div style="overflow: auto;" width="80%">
{{linkto=URL('update',args=request.args[0])}}
@@ -61,35 +66,35 @@
</div>
{{pass}}
<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 ''}}
{{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'):}}
{{fieldname=table._primarykey[0]}}
{{dbname=request.args[0]}}
{{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}}
{{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{=T("Table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{else:}}
{{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{=T("Table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{pass}}
</h2>
<h3>{{=T("New Record")}}</h3><br/>
{{=form}}
{{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'):}}
{{fieldname=request.vars.keys()[0]}}
{{dbname=request.args[0]}}
{{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}}
{{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
{{=T("Table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{=T("Record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
{{else:}}
{{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
{{=T("Table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{=T("Record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
{{pass}}
</h2>
<h3>{{=T("Edit current record")}}</h3><br/><br/>{{=form}}
@@ -11,6 +11,8 @@ import copy
import gluon.contenttype
import gluon.fileutils
response.subtitle = 'Database Administration (appadmin)'
# ## critical --- make a copy of the environment
global_env = copy.copy(globals())
+7 -7
View File
@@ -10,7 +10,7 @@
'%s selected': '%s označených',
'Administrative interface': 'pro administrátorské rozhranie kliknite sem',
'Are you sure you want to delete this object?': 'Opravdu chceš odstranit tento objekt?',
'Available databases and tables': 'Dostupné databáze a tabuľky',
'Available Databases and Tables': 'Dostupné databáze a tabuľky',
'Cannot be empty': 'Nemůže být prázdné',
'Change password': 'Změna hesla',
'Check to delete': 'Označit ke smazání',
@@ -68,7 +68,7 @@
'Reset Password key': 'Nastavit registrační kľíč',
'Retrieve username': 'Retrieve username',
'Role': 'Role',
'Rows in table': 'řádků v tabulce',
'Rows in Table': 'řádků v tabulce',
'Rows selected': 'označených řádků',
'Stylesheet': 'CSS',
'Submit': 'Odeslat',
@@ -100,8 +100,8 @@
'cache': 'cache',
'customize me!': 'uprav mě!',
'data uploaded': 'data nahrána',
'database': 'databáze',
'database %s select': 'databáze %s výber',
'Database': 'databáze',
'Database %s select': 'databáze %s výber',
'db': 'db',
'design': 'návrh',
'done!': 'hotovo!',
@@ -121,11 +121,11 @@
'password': 'heslo',
'previous 100 rows': 'předchádzajících 100 řádků',
'profile': 'profil',
'record': 'záznam',
'Record': 'záznam',
'record does not exist': 'záznam neexistuje',
'record id': 'id záznamu',
'Record id': 'id záznamu',
'register': 'registrovat',
'state': 'stav',
'table': 'tabulka',
'Table': 'tabulka',
'unable to parse csv file': 'nedá sa zpracovat csv soubor',
}
+11
View File
@@ -2,4 +2,15 @@
{
'!langcode!': 'en-us',
'!langname!': 'English (US)',
'%s %%(shop)': '%s %%(shop)',
'%s %%(shop[0])': '%s %%(shop[0])',
'%s %%{shop[0]}': '%s %%{shop[0]}',
'%s %%{shop}': '%s %%{shop}',
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
'@markmin\x01**Hello World**': '**Hello World**',
'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g',
'enter date and time as %(format)s': 'enter date and time as %(format)s',
'Hello World': 'Hello World',
'Hello World ## comment': 'Hello World ',
'Hello World## comment': 'Hello World',
}
+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"?',
'Authentication': 'Autenticación',
'Available databases and tables': 'Bases de datos y tablas disponibles',
'Available Databases and Tables': 'Bases de datos y tablas disponibles',
'Cannot be empty': 'No puede estar vacío',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'No se puede compilar: hay errores en su aplicación. Depure, corrija errores y vuelva a intentarlo.',
'Change Password': 'Cambie Contraseña',
@@ -99,7 +99,7 @@
'Reset Password key': 'Reset Password key',
'Resolve Conflict file': 'archivo Resolución de Conflicto',
'Role': 'Rol',
'Rows in table': 'Filas en la tabla',
'Rows in Table': 'Filas en la tabla',
'Rows selected': 'Filas seleccionadas',
'Saved file hash:': 'Hash del archivo guardado:',
'Static files': 'Archivos estáticos',
@@ -165,8 +165,8 @@
'currently saved or': 'actualmente guardado o',
'customize me!': 'Adaptame!',
'data uploaded': 'datos subidos',
'database': 'base de datos',
'database %s select': 'selección en base de datos %s',
'Database': 'base de datos',
'Database %s select': 'selección en base de datos %s',
'database administration': 'administración base de datos',
'db': 'db',
'defines tables': 'define tablas',
@@ -218,9 +218,9 @@
'pack all': 'empaquetar todo',
'pack compiled': 'empaquete compiladas',
'previous 100 rows': '100 filas anteriores',
'record': 'registro',
'Record': 'registro',
'record does not exist': 'el registro no existe',
'record id': 'id de registro',
'Record id': 'id de registro',
'register': 'registrese',
'remove compiled': 'eliminar compiladas',
'restore': 'restaurar',
@@ -232,7 +232,7 @@
'some files could not be removed': 'algunos archivos no pudieron ser removidos',
'state': 'estado',
'static': 'estáticos',
'table': 'tabla',
'Table': 'tabla',
'test': 'probar',
'the application logic, each URL path is mapped in one exposed function in the controller': 'la lógica de la aplicación, cada ruta URL se mapea en una función expuesta en el controlador',
'the data representation, define database tables and sets': 'la representación de datos, define tablas y conjuntos de base de datos',
+7 -7
View File
@@ -14,7 +14,7 @@
'Ajax Recipes': 'Recettes Ajax',
'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
'Authentication': 'Authentification',
'Available databases and tables': 'Bases de données et tables disponibles',
'Available Databases and Tables': 'Bases de données et tables disponibles',
'Buy this book': 'Acheter ce livre',
'Cannot be empty': 'Ne peut pas être vide',
'Check to delete': 'Cliquez pour supprimer',
@@ -95,7 +95,7 @@
'Reset Password key': 'Réinitialiser le mot clé',
'Resources': 'Ressources',
'Role': 'Rôle',
'Rows in table': 'Lignes du tableau',
'Rows in Table': 'Lignes du tableau',
'Rows selected': 'Lignes sélectionnées',
'Semantic': 'Sémantique',
'Services': 'Services',
@@ -135,8 +135,8 @@
'change password': 'changer le mot de passe',
'customize me!': 'personnalisez-moi!',
'data uploaded': 'données téléchargées',
'database': 'base de données',
'database %s select': 'base de données %s select',
'Database': 'base de données',
'Database %s select': 'base de données %s select',
'db': 'db',
'design': 'design',
'done!': 'fait!',
@@ -157,12 +157,12 @@
'please input your password again': "S'il vous plaît entrer votre mot de passe",
'previous 100 rows': '100 lignes précédentes',
'profile': 'profile',
'record': 'enregistrement',
'Record': 'enregistrement',
'record does not exist': "l'archive n'existe pas",
'record id': "id d'enregistrement",
'Record id': "id d'enregistrement",
'register': "s'inscrire",
'state': 'état',
'table': 'tableau',
'Table': 'tableau',
'unable to parse csv file': "incapable d'analyser le fichier cvs",
'value already in database or empty': 'valeur déjà dans la base ou vide',
}
+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é",
'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
'Authentication': 'Authentification',
'Available databases and tables': 'Bases de données et tables disponibles',
'Available Databases and Tables': 'Bases de données et tables disponibles',
'Buy this book': 'Acheter ce livre',
'cache': 'cache',
'Cannot be empty': 'Ne peut pas être vide',
@@ -34,8 +34,8 @@
'customize me!': 'personnalisez-moi!',
'data uploaded': 'données téléchargées',
'Database': 'Base de données',
'database': 'base de données',
'database %s select': 'base de données %s select',
'Database': 'base de données',
'Database %s select': 'base de données %s select',
'db': 'db',
'DB Model': 'Modèle DB',
'Delete:': 'Supprimer:',
@@ -114,9 +114,9 @@
'Quick Examples': 'Examples Rapides',
'Readme': 'Lisez-moi',
'Recipes': 'Recettes',
'record': 'enregistrement',
'Record': 'enregistrement',
'record does not exist': "l'archive n'existe pas",
'record id': "id d'enregistrement",
'Record id': "id d'enregistrement",
'Record ID': "ID d'enregistrement",
'Register': "S'inscrire",
'register': "s'inscrire",
@@ -127,7 +127,7 @@
'Reset Password key': 'Réinitialiser le mot clé',
'Resources': 'Ressources',
'Role': 'Rôle',
'Rows in table': 'Lignes du tableau',
'Rows in Table': 'Lignes du tableau',
'Rows selected': 'Lignes sélectionnées',
'Semantic': 'Sémantique',
'Services': 'Services',
@@ -136,7 +136,7 @@
'Submit': 'Soumettre',
'Support': 'Support',
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
'table': 'tableau',
'Table': 'tableau',
'Table name': 'Nom du tableau',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "query" est une condition comme "db.table1.champ1==\'valeur\'". Quelque chose comme "db.table1.champ1==db.table2.champ2" résulte en un JOIN SQL.',
'The Core': 'Le noyau',
+7 -7
View File
@@ -9,7 +9,7 @@
'%s %%{row} updated': '%s पंक्तियाँ अद्यतन',
'%s selected': '%s चुना हुआ',
'Administrative interface': 'प्रशासनिक इंटरफेस के लिए यहाँ क्लिक करें',
'Available databases and tables': 'उपलब्ध डेटाबेस और तालिका',
'Available Databases and Tables': 'उपलब्ध डेटाबेस और तालिका',
'Cannot be empty': 'खाली नहीं हो सकता',
'Change Password': 'पासवर्ड बदलें',
'Check to delete': 'हटाने के लिए चुनें',
@@ -43,7 +43,7 @@
'Powered by': 'Powered by',
'Query:': 'प्रश्न:',
'Register': 'पंजीकृत (रजिस्टर) करना ',
'Rows in table': 'तालिका में पंक्तियाँ ',
'Rows in Table': 'तालिका में पंक्तियाँ ',
'Rows selected': 'चयनित (चुने गये) पंक्तियाँ ',
'Stylesheet': 'Stylesheet',
'Sure you want to delete this object?': 'सुनिश्चित हैं कि आप इस वस्तु को हटाना चाहते हैं?',
@@ -61,8 +61,8 @@
'change password': 'change password',
'customize me!': 'मुझे अनुकूलित (कस्टमाइज़) करें!',
'data uploaded': 'डाटा अपलोड सम्पन्न ',
'database': 'डेटाबेस',
'database %s select': 'डेटाबेस %s चुनी हुई',
'Database': 'डेटाबेस',
'Database %s select': 'डेटाबेस %s चुनी हुई',
'db': 'db',
'design': 'रचना करें',
'done!': 'हो गया!',
@@ -77,11 +77,11 @@
'next 100 rows': 'अगले 100 पंक्तियाँ',
'or import from csv file': 'या csv फ़ाइल से आयात',
'previous 100 rows': 'पिछले 100 पंक्तियाँ',
'record': 'record',
'Record': 'Record',
'record does not exist': 'रिकॉर्ड मौजूद नहीं है',
'record id': 'रिकॉर्ड पहचानकर्ता (आईडी)',
'Record id': 'रिकॉर्ड पहचानकर्ता (आईडी)',
'register': 'register',
'state': 'स्थिति',
'table': 'तालिका',
'Table': 'तालिका',
'unable to parse csv file': 'csv फ़ाइल पार्स करने में असमर्थ',
}
+7 -7
View File
@@ -9,7 +9,7 @@
'%s %%{row} updated': '%s sorok frissítődtek',
'%s selected': '%s kiválasztott',
'Administrative interface': 'az adminisztrációs felületért kattints ide',
'Available databases and tables': 'Elérhető adatbázisok és táblák',
'Available Databases and Tables': 'Elérhető adatbázisok és táblák',
'Cannot be empty': 'Nem lehet üres',
'Check to delete': 'Törléshez válaszd ki',
'Client IP': 'Client IP',
@@ -50,7 +50,7 @@
'Registration key': 'Registration key',
'Reset Password key': 'Reset Password key',
'Role': 'Role',
'Rows in table': 'Sorok a táblában',
'Rows in Table': 'Sorok a táblában',
'Rows selected': 'Kiválasztott sorok',
'Stylesheet': 'Stylesheet',
'Sure you want to delete this object?': 'Biztos törli ezt az objektumot?',
@@ -71,8 +71,8 @@
'change password': 'jelszó megváltoztatása',
'customize me!': 'változtass meg!',
'data uploaded': 'adat feltöltve',
'database': 'adatbázis',
'database %s select': 'adatbázis %s kiválasztás',
'Database': 'adatbázis',
'Database %s select': 'adatbázis %s kiválasztás',
'db': 'db',
'design': 'design',
'done!': 'kész!',
@@ -88,11 +88,11 @@
'next 100 rows': 'következő 100 sor',
'or import from csv file': 'vagy betöltés csv fájlból',
'previous 100 rows': 'előző 100 sor',
'record': 'bejegyzés',
'Record': 'bejegyzés',
'record does not exist': 'bejegyzés nem létezik',
'record id': 'bejegyzés id',
'Record id': 'bejegyzés id',
'register': 'regisztráció',
'state': 'állapot',
'table': 'tábla',
'Table': 'tábla',
'unable to parse csv file': 'nem lehet a csv fájlt beolvasni',
}
+86 -7
View File
@@ -9,99 +9,178 @@
'%s selected': '%s selezionato',
'%Y-%m-%d': '%d/%m/%Y',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
'@markmin\x01Number of entries: **%s**': 'Number of entries: **%s**',
'About': 'About',
'Access Control': 'Access Control',
'Administrative Interface': 'Administrative Interface',
'Administrative interface': 'Interfaccia amministrativa',
'Ajax Recipes': 'Ajax Recipes',
'appadmin is disabled because insecure channel': 'Amministrazione (appadmin) disabilitata: comunicazione non sicura',
'Available databases and tables': 'Database e tabelle disponibili',
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
'Available Databases and Tables': 'Database e tabelle disponibili',
'Buy this book': 'Buy this book',
'cache': 'cache',
'Cannot be empty': 'Non può essere vuoto',
'change password': 'Cambia password',
'Check to delete': 'Seleziona per cancellare',
'Clear CACHE?': 'Clear CACHE?',
'Clear DISK': 'Clear DISK',
'Clear RAM': 'Clear RAM',
'Client IP': 'Client IP',
'Community': 'Community',
'Components and Plugins': 'Components and Plugins',
'Controller': 'Controller',
'Copyright': 'Copyright',
'Created By': 'Created By',
'Created On': 'Created On',
'Current request': 'Richiesta (request) corrente',
'Current response': 'Risposta (response) corrente',
'Current session': 'Sessione (session) corrente',
'customize me!': 'Personalizzami!',
'data uploaded': 'dati caricati',
'Database': 'Database',
'database': 'database',
'database %s select': 'database %s select',
'Database %s select': 'Database %s select',
'db': 'db',
'DB Model': 'Modello di DB',
'Delete': 'Delete',
'Delete:': 'Cancella:',
'Demo': 'Demo',
'Deployment Recipes': 'Deployment Recipes',
'Description': 'Descrizione',
'design': 'progetta',
'DISK': 'DISK',
'Disk Cleared': 'Disk Cleared',
'Documentation': 'Documentazione',
"Don't know what to do?": "Don't know what to do?",
'done!': 'fatto!',
'Download': 'Download',
'E-mail': 'E-mail',
'Edit': 'Modifica',
'Edit current record': 'Modifica record corrente',
'edit profile': 'modifica profilo',
'Edit This App': 'Modifica questa applicazione',
'Email and SMS': 'Email and SMS',
'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g',
'Errors': 'Errors',
'export as csv file': 'esporta come file CSV',
'FAQ': 'FAQ',
'First name': 'Nome',
'Forms and Validators': 'Forms and Validators',
'Free Applications': 'Free Applications',
'Group %(group_id)s created': 'Group %(group_id)s created',
'Group ID': 'ID Gruppo',
'Group uniquely assigned to user %(id)s': 'Group uniquely assigned to user %(id)s',
'Groups': 'Groups',
'hello': 'hello',
'hello world': 'salve mondo',
'Hello World': 'Salve Mondo',
'Hello World in a flash!': 'Salve Mondo in un flash!',
'Home': 'Home',
'How did you get here?': 'How did you get here?',
'import': 'import',
'Import/Export': 'Importa/Esporta',
'Index': 'Indice',
'insert new': 'inserisci nuovo',
'insert new %s': 'inserisci nuovo %s',
'Internal State': 'Stato interno',
'Introduction': 'Introduction',
'Invalid email': 'Email non valida',
'Invalid login': 'Invalid login',
'Invalid Query': 'Richiesta (query) non valida',
'invalid request': 'richiesta non valida',
'Is Active': 'Is Active',
'Last name': 'Cognome',
'Layout': 'Layout',
'Layout Plugins': 'Layout Plugins',
'Layouts': 'Layouts',
'Live Chat': 'Live Chat',
'Logged in': 'Logged in',
'login': 'accesso',
'Login': 'Login',
'logout': 'uscita',
'Logout': 'Logout',
'Lost Password': 'Lost Password',
'Lost password?': 'Lost password?',
'lost password?': 'dimenticato la password?',
'Main Menu': 'Menu principale',
'Manage Cache': 'Manage Cache',
'Menu Model': 'Menu Modelli',
'Modified By': 'Modified By',
'Modified On': 'Modified On',
'My Sites': 'My Sites',
'Name': 'Nome',
'New Record': 'Nuovo elemento (record)',
'new record inserted': 'nuovo record inserito',
'next 100 rows': 'prossime 100 righe',
'No databases in this application': 'Nessun database presente in questa applicazione',
'not authorized': 'non autorizzato',
'Object or table name': 'Object or table name',
'Online examples': 'Vedere gli esempi',
'or import from csv file': 'oppure importa da file CSV',
'Origin': 'Origine',
'Other Plugins': 'Other Plugins',
'Other Recipes': 'Other Recipes',
'Overview': 'Overview',
'Password': 'Password',
"Password fields don't match": "Password fields don't match",
'please input your password again': 'please input your password again',
'Plugins': 'Plugins',
'Powered by': 'Powered by',
'Preface': 'Preface',
'previous 100 rows': '100 righe precedenti',
'Profile': 'Profile',
'Python': 'Python',
'Query:': 'Richiesta (query):',
'record': 'record',
'Quick Examples': 'Quick Examples',
'RAM': 'RAM',
'Ram Cleared': 'Ram Cleared',
'Recipes': 'Recipes',
'Record': 'Record',
'record does not exist': 'il record non esiste',
'record id': 'record id',
'Record ID': 'Record ID',
'Record id': 'Record id',
'Register': 'Register',
'register': 'registrazione',
'Registration identifier': 'Registration identifier',
'Registration key': 'Chiave di Registazione',
'Registration successful': 'Registration successful',
'Remember me (for 30 days)': 'Remember me (for 30 days)',
'Reset Password key': 'Resetta chiave Password ',
'Role': 'Ruolo',
'Rows in table': 'Righe nella tabella',
'Rows in Table': 'Righe nella tabella',
'Rows selected': 'Righe selezionate',
'Semantic': 'Semantic',
'Services': 'Services',
'state': 'stato',
'Stylesheet': 'Foglio di stile (stylesheet)',
'submit': 'submit',
'Submit': 'Submit',
'Support': 'Support',
'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?',
'table': 'tabella',
'Table': 'tabella',
'Table name': 'Nome tabella',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La richiesta (query) è una condizione come ad esempio "db.tabella1.campo1==\'valore\'". Una condizione come "db.tabella1.campo1==db.tabella2.campo2" produce un "JOIN" SQL.',
'The Core': 'The Core',
'The output of the file is a dictionary that was rendered by the view %s': 'L\'output del file è un "dictionary" che è stato visualizzato dalla vista %s',
'The Views': 'The Views',
'This App': 'This App',
'This is a copy of the scaffolding application': "Questa è una copia dell'applicazione di base (scaffold)",
'Timestamp': 'Ora (timestamp)',
'Twitter': 'Twitter',
'unable to parse csv file': 'non riesco a decodificare questo file CSV',
'Update': 'Update',
'Update:': 'Aggiorna:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).',
'User %(id)s Logged-in': 'User %(id)s Logged-in',
'User %(id)s Registered': 'User %(id)s Registered',
'User ID': 'ID Utente',
'Verify Password': 'Verify Password',
'Videos': 'Videos',
'View': 'Vista',
'Welcome': 'Welcome',
'Welcome %s': 'Benvenuto %s',
'Welcome to web2py': 'Benvenuto su web2py',
'Welcome to web2py!': 'Welcome to web2py!',
'Which called the function %s located in the file %s': 'che ha chiamato la funzione %s presente nel file %s',
'You are successfully running web2py': 'Stai eseguendo web2py con successo',
'You can modify this application and adapt it to your needs': 'Puoi modificare questa applicazione adattandola alle tue necessità',
+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: you cannot edit the running application!': 'LET OP: je kan de applicatie die nu draait niet editen!',
'Authentication': 'Authenticatie',
'Available databases and tables': 'Beschikbare databases en tabellen',
'Available Databases and Tables': 'Beschikbare databases en tabellen',
'Back': 'Terug',
'Buy this book': 'Koop dit boek',
'Cache': 'Cache',
@@ -95,8 +95,8 @@
'customize me!': 'pas me aan!',
'data uploaded': 'data geupload',
'Database': 'Database',
'database': 'database',
'database %s select': 'database %s select',
'Database': 'Database',
'Database %s select': 'Database %s select',
'database administration': 'database administratie',
'Date and Time': 'Datum en Tijd',
'db': 'db',
@@ -260,9 +260,9 @@
'RAM Cache Keys': 'RAM Cache Keys',
'Ram Cleared': 'Ram Geleegd',
'Recipes': 'Recepten',
'record': 'record',
'Record': 'Record',
'record does not exist': 'record bestaat niet',
'record id': 'record id',
'Record id': 'Record id',
'Record ID': 'Record ID',
'register': 'registreer',
'Register': 'Registreer',
@@ -277,7 +277,7 @@
'restore': 'herstel',
'revert': 'herstel',
'Role': 'Rol',
'Rows in table': 'Rijen in tabel',
'Rows in Table': 'Rijen in tabel',
'Rows selected': 'Rijen geselecteerd',
'save': 'bewaar',
'Save profile': 'Bewaar profiel',
@@ -300,7 +300,7 @@
'submit': 'submit',
'Support': 'Support',
'Sure you want to delete this object?': 'Weet je zeker dat je dit object wilt verwijderen?',
'table': 'Tabel',
'Table': 'Tabel',
'Table name': 'Tabelnaam',
'test': 'test',
'Testing application': 'Applicatie testen',
+7 -7
View File
@@ -10,7 +10,7 @@
'%s selected': '%s wybranych',
'Administrative interface': 'Kliknij aby przejść do panelu administracyjnego',
'Authentication': 'Uwierzytelnienie',
'Available databases and tables': 'Dostępne bazy danych i tabele',
'Available Databases and Tables': 'Dostępne bazy danych i tabele',
'Cannot be empty': 'Nie może być puste',
'Change Password': 'Zmień hasło',
'Check to delete': 'Zaznacz aby usunąć',
@@ -59,7 +59,7 @@
'Register': 'Zarejestruj',
'Registration key': 'Klucz rejestracji',
'Role': 'Rola',
'Rows in table': 'Wiersze w tabeli',
'Rows in Table': 'Wiersze w tabeli',
'Rows selected': 'Wybrane wiersze',
'Stylesheet': 'Arkusz stylów',
'Submit': 'Wyślij',
@@ -83,8 +83,8 @@
'change password': 'change password',
'customize me!': 'dostosuj mnie!',
'data uploaded': 'dane wysłane',
'database': 'baza danych',
'database %s select': 'wybór z bazy danych %s',
'Database': 'baza danych',
'Database %s select': 'wybór z bazy danych %s',
'db': 'baza danych',
'design': 'projektuj',
'done!': 'zrobione!',
@@ -99,11 +99,11 @@
'next 100 rows': 'następne 100 wierszy',
'or import from csv file': 'lub zaimportuj z pliku csv',
'previous 100 rows': 'poprzednie 100 wierszy',
'record': 'rekord',
'Record': 'rekord',
'record does not exist': 'rekord nie istnieje',
'record id': 'id rekordu',
'Record id': 'id rekordu',
'register': 'register',
'state': 'stan',
'table': 'tabela',
'Table': 'tabela',
'unable to parse csv file': 'nie można sparsować pliku csv',
}
+7 -7
View File
@@ -12,7 +12,7 @@
'Access Control': 'Access Control',
'Administrative interface': 'Interface administrativa',
'Ajax Recipes': 'Ajax Recipes',
'Available databases and tables': 'Bancos de dados e tabelas disponíveis',
'Available Databases and Tables': 'Bancos de dados e tabelas disponíveis',
'Buy this book': 'Buy this book',
'Cannot be empty': 'Não pode ser vazio',
'Check to delete': 'Marque para apagar',
@@ -79,7 +79,7 @@
'Reset Password key': 'Reset Password key',
'Resources': 'Resources',
'Role': 'Role',
'Rows in table': 'Linhas na tabela',
'Rows in Table': 'Linhas na tabela',
'Rows selected': 'Linhas selecionadas',
'Semantic': 'Semantic',
'Services': 'Services',
@@ -115,8 +115,8 @@
'change password': 'modificar senha',
'customize me!': 'Personalize-me!',
'data uploaded': 'dados enviados',
'database': 'banco de dados',
'database %s select': 'Selecionar banco de dados %s',
'Database': 'banco de dados',
'Database %s select': 'Selecionar banco de dados %s',
'db': 'bd',
'design': 'design',
'done!': 'concluído!',
@@ -132,11 +132,11 @@
'next 100 rows': 'próximas 100 linhas',
'or import from csv file': 'ou importar de um arquivo csv',
'previous 100 rows': '100 linhas anteriores',
'record': 'registro',
'Record': 'registro',
'record does not exist': 'registro não existe',
'record id': 'id do registro',
'Record id': 'id do registro',
'register': 'Registre-se',
'state': 'estado',
'table': 'tabela',
'Table': 'tabela',
'unable to parse csv file': 'não foi possível analisar arquivo csv',
}
+7 -7
View File
@@ -12,7 +12,7 @@
'Administrative interface': 'Painel administrativo',
'Author Reference Auth User': 'Author Reference Auth User',
'Author Reference Auth User.username': 'Author Reference Auth User.username',
'Available databases and tables': 'bases de dados e tabelas disponíveis',
'Available Databases and Tables': 'bases de dados e tabelas disponíveis',
'Cannot be empty': 'não pode ser vazio',
'Category Create': 'Category Create',
'Category Select': 'Category Select',
@@ -58,7 +58,7 @@
'Powered by': 'Suportado por',
'Query:': 'Interrogação:',
'Replyto Reference Post': 'Replyto Reference Post',
'Rows in table': 'Linhas numa tabela',
'Rows in Table': 'Linhas numa tabela',
'Rows selected': 'Linhas seleccionadas',
'Stylesheet': 'Folha de estilo',
'Sure you want to delete this object?': 'Tem a certeza que deseja eliminar este objecto?',
@@ -83,8 +83,8 @@
'create new post': 'create new post',
'customize me!': 'Personaliza-me!',
'data uploaded': 'informação enviada',
'database': 'base de dados',
'database %s select': 'selecção de base de dados %s',
'Database': 'base de dados',
'Database %s select': 'selecção de base de dados %s',
'db': 'bd',
'design': 'design',
'done!': 'concluído!',
@@ -102,9 +102,9 @@
'next 100 rows': 'próximas 100 linhas',
'or import from csv file': 'ou importe a partir de ficheiro csv',
'previous 100 rows': '100 linhas anteriores',
'record': 'registo',
'Record': 'registo',
'record does not exist': 'registo inexistente',
'record id': 'id de registo',
'Record id': 'id de registo',
'register': 'register',
'search category': 'search category',
'search comment': 'search comment',
@@ -116,6 +116,6 @@
'show comment': 'show comment',
'show post': 'show post',
'state': 'estado',
'table': 'tabela',
'Table': 'tabela',
'unable to parse csv file': 'não foi possível carregar ficheiro csv',
}
+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: you cannot edit the running application!': 'ATENȚIE: nu puteți edita o aplicație în curs de execuție!',
'Authentication': 'Autentificare',
'Available databases and tables': 'Baze de date și tabele disponibile',
'Available Databases and Tables': 'Baze de date și tabele disponibile',
'Back': 'Înapoi',
'Buy this book': 'Cumpără această carte',
'cache': 'cache',
@@ -92,8 +92,8 @@
'customize me!': 'Personalizează-mă!',
'data uploaded': 'date încărcate',
'Database': 'Baza de date',
'database': 'bază de date',
'database %s select': 'selectare bază de date %s',
'Database': 'bază de date',
'Database %s select': 'selectare bază de date %s',
'database administration': 'administrare bază de date',
'Date and Time': 'Data și ora',
'db': 'db',
@@ -247,9 +247,9 @@
'Quick Examples': 'Exemple rapide',
'RAM Cache Keys': 'Chei cache RAM',
'Recipes': 'Rețete',
'record': 'înregistrare',
'Record': 'înregistrare',
'record does not exist': 'înregistrare inexistentă',
'record id': 'id înregistrare',
'Record id': 'id înregistrare',
'Record ID': 'ID înregistrare',
'register': 'înregistrare',
'Register': 'Înregistrare',
@@ -264,7 +264,7 @@
'restore': 'restaurare',
'revert': 'revenire',
'Role': 'Rol',
'Rows in table': 'Linii în tabel',
'Rows in Table': 'Linii în tabel',
'Rows selected': 'Linii selectate',
'save': 'salvare',
'Save profile': 'Salvează profil',
@@ -284,7 +284,7 @@
'Submit': 'Înregistrează',
'Support': 'Suport',
'Sure you want to delete this object?': 'Sigur ștergeți acest obiect?',
'table': 'tabel',
'Table': 'tabel',
'Table name': 'Nume tabel',
'test': 'test',
'Testing application': 'Testare aplicație',
+6 -6
View File
@@ -28,7 +28,7 @@
'Administrative interface': 'административный интерфейс',
'Ajax Recipes': 'Ajax Recipes',
'Are you sure you want to delete this object?': 'Вы уверены, что хотите удалить этот объект?',
'Available databases and tables': 'Базы данных и таблицы',
'Available Databases and Tables': 'Базы данных и таблицы',
'Buy this book': 'Buy this book',
'cache': 'cache',
'Cannot be empty': 'Пустое значение недопустимо',
@@ -45,9 +45,9 @@
'Current session': 'Текущая сессия',
'customize me!': 'настройте внешний вид!',
'data uploaded': 'данные загружены',
'database': 'база данных',
'Database': 'база данных',
'Database': 'Database',
'database %s select': 'выбор базы данных %s',
'Database %s select': 'выбор базы данных %s',
'db': 'БД',
'DB Model': 'DB Model',
'Delete:': 'Удалить:',
@@ -129,7 +129,7 @@
'Quick Examples': 'Quick Examples',
'Recipes': 'Recipes',
'record does not exist': 'запись не найдена',
'record id': 'id записи',
'Record id': 'id записи',
'Record ID': 'ID записи',
'Register': 'Зарегистрироваться',
'Registration identifier': 'Registration identifier',
@@ -137,7 +137,7 @@
'Remember me (for 30 days)': 'Запомнить меня (на 30 дней)',
'Reset Password key': 'Сбросить ключ пароля',
'Role': 'Роль',
'Rows in table': 'Строк в таблице',
'Rows in Table': 'Строк в таблице',
'Rows selected': 'Выделено строк',
'Semantic': 'Semantic',
'Services': 'Services',
@@ -146,7 +146,7 @@
'Submit': 'Отправить',
'Support': 'Support',
'Sure you want to delete this object?': 'Подтвердите удаление объекта',
'table': 'таблица',
'Table': 'таблица',
'Table name': 'Имя таблицы',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Запрос" - это условие вида "db.table1.field1==\'значение\'". Выражение вида "db.table1.field1==db.table2.field2" формирует SQL JOIN.',
'The Core': 'The Core',
+7 -7
View File
@@ -9,7 +9,7 @@
'%s %%{row} updated': '%s upravených záznamov',
'%s selected': '%s označených',
'Administrative interface': 'pre administrátorské rozhranie kliknite sem',
'Available databases and tables': 'Dostupné databázy a tabuľky',
'Available Databases and Tables': 'Dostupné databázy a tabuľky',
'Cannot be empty': 'Nemôže byť prázdne',
'Check to delete': 'Označiť na zmazanie',
'Controller': 'Controller',
@@ -56,7 +56,7 @@
'Remember me (for 30 days)': 'Zapamätaj si ma (na 30 dní)',
'Reset Password key': 'Nastaviť registračný kľúč',
'Role': 'Rola',
'Rows in table': 'riadkov v tabuľke',
'Rows in Table': 'riadkov v tabuľke',
'Rows selected': 'označených riadkov',
'Stylesheet': 'Stylesheet',
'Submit': 'Odoslať',
@@ -85,8 +85,8 @@
'cache': 'cache',
'customize me!': 'prispôsob ma!',
'data uploaded': 'údaje naplnené',
'database': 'databáza',
'database %s select': 'databáza %s výber',
'Database': 'databáza',
'Database %s select': 'databáza %s výber',
'db': 'db',
'design': 'návrh',
'done!': 'hotovo!',
@@ -102,11 +102,11 @@
'or import from csv file': 'alebo naimportovať z csv súboru',
'password': 'heslo',
'previous 100 rows': 'predchádzajúcich 100 riadkov',
'record': 'záznam',
'Record': 'záznam',
'record does not exist': 'záznam neexistuje',
'record id': 'id záznamu',
'Record id': 'id záznamu',
'register': 'registrovať',
'state': 'stav',
'table': 'tabuľka',
'Table': 'tabuľka',
'unable to parse csv file': 'nedá sa načítať csv súbor',
}
+7 -7
View File
@@ -36,7 +36,7 @@
'Ajax Recipes': 'Рецепти для Ajax',
'appadmin is disabled because insecure channel': 'використовується незахищенний канал (HTTP). Appadmin вимкнено',
'Are you sure you want to delete this object?': "Ви впевнені, що хочете вилучити цей об'єкт?",
'Available databases and tables': 'Доступні бази даних та таблиці',
'Available Databases and Tables': 'Доступні бази даних та таблиці',
'Buy this book': 'Купити книжку',
'cache': 'кеш',
'Cache': 'Кеш',
@@ -58,9 +58,9 @@
'Current session': 'Поточна сесія (current session)',
'customize me!': 'причепуріть мене!',
'data uploaded': 'дані завантажено',
'database': 'база даних',
'Database': 'база даних',
'Database': 'База даних',
'database %s select': 'Вибірка з бази даних %s',
'Database %s select': 'Вибірка з бази даних %s',
'db': 'база даних',
'DB Model': 'Модель БД',
'Delete:': 'Вилучити:',
@@ -157,10 +157,10 @@
'RAM Cache Keys': 'Ключі ОЗП-кешу',
'Ram Cleared': 'ОЗП-кеш очищено',
'Recipes': 'Рецепти',
'record': 'запис',
'Record': 'запис',
'Record %(id)s updated': 'Запис %(id)s змінено',
'record does not exist': 'запису не існує',
'record id': 'ід. запису',
'Record id': 'ід. запису',
'Record ID': 'Ід.запису',
'Record Updated': 'Запис змінено',
'Register': 'Реєстрація',
@@ -171,7 +171,7 @@
'Request reset password': 'Запит на зміну пароля',
'Reset Password key': 'Ключ скидання пароля',
'Role': 'Роль',
'Rows in table': 'Рядки в таблиці',
'Rows in Table': 'Рядки в таблиці',
'Rows selected': 'Відмічено рядків',
'Save profile': 'Зберегти параметри',
'Semantic': 'Семантика',
@@ -183,7 +183,7 @@
'submit': 'submit',
'Submit': 'Застосувати',
'Support': 'Підтримка',
'table': 'Таблиця',
'Table': 'Таблиця',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Запит" це умова, на зразок "db.table1.field1==\'значення\'". Вираз "db.table1.field1==db.table2.field2" повертає результат об\'єднання (SQL JOIN) таблиць.',
'The Core': 'Ядро',
'The output of the file is a dictionary that was rendered by the view %s': 'Результат функції - словник пар (назва=значення) було відображено з допомогою відображення (view) %s',
+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"',
'Authentication': '驗證',
'Available databases and tables': '可提供的資料庫和資料表',
'Available Databases and Tables': '可提供的資料庫和資料表',
'Cannot be empty': '不可空白',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': '無法編譯:應用程式中含有錯誤,請除錯後再試一次.',
'Change Password': '變更密碼',
@@ -101,7 +101,7 @@
'Reset Password key': '重設密碼',
'Resolve Conflict file': '解決衝突檔案',
'Role': '角色',
'Rows in table': '在資料表裏的資料',
'Rows in Table': '在資料表裏的資料',
'Rows selected': '筆資料被選擇',
'Saved file hash:': '檔案雜湊值已紀錄:',
'Static files': '靜態檔案',
@@ -144,8 +144,8 @@
'change password': '變更密碼',
'customize me!': '請調整我!',
'data uploaded': '資料已上傳',
'database': '資料庫',
'database %s select': '已選擇 %s 資料庫',
'Database': '資料庫',
'Database %s select': '已選擇 %s 資料庫',
'db': 'db',
'design': '設計',
'done!': '完成!',
@@ -160,11 +160,11 @@
'next 100 rows': '往後 100 筆',
'or import from csv file': '或是從逗號分隔檔(CSV)匯入',
'previous 100 rows': '往前 100 筆',
'record': '紀錄',
'Record': '紀錄',
'record does not exist': '紀錄不存在',
'record id': '紀錄編號',
'Record id': '紀錄編號',
'register': '註冊',
'state': '狀態',
'table': '資料表',
'Table': '資料表',
'unable to parse csv file': '無法解析逗號分隔檔(csv)',
}
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>
{{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}}
<table>
{{for db in sorted(databases):}}
{{for table in databases[db].tables:}}
{{qry='%s.%s.id>0'%(db,table)}}
@@ -28,20 +29,24 @@
{{qry=''}}
{{pass}}
{{pass}}
<h3>
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
</h3>
[ {{=A(str(T('insert new'))+' '+table,_href=URL('insert',args=[db,table]))}} ]
<br /><br />
<tr>
<th style="font-size: 1.75em;">
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
</th>
<td>
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
</td>
</tr>
{{pass}}
</table>
{{pass}}
{{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>
{{if table:}}
[ {{=A(str(T('insert new %s'))%table,_href=URL('insert',args=[request.args[0],table]))}} ]<br/><br/>
<h3>{{=T("Rows in table")}}</h3><br/>
{{if table:}}
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
<h3>{{=T("Rows in Table")}}</h3><br/>
{{else:}}
<h3>{{=T("Rows selected")}}</h3><br/>
{{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>
<br/><br/>
<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 stop<nrows:}}[ {{=A(T('next 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)),_class="btn")}}{{pass}}
{{if rows:}}
<div style="overflow: auto;" width="80%">
{{linkto=URL('update',args=request.args[0])}}
@@ -61,35 +66,35 @@
</div>
{{pass}}
<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 ''}}
{{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'):}}
{{fieldname=table._primarykey[0]}}
{{dbname=request.args[0]}}
{{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}}
{{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{=T("Table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{else:}}
{{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{=T("Table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{pass}}
</h2>
<h3>{{=T("New Record")}}</h3><br/>
{{=form}}
{{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'):}}
{{fieldname=request.vars.keys()[0]}}
{{dbname=request.args[0]}}
{{tablename=request.args[1]}}
{{cond = table[fieldname].type in ['string','text'] and '!=""' or '>0'}}
{{=T("table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{=T("record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
{{=T("Table")}} {{=A(tablename,_href=URL('select',args=dbname,vars=dict(query='%s.%s.%s%s'%(dbname,tablename,fieldname,cond))))}}
{{=T("Record")}} {{=A('%s=%s'%request.vars.items()[0],_href=URL('update',args=request.args[:2],vars=request.vars))}}
{{else:}}
{{=T("table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{=T("record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
{{=T("Table")}} {{=A(request.args[1],_href=URL('select',args=request.args[0],vars=dict(query='%s.%s.id>0'%tuple(request.args[:2]))))}}
{{=T("Record id")}} {{=A(request.args[2],_href=URL('update',args=request.args[:3]))}}
{{pass}}
</h2>
<h3>{{=T("Edit current record")}}</h3><br/><br/>{{=form}}
+1 -1
View File
@@ -298,7 +298,7 @@ class CacheOnDisk(CacheAbstract):
try:
storage = self._open_shelf_with_lock()
try:
if not storage.has_key(CacheAbstract.cache_stats_name):
if not CacheAbstract.cache_stats_name in storage:
storage[CacheAbstract.cache_stats_name] = {
'hit_total': 0,
'misses': 0,
+41 -40
View File
@@ -50,6 +50,8 @@ is_pypy = settings.global_settings.is_pypy
is_gae = settings.global_settings.web2py_runtime_gae
is_jython = settings.global_settings.is_jython
pjoin = os.path.join
TEST_CODE = \
r"""
def _TEST():
@@ -319,7 +321,7 @@ def local_import_aux(name, reload_force=False, app='welcome'):
"""
OLD IMPLEMENTATION:
items = name.replace('/','.').split('.')
filename, modulepath = items[-1], os.path.join(apath,'modules',*items[:-1])
filename, modulepath = items[-1], pjoin(apath,'modules',*items[:-1])
imp.acquire_lock()
try:
file=None
@@ -348,11 +350,9 @@ def build_environment(request, response, session, store_current=True):
"""
Build the environment dictionary into which web2py files are executed.
"""
_validators = validators
_html = html
environment = dict(map(lambda key: (key, getattr(_html, key)), _html.__all__))
environment.update(map(lambda key: (key, getattr(_validators, key)), _validators.__all__))
h,v = html,validators
environment = dict((k,getattr(h,k)) for k in h.__all__)
environment.update((k,getattr(v, k)) for k in v.__all__)
if not request.env:
request.env = Storage()
@@ -389,7 +389,7 @@ def build_environment(request, response, session, store_current=True):
environment['local_import'] = \
lambda name, reload=False, app=request.application:\
local_import_aux(name,reload,app)
BaseAdapter.set_folder(os.path.join(request.folder, 'databases'))
BaseAdapter.set_folder(pjoin(request.folder, 'databases'))
response._view_environment = copy.copy(environment)
return environment
@@ -419,11 +419,11 @@ def compile_views(folder):
Compiles all the views in the application specified by `folder`
"""
path = os.path.join(folder, 'views')
path = pjoin(folder, 'views')
for file in listdir(path, '^[\w/\-]+(\.\w+)+$'):
data = parse_template(file, path)
filename = ('views/%s.py' % file).replace('/', '_').replace('\\', '_')
filename = os.path.join(folder, 'compiled', filename)
filename = pjoin(folder, 'compiled', filename)
write_file(filename, data)
save_pyc(filename)
os.unlink(filename)
@@ -434,10 +434,10 @@ def compile_models(folder):
Compiles all the models in the application specified by `folder`
"""
path = os.path.join(folder, 'models')
path = pjoin(folder, 'models')
for file in listdir(path, '.+\.py$'):
data = read_file(os.path.join(path, file))
filename = os.path.join(folder, 'compiled','models',file)
data = read_file(pjoin(path, file))
filename = pjoin(folder, 'compiled','models',file)
mktree(filename)
write_file(filename, data)
save_pyc(filename)
@@ -449,15 +449,15 @@ def compile_controllers(folder):
Compiles all the controllers in the application specified by `folder`
"""
path = os.path.join(folder, 'controllers')
path = pjoin(folder, 'controllers')
for file in listdir(path, '.+\.py$'):
### why is this here? save_pyc(os.path.join(path, file))
data = read_file(os.path.join(path,file))
### why is this here? save_pyc(pjoin(path, file))
data = read_file(pjoin(path,file))
exposed = regex_expose.findall(data)
for function in exposed:
command = data + "\nresponse._vars=response._caller(%s)\n" % \
function
filename = os.path.join(folder, 'compiled', ('controllers/'
filename = pjoin(folder, 'compiled', ('controllers/'
+ file[:-3]).replace('/', '_')
+ '_' + function + '.py')
write_file(filename, command)
@@ -474,18 +474,18 @@ def run_models_in(environment):
folder = environment['request'].folder
c = environment['request'].controller
f = environment['request'].function
cpath = os.path.join(folder, 'compiled')
cpath = pjoin(folder, 'compiled')
if os.path.exists(cpath):
for model in listdir(cpath, '^models_\w+\.pyc$', 0):
restricted(read_pyc(model), environment, layer=model)
path = os.path.join(cpath, 'models')
path = pjoin(cpath, 'models')
models = listdir(path, '^\w+\.pyc$',0,sort=False)
compiled=True
else:
path = os.path.join(folder, 'models')
path = pjoin(folder, 'models')
models = listdir(path, '^\w+\.py$',0,sort=False)
compiled=False
paths = (path, os.path.join(path,c), os.path.join(path,c,f))
paths = (path, pjoin(path,c), pjoin(path,c,f))
for model in models:
if not os.path.split(model)[0] in paths and c!='appadmin':
continue
@@ -509,11 +509,11 @@ def run_controller_in(controller, function, environment):
# if compiled should run compiled!
folder = environment['request'].folder
path = os.path.join(folder, 'compiled')
path = pjoin(folder, 'compiled')
badc = 'invalid controller (%s/%s)' % (controller, function)
badf = 'invalid function (%s/%s)' % (controller, function)
if os.path.exists(path):
filename = os.path.join(path, 'controllers_%s_%s.pyc'
filename = pjoin(path, 'controllers_%s_%s.pyc'
% (controller, function))
if not os.path.exists(filename):
raise HTTP(404,
@@ -528,7 +528,7 @@ def run_controller_in(controller, function, environment):
[add_path_first(path) for path in paths]
# TESTING END
filename = os.path.join(folder, 'controllers/%s.py'
filename = pjoin(folder, 'controllers/%s.py'
% controller)
if not os.path.exists(filename):
raise HTTP(404,
@@ -539,7 +539,7 @@ def run_controller_in(controller, function, environment):
code += TEST_CODE
restricted(code, environment, layer=filename)
else:
filename = os.path.join(folder, 'controllers/%s.py'
filename = pjoin(folder, 'controllers/%s.py'
% controller)
if not os.path.exists(filename):
raise HTTP(404,
@@ -576,19 +576,20 @@ def run_view_in(environment):
request = environment['request']
response = environment['response']
view = response.view
folder = request.folder
path = os.path.join(folder, 'compiled')
badv = 'invalid view (%s)' % response.view
path = pjoin(folder, 'compiled')
badv = 'invalid view (%s)' % view
patterns = response.generic_patterns or []
regex = re.compile('|'.join(map(fnmatch.translate, patterns)))
short_action = '%(controller)s/%(function)s.%(extension)s' % request
allow_generic = patterns and regex.search(short_action)
if not isinstance(response.view, str):
ccode = parse_template(response.view, os.path.join(folder, 'views'),
if not isinstance(view, str):
ccode = parse_template(view, pjoin(folder, 'views'),
context=environment)
restricted(ccode, environment, 'file stream')
elif os.path.exists(path):
x = response.view.replace('/', '_')
x = view.replace('/', '_')
files = ['views_%s.pyc' % x]
if allow_generic:
files.append('views_generic.%s.pyc' % request.extension)
@@ -599,7 +600,7 @@ def run_view_in(environment):
files.append('views_generic.pyc')
# end backward compatibility code
for f in files:
filename = os.path.join(path,f)
filename = pjoin(path,f)
if os.path.exists(filename):
code = read_pyc(filename)
restricted(code, environment, layer=filename)
@@ -608,10 +609,10 @@ def run_view_in(environment):
rewrite.thread.routes.error_message % badv,
web2py_error=badv)
else:
filename = os.path.join(folder, 'views', response.view)
filename = pjoin(folder, 'views', view)
if not os.path.exists(filename) and allow_generic:
response.view = 'generic.' + request.extension
filename = os.path.join(folder, 'views', response.view)
view = 'generic.' + request.extension
filename = pjoin(folder, 'views', view)
if not os.path.exists(filename):
raise HTTP(404,
rewrite.thread.routes.error_message % badv,
@@ -619,12 +620,12 @@ def run_view_in(environment):
layer = filename
if is_gae:
ccode = getcfs(layer, filename,
lambda: compile2(parse_template(response.view,
os.path.join(folder, 'views'),
lambda: compile2(parse_template(view,
pjoin(folder, 'views'),
context=environment),layer))
else:
ccode = parse_template(response.view,
os.path.join(folder, 'views'),
ccode = parse_template(view,
pjoin(folder, 'views'),
context=environment)
restricted(ccode, environment, layer)
@@ -633,8 +634,8 @@ def remove_compiled_application(folder):
Deletes the folder `compiled` containing the compiled application.
"""
try:
shutil.rmtree(os.path.join(folder, 'compiled'))
path = os.path.join(folder, 'controllers')
shutil.rmtree(pjoin(folder, 'compiled'))
path = pjoin(folder, 'controllers')
for file in listdir(path,'.*\.pyc$',drop=False):
os.unlink(file)
except OSError:
@@ -646,7 +647,7 @@ def compile_application(folder):
Compiles all models, views, controller for the application in `folder`.
"""
remove_compiled_application(folder)
os.mkdir(os.path.join(folder, 'compiled'))
os.mkdir(pjoin(folder, 'compiled'))
compile_models(folder)
compile_controllers(folder)
compile_views(folder)
+61 -48
View File
@@ -117,7 +117,8 @@ class Request(Storage):
user_agent_parser.detect(self.env.http_user_agent)
user_agent = Storage(user_agent)
for key,value in user_agent.items():
if isinstance(value,dict): user_agent[key] = Storage(value)
if isinstance(value,dict):
user_agent[key] = Storage(value)
return user_agent
def requires_https(self):
@@ -222,9 +223,9 @@ class Response(Storage):
return page
def include_meta(self):
s = '\n'
for key,value in (self.meta or {}).items():
s += '<meta name="%s" content="%s" />\n' % (key,xmlescape(value))
s = '\n'.join(
'<meta name="%s" content="%s" />\n' % (k,xmlescape(v))
for k,v in (self.meta or {}).iteritems())
self.write(s,escape=False)
def include_files(self):
@@ -297,14 +298,15 @@ class Response(Storage):
default to the last request argument otherwise)
"""
headers = self.headers
# for attachment settings and backward compatibility
keys = [item.lower() for item in self.headers]
keys = [item.lower() for item in headers]
if attachment:
if filename is None:
attname = ""
else:
attname = filename
self.headers["Content-Disposition"] = \
headers["Content-Disposition"] = \
"attachment;filename=%s" % attname
if not request:
@@ -313,30 +315,31 @@ class Response(Storage):
stream_file_or_304_or_206(stream,
chunk_size=chunk_size,
request=request,
headers=self.headers)
headers=headers)
# ## the following is for backward compatibility
if hasattr(stream, 'name'):
filename = stream.name
if filename and not 'content-type' in keys:
self.headers['Content-Type'] = contenttype(filename)
headers['Content-Type'] = contenttype(filename)
if filename and not 'content-length' in keys:
try:
self.headers['Content-Length'] = \
headers['Content-Length'] = \
os.path.getsize(filename)
except OSError:
pass
env = request.env
# Internet Explorer < 9.0 will not allow downloads over SSL unless caching is enabled
if request.is_https and isinstance(request.env.http_user_agent,str) and \
not re.search(r'Opera', request.env.http_user_agent) and \
re.search(r'MSIE [5-8][^0-9]', request.env.http_user_agent):
self.headers['Pragma'] = 'cache'
self.headers['Cache-Control'] = 'private'
if request.is_https and isinstance(env.http_user_agent,str) and \
not re.search(r'Opera', env.http_user_agent) and \
re.search(r'MSIE [5-8][^0-9]', env.http_user_agent):
headers['Pragma'] = 'cache'
headers['Cache-Control'] = 'private'
if request and request.env.web2py_use_wsgi_file_wrapper:
wrapped = request.env.wsgi_file_wrapper(stream, chunk_size)
if request and env.web2py_use_wsgi_file_wrapper:
wrapped = env.wsgi_file_wrapper(stream, chunk_size)
else:
wrapped = streamer(stream, chunk_size=chunk_size)
return wrapped
@@ -364,11 +367,13 @@ class Response(Storage):
(filename, stream) = field.retrieve(name)
except IOError:
raise HTTP(404)
self.headers['Content-Type'] = contenttype(name)
headers = self.headers
headers['Content-Type'] = contenttype(name)
if attachment:
self.headers['Content-Disposition'] = \
"attachment; filename=%s" % filename
return self.stream(stream, chunk_size = chunk_size, request=request)
headers['Content-Disposition'] = \
'attachment; filename=%s' % filename
return self.stream(stream, chunk_size=chunk_size, request=request)
def json(self, data, default=None):
return json(data, default = default or custom_json)
@@ -405,8 +410,15 @@ class Response(Storage):
dbstats = [TABLE(*[TR(PRE(row[0]),'%.2fms' % (row[1]*1000)) \
for row in i.db._timings]) \
for i in thread.instances]
dbtables = dict([(i.uri, {'defined': sorted(list(set(i.db.tables) -
set(i.db._LAZY_TABLES.keys()))) or
'[no defined tables]',
'lazy': sorted(i.db._LAZY_TABLES.keys()) or
'[no lazy tables]'})
for i in thread.instances])
else:
dbstats = [] # if no db or on GAE
dbtables = {}
u = web2py_uuid()
return DIV(
BUTTON('design',_onclick="document.location='%s'" % admin),
@@ -416,6 +428,8 @@ class Response(Storage):
DIV(BEAUTIFY(current.session),_class="hidden",_id="session-%s"%u),
BUTTON('response',_onclick="jQuery('#response-%s').slideToggle()"%u),
DIV(BEAUTIFY(current.response),_class="hidden",_id="response-%s"%u),
BUTTON('db tables',_onclick="jQuery('#db-tables-%s').slideToggle()"%u),
DIV(BEAUTIFY(dbtables),_class="hidden",_id="db-tables-%s"%u),
BUTTON('db stats',_onclick="jQuery('#db-stats-%s').slideToggle()"%u),
DIV(BEAUTIFY(dbstats),_class="hidden",_id="db-stats-%s"%u),
SCRIPT("jQuery('.hidden').hide()")
@@ -452,14 +466,15 @@ class Session(Storage):
response.session_id_name = 'session_id_%s' % masterapp.lower()
# Load session data from cookie
cookies = request.cookies
if cookie_key:
response.session_cookie_key = cookie_key
response.session_cookie_key2 = hashlib.md5(cookie_key).digest()
cookie_name = request.application.lower()+'_session_data'
cookie_name = masterapp.lower()+'_session_data'
response.session_cookie_name = cookie_name
if cookie_data in request.cookies:
cookie_value = request.cookies[cookie_name].value
if cookie_name in cookies:
cookie_value = cookies[cookie_name].value
cookie_parts = cookie_value.split(":")
enc = cookie_parts[2]
cipher = AES.new(cookie_key)
@@ -477,9 +492,9 @@ class Session(Storage):
return
response.session_new = False
client = request.client and request.client.replace(':', '.')
if response.session_id_name in request.cookies:
if response.session_id_name in cookies:
response.session_id = \
request.cookies[response.session_id_name].value
cookies[response.session_id_name].value
if regex_session_id.match(response.session_id):
response.session_filename = \
os.path.join(up(request.folder), masterapp,
@@ -532,38 +547,35 @@ class Session(Storage):
table_migrate = False
tname = tablename + '_' + masterapp
table = db.get(tname, None)
Field = db.Field
if table is None:
table = db.define_table(
db.define_table(
tname,
db.Field('locked', 'boolean', default=False),
db.Field('client_ip', length=64),
db.Field('created_datetime', 'datetime',
Field('locked', 'boolean', default=False),
Field('client_ip', length=64),
Field('created_datetime', 'datetime',
default=request.now),
db.Field('modified_datetime', 'datetime'),
db.Field('unique_key', length=64),
db.Field('session_data', 'blob'),
Field('modified_datetime', 'datetime'),
Field('unique_key', length=64),
Field('session_data', 'blob'),
migrate=table_migrate,
)
table = db[tname] # to allow for lazy table
try:
# Get session data out of the database
# Key comes from the cookie
key = request.cookies[response.session_id_name].value
# Get session data out of the database
# Key comes from the cookie
key = cookies[response.session_id_name].value
(record_id, unique_key) = key.split(':')
if record_id == '0':
raise Exception, 'record_id == 0'
# Select from database.
# Select from database
rows = db(table.id == record_id).select()
# Make sure the session data exists in the database
# Make sure the session data exists in the database
if len(rows) == 0 or rows[0].unique_key != unique_key:
raise Exception, 'No record'
# rows[0].update_record(locked=True)
# Unpickle the data
# rows[0].update_record(locked=True)
# Unpickle the data
session_data = cPickle.loads(rows[0].session_data)
self.update(session_data)
except Exception:
@@ -573,8 +585,9 @@ class Session(Storage):
response._dbtable_and_field = \
(response.session_id_name, table, record_id, unique_key)
response.session_id = '%s:%s' % (record_id, unique_key)
response.cookies[response.session_id_name] = response.session_id
response.cookies[response.session_id_name]['path'] = '/'
rcookies = response.cookies
rcookies[response.session_id_name] = response.session_id
rcookies[response.session_id_name]['path'] = '/'
self.__hash = hashlib.md5(str(self)).digest()
if self.flash:
(response.flash, self.flash) = (self.flash, None)
+39 -33
View File
@@ -296,7 +296,8 @@ def URL(
if other.endswith('/'):
other += '/' # add trailing slash to make last trailing empty arg explicit
if vars.has_key('_signature'): vars.pop('_signature')
if '_signature' in vars:
vars.pop('_signature')
list_vars = []
for (key, vals) in sorted(vars.items()):
if not isinstance(vals, (list, tuple)):
@@ -387,7 +388,7 @@ def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=
"""
if not request.get_vars.has_key('_signature'):
if not '_signature' in request.get_vars:
return False # no signature in the request URL
# check if user_signature requires
@@ -484,15 +485,17 @@ class XmlComponent(object):
components += [other]
return CAT(*components)
def add_class(self, name):
def add_class(self, name):
""" add a class to _class attribute """
classes = set(self['_class'].split())|set(name.split())
c = self['_class']
classes = (set(c.split()) if c else set())|set(name.split())
self['_class'] = ' '.join(classes) if classes else None
return self
def remove_class(self, name):
""" remove a class from _class attribute """
classes = set(self['_class'].split())-set(name.split())
c = self['_class']
classes = (set(c.split()) if c else set())-set(name.split())
self['_class'] = ' '.join(classes) if classes else None
return self
@@ -656,17 +659,17 @@ class DIV(XmlComponent):
self.attributes = attributes
self._fixup()
# converts special attributes in components attributes
self._postprocessing()
self.parent = None
for c in self.components:
self._setnode(c)
self._postprocessing()
def update(self, **kargs):
"""
dictionary like updating of the tag attributes
"""
for (key, value) in kargs.items():
for (key, value) in kargs.iteritems():
self[key] = value
return self
@@ -811,7 +814,9 @@ class DIV(XmlComponent):
c.latest = self.latest
c.session = self.session
c.formname = self.formname
if hideerror: c['hideerror'] = hideerror
if hideerror and not \
self.attributes.get('hideerror',False):
c['hideerror'] = hideerror
newstatus = c._traverse(status,hideerror) and newstatus
# for input, textarea, select, option
@@ -1038,7 +1043,7 @@ class DIV(XmlComponent):
tag = getattr(self,'tag').replace('/', '')
if args and tag not in args:
check = False
for (key, value) in kargs.items():
for (key, value) in kargs.iteritems():
if key not in ['first_only', 'replace', 'find_text']:
if isinstance(value, (str, int)):
if self[key] != str(value):
@@ -1109,16 +1114,15 @@ class DIV(XmlComponent):
sibs = [s for s in self.parent.components if not s == self]
matches = []
first_only = False
if kargs.has_key("first_only"):
first_only = kargs["first_only"]
del kargs["first_only"]
if 'first_only' in kargs:
first_only = kargs.pop('first_only')
for c in sibs:
try:
check = True
tag = getattr(c,'tag').replace("/","")
if args and tag not in args:
check = False
for (key, value) in kargs.items():
for (key, value) in kargs.iteritems():
if c[key] != value:
check = False
if check:
@@ -1682,14 +1686,14 @@ class INPUT(DIV):
if name is None or name == '':
return True
name = str(name)
request_vars_get = self.request_vars.get
if self['_type'] != 'checkbox':
self['old_value'] = self['value'] or self['_value'] or ''
value = self.request_vars.get(name, '')
value = request_vars_get(name, '')
self['value'] = value
else:
self['old_value'] = self['value'] or False
value = self.request_vars.get(name)
value = request_vars_get(name)
if isinstance(value, (tuple, list)):
self['value'] = self['_value'] in value
else:
@@ -1932,14 +1936,15 @@ class FORM(DIV):
# check formname and formkey
status = True
if self.session:
formkey = self.session.get('_formkey[%s]' % self.formname, None)
request_vars = self.request_vars
if session:
formkey = session.get('_formkey[%s]' % formname, None)
# check if user tampering with form and void CSRF
if formkey != self.request_vars._formkey:
if formkey != request_vars._formkey:
status = False
if self.formname != self.request_vars._formname:
if formname != request_vars._formname:
status = False
if status and self.session:
if status and session:
# check if editing a record that has been modified by the server
if hasattr(self,'record_hash') and self.record_hash != formkey:
status = False
@@ -1983,10 +1988,10 @@ class FORM(DIV):
def hidden_fields(self):
c = []
attr = self.attributes.get('hidden',{})
if 'hidden' in self.attributes:
for (key, value) in self.attributes.get('hidden',{}).items():
c.append(INPUT(_type='hidden', _name=key, _value=value))
c = [INPUT(_type='hidden', _name=key, _value=value)
for (key, value) in attr.iteritems()]
if hasattr(self, 'formkey') and self.formkey:
c.append(INPUT(_type='hidden', _name='_formkey',
_value=self.formkey))
@@ -2055,7 +2060,7 @@ class FORM(DIV):
onsuccess(self)
if next:
if self.vars:
for key,value in self.vars.items():
for key,value in self.vars.iteritems():
next = next.replace('[%s]' % key,
urllib.quote(str(value)))
if not next.startswith('/'):
@@ -2116,11 +2121,11 @@ class FORM(DIV):
inputs = [INPUT(_type='button',
_value=name,
_onclick=FORM.REDIRECT_JS % link) \
for name,link in buttons.items()]
for name,link in buttons.iteritems()]
inputs += [INPUT(_type='hidden',
_name=name,
_value=value)
for name,value in hidden.items()]
for name,value in hidden.iteritems()]
form = FORM(INPUT(_type='submit',_value=text),*inputs)
form.process()
return form
@@ -2268,10 +2273,11 @@ class MENU(DIV):
select = SELECT(**self.attributes)
for item in data:
if len(item) <= 4 or item[4] == True:
if item[2]:
select.append(OPTION(CAT(prefix, item[0]), _value=item[2], _selected=item[1]))
if len(item)>3 and len(item[3]):
self.serialize_mobile(item[3], select, prefix = CAT(prefix, item[0], '/'))
select.append(OPTION(CAT(prefix, item[0]),
_value=item[2], _selected=item[1]))
if len(item)>3 and len(item[3]):
self.serialize_mobile(
item[3], select, prefix = CAT(prefix, item[0], '/'))
select['_onchange'] = 'window.location=this.value'
return select
@@ -2323,7 +2329,7 @@ def test():
>>> print form.accepts({'myvar':'34'}, formname=None)
False
>>> print form.xml()
<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)
True
>>> print form.xml()
@@ -2337,7 +2343,7 @@ def test():
>>> print form.accepts({'myvar':'as df'}, formname=None)
False
>>> 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={}
>>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$')))
>>> 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()]
def to(self, responder):
if self.status in defined_status:
status = '%d %s' % (self.status, defined_status[self.status])
status = self.status
headers = self.headers
if status in defined_status:
status = '%d %s' % (status, defined_status[status])
else:
status = str(self.status) + ' '
if not 'Content-Type' in self.headers:
self.headers['Content-Type'] = 'text/html; charset=UTF-8'
status = str(status) + ' '
if not 'Content-Type' in headers:
headers['Content-Type'] = 'text/html; charset=UTF-8'
body = self.body
if status[:1] == '4':
if not body:
body = status
if isinstance(body, str):
if len(body)<512 and self.headers['Content-Type'].startswith('text/html'):
if len(body)<512 and headers['Content-Type'].startswith('text/html'):
body += '<!-- %s //-->' % ('x'*512) ### trick IE
self.headers['Content-Length'] = len(body)
headers = []
for (k, v) in self.headers.items():
headers['Content-Length'] = len(body)
rheaders = []
for k, v in headers.iteritems():
if isinstance(v, list):
for item in v:
headers.append((k, str(item)))
rheaders += [(k, str(item)) for item in v]
else:
headers.append((k, str(v)))
responder(status, headers)
if hasattr(body, '__iter__') and not isinstance(self.body, str):
rheaders.append((k, str(v)))
responder(status, rheaders)
if isinstance(body,str):
return [body]
elif hasattr(body, '__iter__'):
return body
return [str(body)]
else:
return [str(body)]
@property
def message(self):
'''
"""
compose a message describing this exception
"status defined_status [web2py_error]"
"status defined_status [web2py_error]"
message elements that are not defined are omitted
'''
"""
msg = '%(status)d'
if self.status in defined_status:
msg = '%(status)d %(defined_status)s'
+283 -357
View File
@@ -10,7 +10,7 @@ Plural subsystem is created by Vladyslav Kozlovskyy (Ukraine)
<dbdevelop@gmail.com>
"""
from os import path as ospath, stat as ostat, sep as osep
import os
import re
from utf8 import Utf8
from cgi import escape
@@ -20,7 +20,7 @@ import marshal
import copy_reg
from fileutils import abspath, listdir
import settings
from cfs import getcfs, cfs
from cfs import getcfs
from thread import allocate_lock
from html import XML, xmlescape
from contrib.markmin.markmin2html import render, markmin_escape
@@ -28,10 +28,37 @@ from string import maketrans
__all__ = ['translator', 'findT', 'update_all_languages']
ospath = os.path
ostat = os.stat
osep = os.sep
isdir = os.path.isdir
is_gae = settings.global_settings.web2py_runtime_gae
DEFAULT_LANGUAGE = 'en'
# DEFAULT PLURAL-FORMS RULES:
# language doesn't use plural forms
DEFAULT_NPLURALS = 1
# only one singular/plural form is used
DEFAULT_GET_PLURAL_ID = lambda n: 0
# word is unchangeable
DEFAULT_CONSTRUCTOR_PLURAL_FORM = lambda word, plural_id: word
def safe_eval(text):
if text.strip():
try:
import ast
return ast.literal_eval(text)
except ImportError:
return eval(text,{},{})
return None
# used as default filter in translator.M()
markmin = lambda s: render( regex_param.sub(
lambda m: '{' + markmin_escape(m.group('s')) + '}',
s ), sep='br', autolinks=None, id_prefix='' )
def markmin_aux(m):
return '{%s}' % markmin_escape(m.group('s'))
def markmin(s):
return render(regex_param.sub(markmin_aux,s),
sep='br', autolinks=None, id_prefix='')
NUMBERS = (int,long,float)
@@ -49,28 +76,24 @@ regex_param=re.compile(r'{(?P<s>.+?)}')
regex_language = \
re.compile('^([a-zA-Z]{2})(\-[a-zA-Z]{2})?(\-[a-zA-Z]+)?$')
regex_langfile = re.compile('^[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$')
regex_langinfo = re.compile("^[^'\"]*['\"]([^'\"]*)['\"]\s*:\s*['\"]([^'\"]*)['\"].*$")
regex_backslash = re.compile(r"\\([\\{}%])")
regex_plural = re.compile('%({.+?})')
regex_plural_dict = re.compile('^{(?P<w>[^()[\]][^()[\]]*?)\((?P<n>[^()\[\]]+)\)}$') # %%{word(varname or number)}
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$')
upper_fun = lambda s: unicode(s,'utf-8').upper().encode('utf-8')
title_fun = lambda s: unicode(s,'utf-8').title().encode('utf-8')
cap_fun = lambda s: unicode(s,'utf-8').capitalize().encode('utf-8')
# DEFAULT PLURAL-FORMS RULES:
default_nplurals = 1 # language doesn't use plural forms
default_get_plural_id = lambda n: 0 # only one singular/plural form is used
default_construct_plural_form = lambda word, plural_id: word # word is unchangeable
# UTF8 helper functions
def upper_fun(s):
return unicode(s,'utf-8').upper().encode('utf-8')
def title_fun(s):
return unicode(s,'utf-8').title().encode('utf-8')
def cap_fun(s):
return lambda s: unicode(s,'utf-8').capitalize().encode('utf-8')
ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f')
ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
# cache of translated messages:
# of structure:
# global_language_cache:
# { 'languages/xx.py':
# ( {"def-message": "xx-message",
# ...
@@ -78,35 +101,38 @@ ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
# 'languages/yy.py': ( {dict}, lock_object )
# ...
# }
tcache={}
global_language_cache={}
def get_from_cache(cache, val, fun):
lock=cache[1]
lang_dict, lock = cache
lock.acquire()
try:
result=cache[0].get(val);
result = lang_dict.get(val);
finally:
lock.release()
if result:
return result
lock.acquire()
try:
result=cache[0].setdefault(val, fun())
result = lang_dict.setdefault(val, fun())
finally:
lock.release()
return result
def clear_cache(cache):
lock=cache[1]
def clear_cache(filename):
cache = global_language_cache.setdefault(
filename, ({}, allocate_lock()))
lang_dict, lock = cache
lock.acquire()
try:
cache[0].clear();
lang_dict.clear();
finally:
lock.release()
def lang_sampling(lang_tuple, langlist):
""" search *lang_tuple* in *langlist*
"""
search *lang_tuple* in *langlist*
Args:
lang_tuple (tuple of strings): ('aa'[[,'-bb'],'-cc'])
@@ -137,32 +163,28 @@ def lang_sampling(lang_tuple, langlist):
def read_dict_aux(filename):
fp = portalocker.LockedFile(filename, 'r')
lang_text = fp.read().replace('\r\n', '\n')
fp.close()
# clear cache of processed messages:
clear_cache(tcache.setdefault(filename, ({}, allocate_lock())))
if not lang_text.strip():
return {}
lang_text = portalocker.read_locked(filename).replace('\r\n', '\n')
clear_cache(filename)
try:
return eval(lang_text)
return safe_eval(lang_text) or {}
except Exception, e:
status='Syntax error in %s (%s)' % (filename, e)
status = 'Syntax error in %s (%s)' % (filename, e)
logging.error(status)
return {'__corrupted__':status}
def read_dict(filename):
""" return dictionary with translation messages
"""
return dictionary with translation messages
"""
return getcfs('lang:'+filename, filename,
lambda: read_dict_aux(filename))
def get_lang_info(lang, langdir):
"""retrieve lang information from *langdir*/*lang*.py file.
Read few strings from lang.py file until keys !langname!,
!langcode! or keys greater then '!*' were found
"""
retrieve lang information from *langdir*/*lang*.py file.
Read few strings from lang.py file until keys !langname!,
!langcode! or keys greater then '!*' were found
args:
lang (str): lang-code or 'default'
@@ -173,159 +195,78 @@ def get_lang_info(lang, langdir):
e.g.: ('en', 'English', 1338549043.0)
"""
filename = ospath.join(langdir, lang+'.py')
langcode=langname=''
f = portalocker.LockedFile(filename, 'r')
try:
while not (langcode and langname):
line = f.readline()
if not line:
break
match=regex_langinfo.match(line)
if match:
k = match.group(1)
if k == '!langname!':
langname = match.group(2)
elif k == '!langcode!':
langcode = match.group(2)
elif k[0:1] > '!':
break
finally:
f.close()
if not langcode:
langcode = lang if lang != 'default' else 'en'
return langcode, langname or langcode, ostat(filename).st_mtime
d = read_dict(filename)
langcode = d.get('!langcode!',DEFAULT_LANGUAGE)
langname = d.get('!langname!',langcode)
return (langcode, langname or langcode, ostat(filename).st_mtime)
def read_possible_languages_aux(langdir):
def read_possible_languages(appdir):
langs = {}
# scan languages directory for langfiles:
for langfile in [f for f in
listdir(langdir, regex_langfile) +
listdir(langdir, '^default\.py$')
if osep not in f]:
lang=langfile[:-3]
langs[lang]=get_lang_info(lang, langdir)
if 'default' not in langs:
langdir = ospath.join(appdir,'languages')
for filename in os.listdir(langdir):
if regex_langfile.match(filename) or filename=='default.py':
lang = filename[:-3]
langs[lang] = get_lang_info(lang, langdir)
if not 'en' in langs:
# if default.py is not found, add default value:
langs['default'] = ('en', 'English', 0)
deflang=langs['default']
if deflang[0] not in langs:
# create language from default.py:
langs[deflang[0]] = (deflang[0], deflang[1], 0)
langs['en'] = ('en', 'English', 0)
return langs
def read_possible_languages(path):
lang_path = ospath.join(path, 'languages')
return getcfs('langs:'+lang_path, lang_path,
lambda: read_possible_languages_aux(lang_path))
def read_plural_rules_aux(filename):
"""retrieve plural rules from rules/*plural_rules-lang*.py file.
def read_global_plural_rules(filename):
"""
retrieve plural rules from rules/*plural_rules-lang*.py file.
args:
filename (str): plural_rules filename
returns:
tuple(nplurals, get_plural_id, construct_plural_form)
e.g.: (3, <function>, <function>)
(nplurals, get_plural_id, construct_plural_form, status)
e.g.: (3, <function>, <function>, ok)
"""
f = portalocker.LockedFile(filename, 'r')
plural_py=f.read().replace('\r\n','\n')
f.close()
env = {}
data = portalocker.read_locked(filename)
try:
exec(plural_py)
nplurals=locals().get('nplurals', default_nplurals)
get_plural_id=locals().get('get_plural_id', default_get_plural_id)
construct_plural_form=locals().get('construct_plural_form',
default_construct_plural_form)
exec(data) in env
status='ok'
except Exception, e:
nplurals=default_nplurals
get_plural_id=default_get_plural_id
construct_plural_form=default_construct_plural_form
status='Syntax error in %s (%s)' % (filename, e)
logging.error(status)
nplurals = env.get('nplurals', DEFAULT_NPLURALS)
get_plural_id = env.get('get_plural_id', DEFAULT_GET_PLURAL_ID)
construct_plural_form = env.get('construct_plural_form',
DEFAULT_CONSTRUCTOR_PLURAL_FORM)
return (nplurals, get_plural_id, construct_plural_form, status)
def read_plural_rules(lang):
filename = abspath('gluon','contrib','rules', 'plural_rules-%s.py' % lang)
return getcfs('plural_rules-'+lang, filename,
lambda: read_plural_rules_aux(filename))
pcache={}
def read_possible_plurals():
""" create list of all possible plural rules files
result is cached to increase speed
"""
global pcache
create list of all possible plural rules files
result is cached to increase speed
"""
pdir = abspath('gluon','contrib','rules')
plurals = {}
# scan rules directory for plural_rules-*.py files:
for pname in [f for f in listdir(pdir, regex_plural_rules)
if osep not in f]:
lang=pname[13:-3]
fname=ospath.join(pdir, pname)
mtime=ostat(fname).st_mtime
if lang in pcache and pcache[lang][2] == mtime:
# if plural_file's mtime wasn't changed - use previous value:
plurals[lang]=pcache[lang]
else:
# otherwise, reread plural_rules-file:
if 'plural_rules-'+lang in cfs:
n,f1,f2,status=read_plural_rules(lang)
else:
n,f1,f2,status=read_plural_rules_aux(fname)
plurals[lang]=(n, pname, mtime, status)
pcache=plurals
return pcache
def get_plural_rules(languages):
"""get plural-forms rules for language *lang*
if rules not found - default rules will be return and lang=='unknown'
args:
lang (str): the languages, for one of which the plural-forms is return
returns:
tuples(lang, plural_rules-filename, nplurals,
get_plural_id(), construct_plural_form(), status)
"""
if isinstance(languages, str):
languages = [languages]
all_plurals=read_possible_plurals()
for lang in languages:
match_language = regex_language.match(lang.strip().lower())
if match_language:
match_language = tuple(part
for part in match_language.groups()
if part)
lang = lang_sampling(match_language, all_plurals.keys())
if lang:
( nplurals,
get_plural_id,
construct_plural_form,
status
) = read_plural_rules(lang)
return (lang, all_plurals[lang][1], nplurals,
get_plural_id,
construct_plural_form,
status)
return ('unknown', None, default_nplurals,
default_get_plural_id,
default_construct_plural_form,
'ok')
for pname in os.listdir(pdir):
if not isdir(pname) and regex_plural_rules.match(pname):
lang = pname[13:-3]
fname = ospath.join(pdir, pname)
n, f1, f2, status = read_global_plural_rules(fname)
if status == 'ok':
plurals[lang] = (lang, n, f1, f2, pname)
plurals['default'] = ('default',
DEFAULT_NPLURALS,
DEFAULT_GET_PLURAL_ID,
DEFAULT_CONSTRUCTOR_PLURAL_FORM,
None)
return plurals
PLURAL_RULES = read_possible_plurals()
def read_plural_dict_aux(filename):
fp = portalocker.LockedFile(filename, 'r')
lang_text = fp.read().replace('\r\n', '\n')
fp.close()
if not lang_text.strip():
return {}
lang_text = portalocker.read_locked(filename).replace('\r\n', '\n')
try:
return eval(lang_text)
return eval(lang_text) or {}
except Exception, e:
status='Syntax error in %s (%s)' % (filename, e)
logging.error(status)
@@ -335,7 +276,6 @@ def read_plural_dict(filename):
return getcfs('plurals:'+filename, filename,
lambda: read_plural_dict_aux(filename))
def write_plural_dict(filename, contents):
if '__corrupted__' in contents:
return
@@ -466,13 +406,10 @@ class lazyT(object):
return lazyT(self.m, symbols, self.T, self.f, self.t, self.M)
class translator(object):
"""
this class is instantiated by gluon.compileapp.build_environment
as the T object
::
T.force(None) # turns off translation
T.force('fr, it') # forces web2py to translate using fr.py or it.py
@@ -480,27 +417,21 @@ class translator(object):
notice 1: there is no need to force since, by default, T uses
http_accept_language to determine a translation file.
notice 2: en and en-en are considered different languages!
notice 3: if language xx-yy is not found force() probes other similar
languages using such algorithm: xx-yy.py -> xx.py -> xx-yy*.py -> xx*.py
notice 2:
en and en-en are considered different languages!
notice 3:
if language xx-yy is not found force() probes other similar
languages using such algorithm:
xx-yy.py -> xx.py -> xx-yy*.py -> xx*.py
"""
def __init__(self, request):
global tcache
self.request = request
self.folder = request.folder
dfile = ospath.join(self.folder,'languages','default.py')
if ospath.exists(dfile):
self.default_language_file = dfile
self.default_t = read_dict(dfile)
else: # languages/default.py is not found
self.default_language_file = ospath.join(self.folder, 'languages','')
self.default_t = {}
self.cache = tcache.setdefault(self.default_language_file, ({}, allocate_lock()))
self.current_languages = [self.get_possible_languages_info('default')[0]]
self.langpath = ospath.join(self.folder,'languages')
self.filenames = set(os.listdir(self.langpath))
self.http_accept_language = request.env.http_accept_language
# self.cache # filled in self.force()
# self.accepted_language = None # filled in self.force()
# self.language_file = None # filled in self.force()
# self.plural_language = None # filled in self.force()
@@ -511,12 +442,89 @@ class translator(object):
# self.plural_file = None # filled in self.force()
# self.plural_dict = None # filled in self.force()
# self.plural_status = None # filled in self.force()
self.requested_languages = self.force(self.http_accept_language)
self.requested_languages = \
self.force(self.http_accept_language)
self.lazy = True
self.otherTs = {}
self.filter = markmin
self.ftag = 'markmin'
def get_possible_languages(self):
return [lang[:-3] for lang in self.filenames \
if regex_langfile.match(lang)]
def set_current_languages(self, *languages):
"""
set current AKA "default" languages
setting one of this languages makes force() function
turn translation off to use default language
"""
if len(languages) == 1 and isinstance(
languages[0], (tuple, list)):
languages = languages[0]
self.current_languages = languages
self.force(self.http_accept_language)
def set_plural(self, language):
"""
initialize plural forms subsystem
invoked from self.force()
"""
lang = language[:2]
(self.plural_language,
self.nplurals,
self.get_plural_id,
self.construct_plural_form,
self.plural_filename
) = PLURAL_RULES.get(language,PLURAL_RULES['default'])
for lang in (language, language[:5], language[:2]):
filename = 'plural-%s.py' % lang
if filename in self.filenames:
self.plural_file = ospath.join(self.langpath,filename)
self.plural_dict = read_plural_dict(self.plural_file)
break
else:
self.plural_file = None
self.plural_dict = {}
def plural(self, word, n):
"""
get plural form of word for number *n*
NOTE: *word* MUST be defined in current language
(T.accepted_language)
invoked from T()/M() in %%{} tag
args:
word (str): word in singular
n (numeric): number plural form created for
returns:
(str): word in appropriate singular/plural form
"""
nplurals = self.nplurals
if int(n)==1:
return word
elif word:
id = min(int(n)-1,1) # self.get_plural_id(abs(int(n)))
# id = 0 first plural form
# id = 1 second plural form
# etc.
forms = self.plural_dict.get(word, [])
if len(forms)>=id:
# have this plural form
return forms[id-1]
else:
# guessing this plural form
forms += ['']*(nplurals-len(forms)-1)
form = self.construct_plural_form(word, id)
forms[id-1] = form
self.plural_dict[word] = forms
if self.plural_file and not is_gae:
write_plural_dict(self.plural_file,
self.plural_dict)
return form
def get_possible_languages_info(self, lang=None):
"""
return info for selected language or dictionary with all
@@ -533,90 +541,12 @@ class translator(object):
langname(from !langname! key),
langfile_mtime ) }
"""
if lang:
return read_possible_languages(self.folder).get(lang)
return read_possible_languages(self.folder)
def get_possible_languages(self):
""" get list of all possible languages for current applications """
return sorted( set(lang for lang in
read_possible_languages(self.folder).iterkeys()
if lang != 'default')
| set(self.current_languages))
def set_current_languages(self, *languages):
"""
set current AKA "default" languages
setting one of this languages makes force() function
turn translation off to use default language
"""
if len(languages) == 1 and isinstance(languages[0], (tuple, list)):
languages = languages[0]
self.current_languages = languages
self.force(self.http_accept_language)
def set_plural(self, languages):
""" initialize plural forms subsystem
invoked from self.force()
"""
( self.plural_language,
self.plural_rules_file,
self.nplurals,
self.get_plural_id,
self.construct_plural_form,
self.plural_status
) = get_plural_rules(languages)
if self.plural_language == 'unknown':
self.plural_file = None
self.plural_dict = {}
else:
self.plural_file = ospath.join(self.folder,
'languages',
'plural-%s.py' % self.plural_language)
if ospath.exists(self.plural_file):
self.plural_dict = read_plural_dict(self.plural_file)
else:
self.plural_dict = {}
def plural(self, word, n):
""" get plural form of word for number *n*
NOTE: *word" MUST be defined in current language
(T.accepted_language)
invoked from T()/M() in %%{} tag
args:
word (str): word in singular
n (numeric): number plural form created for
returns:
(str): word in appropriate singular/plural form
"""
nplurals = self.nplurals
if word:
id = self.get_plural_id(abs(int(n)))
if id > 0:
forms = self.plural_dict.get(word, [])
if forms:
try:
form = forms[id-1]
except:
form = None
if form: return form
form = self.construct_plural_form(word, id)
if len(forms) < nplurals-1:
forms.extend('' for i in xrange(nplurals-len(forms)-1))
forms[id-1] = form
self.plural_dict[word] = forms
if (self.plural_file and
not settings.global_settings.web2py_runtime_gae):
write_plural_dict(self.plural_file, self.plural_dict)
return form
return word
info = read_possible_languages(self.folder)
if lang: info = info.get(lang)
return info
def force(self, *languages):
"""
select language(s) for translation
if a list of languages is passed as a parameter,
@@ -627,49 +557,41 @@ class translator(object):
default language will be selected if none
of them matches possible_languages.
"""
global tcache
language = ''
if not languages or languages[0] is None:
if isinstance(languages,str):
languages = regex_language.findall(languages.lower())
elif not languages or languages[0] is None:
languages = []
if len(languages) == 1 and isinstance(languages[0], (str, unicode)):
languages = languages[0]
if languages:
if isinstance(languages, (str, unicode)):
parts = languages.split(';')
languages = []
for al in parts:
languages.extend(al.split(','))
possible_languages = self.get_possible_languages()
for lang in languages:
match_language = regex_language.match(lang.strip().lower())
if match_language:
match_language = tuple(part
for part in match_language.groups()
if part)
language = lang_sampling(match_language,
self.current_languages)
if language:
break
language = lang_sampling(match_language, possible_languages)
if language:
self.language_file = ospath.join(self.folder,
'languages',
language + '.py')
if ospath.exists(self.language_file):
self.t = read_dict(self.language_file)
self.cache = tcache.setdefault(self.language_file,
({},allocate_lock()))
self.set_plural(language)
self.accepted_language = language
return languages
self.accepted_language = language or self.current_languages[0]
self.language_file = self.default_language_file
self.cache = tcache[self.language_file]
self.t = self.default_t
self.set_plural(language or self.current_languages)
for lang in languages:
if lang+'.py' in self.filenames:
language = lang
langfile = language+'.py'
break
elif len(lang)>5 and lang[:5]+'.py' in self.filenames:
language = lang[:5]
langfile = language+'.py'
break
elif len(lang)>2 and lang[:2]+'.py' in self.filenames:
language = lang[:2]
langfile = language+'.py'
break
else:
if 'default.py' in self.filenames:
language = DEFAULT_LANGUAGE
langfile = 'default.py'
else:
language = DEFAULT_LANGUAGE
langfile = None
self.accepted_language = language
if langfile:
self.language_file = ospath.join(self.langpath,langfile)
self.t = read_dict(self.language_file)
else:
self.language_file = None
self.t = {}
self.cache = global_language_cache.setdefault(
self.language_file,({},allocate_lock()))
self.set_plural(language)
return languages
def __call__(self, message, symbols={}, language=None, lazy=None):
@@ -700,26 +622,30 @@ class translator(object):
prefix = '@'+(ftag or 'userdef')+'\x01'
else:
prefix = '@'+self.ftag+'\x01'
message = get_from_cache(self.cache, prefix+message,
lambda: get_tr(message, prefix, filter))
message = get_from_cache(
self.cache, prefix+message,
lambda: get_tr(message, prefix, filter))
if symbols or symbols == 0 or symbols == "":
if isinstance(symbols, dict):
symbols.update( (key, xmlescape(value).translate(ttab_in))
for key, value in symbols.iteritems()
if not isinstance(value, NUMBERS) )
symbols.update(
(key, xmlescape(value).translate(ttab_in))
for key, value in symbols.iteritems()
if not isinstance(value, NUMBERS) )
else:
if not isinstance(symbols, tuple):
symbols = (symbols,)
symbols = tuple(value if isinstance(value, NUMBERS)
else xmlescape(value).translate(ttab_in)
for value in symbols)
symbols = tuple(
value if isinstance(value, NUMBERS)
else xmlescape(value).translate(ttab_in)
for value in symbols)
message = self.params_substitution(message, symbols)
return XML(message.translate(ttab_out))
def M(self, message, symbols={}, language=None, lazy=None, filter=None, ftag=None):
""" get cached translated markmin-message with inserted parametes
if lazy==True lazyT object is returned
def M(self, message, symbols={}, language=None,
lazy=None, filter=None, ftag=None):
"""
get cached translated markmin-message with inserted parametes
if lazy==True lazyT object is returned
"""
if lazy is None:
lazy = self.lazy
@@ -751,18 +677,20 @@ class translator(object):
"""
key = prefix+message
mt = self.t.get(key, None)
if mt is not None:
return mt
if not message.startswith('##') and not '\n' in message:
tokens = message.rsplit('##', 1)
else:
# this allows markmin syntax in translations
tokens = [message]
self.t[key] = mt = self.default_t.get(key, tokens[0])
if (self.language_file != self.default_language_file and
not settings.global_settings.web2py_runtime_gae):
write_dict(self.language_file, self.t)
return regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), mt)
if mt is None:
# we did not find a translation
if message.find('##')>0 and not '\n' in message:
# remove comments
message = message.rsplit('##', 1)[0]
# guess translation same as original
self.t[key] = mt = message
# update language file for later translation
if self.language_file and not is_gae:
write_dict(self.language_file, self.t)
# fix backslash escaping
mt = regex_backslash.sub(
lambda m: m.group(1).translate(ttab_in), mt)
return mt
def params_substitution(self, message, symbols):
"""
@@ -866,19 +794,21 @@ class translator(object):
"""
get cached translated message with inserted parameters(symbols)
"""
message = get_from_cache(self.cache, message, lambda: self.get_t(message))
message = get_from_cache(self.cache, message,
lambda: self.get_t(message))
if symbols or symbols == 0 or symbols == "":
if isinstance(symbols, dict):
symbols.update( (key, str(value).translate(ttab_in))
for key, value in symbols.iteritems()
if not isinstance(value, NUMBERS) )
symbols.update(
(key, str(value).translate(ttab_in))
for key, value in symbols.iteritems()
if not isinstance(value, NUMBERS) )
else:
if not isinstance(symbols, tuple):
symbols = (symbols,)
symbols = tuple(value if isinstance(value, NUMBERS)
else str(value).translate(ttab_in)
for value in symbols)
symbols = tuple(
value if isinstance(value, NUMBERS)
else str(value).translate(ttab_in)
for value in symbols)
message = self.params_substitution(message, symbols)
return message.translate(ttab_out)
@@ -892,26 +822,25 @@ def findT(path, language='en'):
cp = ospath.join(path, 'controllers')
vp = ospath.join(path, 'views')
mop = ospath.join(path, 'modules')
for file in listdir(mp, '^.+\.py$', 0) + listdir(cp, '^.+\.py$', 0)\
+ listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0):
fp = portalocker.LockedFile(file, 'r')
data = fp.read()
fp.close()
for filename in \
listdir(mp, '^.+\.py$', 0)+listdir(cp, '^.+\.py$', 0)\
+listdir(vp, '^.+\.html$', 0)+listdir(mop, '^.+\.py$', 0):
data = portalocker.read_locked(filename)
items = regex_translate.findall(data)
for item in items:
try:
message = eval(item)
if not message.startswith('#') and not '\n' in message:
tokens = message.rsplit('##', 1)
else:
# this allows markmin syntax in translations
tokens = [message]
if len(tokens) == 2:
message = tokens[0].strip() + '##' + tokens[1].strip()
if message and not message in sentences:
sentences[message] = message
message = safe_eval(item)
except:
pass
continue # silently ignore inproperly formatted strings
if not message.startswith('#') and not '\n' in message:
tokens = message.rsplit('##', 1)
else:
# this allows markmin syntax in translations
tokens = [message]
if len(tokens) == 2:
message = tokens[0].strip()+'##'+tokens[1].strip()
if message and not message in sentences:
sentences[message] = message
if not '!langcode!' in sentences:
sentences['!langcode!'] = (
'en' if language in ('default', 'en') else language)
@@ -937,6 +866,3 @@ def update_all_languages(application_path):
if __name__ == '__main__':
import doctest
doctest.testmod()
+104 -96
View File
@@ -21,7 +21,6 @@ import re
import copy
import sys
import time
import thread
import datetime
import signal
import socket
@@ -29,6 +28,7 @@ import tempfile
import random
import string
import urllib2
from thread import allocate_lock
from fileutils import abspath, write_file, parse_version
from settings import global_settings
@@ -69,8 +69,11 @@ import logging.config
import gluon.messageboxhandler
logging.gluon = gluon
exists = os.path.exists
pjoin = os.path.join
logpath = abspath("logging.conf")
if os.path.exists(logpath):
if exists(logpath):
logging.config.fileConfig(abspath("logging.conf"))
else:
logging.basicConfig()
@@ -87,10 +90,10 @@ from dal import BaseAdapter
from settings import global_settings
from validators import CRYPT
from cache import Cache
from html import URL as Url, xmlescape
from html import URL, xmlescape
from utils import is_valid_ip_address
from rewrite import load, url_in, thread as rwthread, try_rewrite_on_error
import newcron
import rewrite
__all__ = ['wsgibase', 'save_password', 'appfactory', 'HttpServer']
@@ -103,7 +106,7 @@ requests = 0 # gc timer
regex_client = re.compile('[\w\-:]+(\.[\w\-]+)*\.?') # ## to account for IPV6
try:
version_info = open(os.path.join(global_settings.gluon_parent, 'VERSION'), 'r')
version_info = open(pjoin(global_settings.gluon_parent, 'VERSION'), 'r')
raw_version_string = version_info.read().strip()
version_info.close()
global_settings.web2py_version = parse_version(raw_version_string)
@@ -118,7 +121,7 @@ except:
if not global_settings.web2py_runtime_gae:
logger.warn('unable to import Rocket')
rewrite.load()
load()
def get_client(env):
"""
@@ -129,11 +132,16 @@ def get_client(env):
"""
g = regex_client.search(env.get('http_x_forwarded_for', ''))
if g:
return g.group()
g = regex_client.search(env.get('remote_addr', ''))
if g:
return g.group()
return '127.0.0.1'
client = g.group()
else:
g = regex_client.search(env.get('remote_addr', ''))
if g:
client = g.group()
else:
client = '127.0.0.1'
if not is_valid_ip_address(client):
raise HTTP(400,"Bad Request (request.client=%s)" % client)
return client
def copystream_progress(request, chunk_size= 10**5):
"""
@@ -253,7 +261,8 @@ def middleware_aux(request, response, *middleware_apps):
for item in middleware_apps:
app=item(app)
def caller(app):
return app(request.wsgi.environ,request.wsgi.start_response)
wsgi = request.wsgi
return app(wsgi.environ, wsgi.start_response)
return lambda caller=caller, app=app: caller(app)
return middleware
@@ -279,14 +288,14 @@ def parse_get_post_vars(request, environ):
# parse POST variables on POST, PUT, BOTH only in post_vars
try:
request.body = copystream_progress(request) ### stores request body
request.body = body = copystream_progress(request)
except IOError:
raise HTTP(400,"Bad Request - HTTP body is incomplete")
if (request.body and request.env.request_method in ('POST', 'PUT', 'BOTH')):
dpost = cgi.FieldStorage(fp=request.body,environ=environ,keep_blank_values=1)
if (body and request.env.request_method in ('POST', 'PUT', 'BOTH')):
dpost = cgi.FieldStorage(fp=body,environ=environ,keep_blank_values=1)
# The same detection used by FieldStorage to detect multipart POSTs
is_multipart = dpost.type[:10] == 'multipart/'
request.body.seek(0)
body.seek(0)
isle25 = sys.version_info[1] <= 5
def listify(a):
@@ -357,9 +366,10 @@ def wsgibase(environ, responder):
request = Request()
response = Response()
session = Session()
request.env.web2py_path = global_settings.applications_parent
request.env.web2py_version = web2py_version
request.env.update(global_settings)
env = request.env
env.web2py_path = global_settings.applications_parent
env.web2py_version = web2py_version
env.update(global_settings)
static_file = False
try:
try:
@@ -373,79 +383,80 @@ def wsgibase(environ, responder):
# serve file if static
# ##################################################
if not environ.get('PATH_INFO',None) and \
environ.get('REQUEST_URI',None):
# for fcgi, get path_info and query_string from request_uri
eget = environ.get
if not eget('PATH_INFO',None) and eget('REQUEST_URI',None):
# for fcgi, get path_info and
# query_string from request_uri
items = environ['REQUEST_URI'].split('?')
environ['PATH_INFO'] = items[0]
if len(items) > 1:
environ['QUERY_STRING'] = items[1]
else:
environ['QUERY_STRING'] = ''
if not environ.get('HTTP_HOST',None):
environ['HTTP_HOST'] = '%s:%s' % (environ.get('SERVER_NAME'),
environ.get('SERVER_PORT'))
if not eget('HTTP_HOST',None):
environ['HTTP_HOST'] = \
eget('SERVER_NAME')+':'+eget('SERVER_PORT')
(static_file, environ) = url_in(request, environ)
(static_file, environ) = rewrite.url_in(request, environ)
if static_file:
if environ.get('QUERY_STRING', '')[:10] == 'attachment':
response.headers['Content-Disposition'] = 'attachment'
if environ.get('QUERY_STRING','').startswith(
'attachment'):
response.headers['Content-Disposition'] \
= 'attachment'
response.stream(static_file, request=request)
# ##################################################
# fill in request items
# ##################################################
http_host = request.env.http_host.split(':',1)[0]
local_hosts = [http_host,'::1','127.0.0.1','::ffff:127.0.0.1']
app = request.application ## must go after url_in!
http_host = env.http_host.split(':',1)[0]
local_hosts = [http_host,'::1','127.0.0.1',
'::ffff:127.0.0.1']
if not global_settings.web2py_runtime_gae:
local_hosts.append(socket.gethostname())
try: local_hosts.append(socket.gethostbyname(http_host))
except socket.gaierror: pass
request.client = get_client(request.env)
if not is_valid_ip_address(request.client):
raise HTTP(400,"Bad Request (request.client=%s)" % \
request.client)
request.folder = abspath('applications',
request.application) + os.sep
x_req_with = str(request.env.http_x_requested_with).lower()
request.ajax = x_req_with == 'xmlhttprequest'
request.cid = request.env.http_web2py_component_element
request.is_local = request.env.remote_addr in local_hosts
request.is_https = request.env.wsgi_url_scheme \
in ['https', 'HTTPS'] or request.env.https == 'on'
# ##################################################
# compute a request.uuid to be used for tickets and toolbar
# ##################################################
response.uuid = request.compute_uuid()
try:
local_hosts.append(
socket.gethostbyname(http_host))
except socket.gaierror:
pass
client = get_client(env)
x_req_with = str(env.http_x_requested_with).lower()
request.update(dict(
client = client,
folder = abspath('applications',app) + os.sep,
ajax = x_req_with == 'xmlhttprequest',
cid = env.http_web2py_component_element,
is_local = env.remote_addr in local_hosts,
is_https = env.wsgi_url_scheme \
in ['https', 'HTTPS'] or env.https=='on'))
request.uuid = request.compute_uuid() # requires client
# ##################################################
# access the requested application
# ##################################################
if not os.path.exists(request.folder):
if request.application == \
rewrite.thread.routes.default_application \
and request.application != 'welcome':
request.application = 'welcome'
redirect(Url(r=request))
elif rewrite.thread.routes.error_handler:
_handler = rewrite.thread.routes.error_handler
redirect(Url(_handler['application'],
if not exists(request.folder):
if app == rwthread.routes.default_application \
and app != 'welcome':
redirect(URL('welcome','default','index'))
elif rwthread.routes.error_handler:
_handler = rwthread.routes.error_handler
redirect(URL(_handler['application'],
_handler['controller'],
_handler['function'],
args=request.application))
args=app))
else:
raise HTTP(404, rewrite.thread.routes.error_message \
raise HTTP(404, rwthread.routes.error_message \
% 'invalid request',
web2py_error='invalid application')
elif not request.is_local and \
os.path.exists(os.path.join(request.folder,'DISABLED')):
exists(pjoin(request.folder,'DISABLED')):
raise HTTP(503, "<html><body><h1>Temporarily down for maintenance</h1></body></html>")
request.url = Url(r=request,
request.url = URL(r=request,
args=request.args,
extension=request.raw_extension)
@@ -492,22 +503,23 @@ def wsgibase(environ, responder):
# ##################################################
# set no-cache headers
# ##################################################
response.headers['Content-Type'] = \
headers = response.headers
headers['Content-Type'] = \
contenttype('.'+request.extension)
response.headers['Cache-Control'] = \
headers['Cache-Control'] = \
'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
response.headers['Expires'] = \
headers['Expires'] = \
time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime())
response.headers['Pragma'] = 'no-cache'
headers['Pragma'] = 'no-cache'
# ##################################################
# run controller
# ##################################################
if global_settings.debugging and request.application != "admin":
if global_settings.debugging and app != "admin":
import gluon.debug
# activate the debugger and wait to reach application code
# activate the debugger
gluon.debug.dbg.do_debug(mainpyfile=request.folder)
serve_controller(request, response, session)
@@ -549,22 +561,24 @@ def wsgibase(environ, responder):
# ##################################################
if request.cid:
if response.flash and not 'web2py-component-flash' \
in http_response.headers:
http_response.headers['web2py-component-flash'] = \
rheaders
if response.flash and \
not 'web2py-component-flash' in rheaders:
rheaders['web2py-component-flash'] = \
urllib2.quote(xmlescape(response.flash)\
.replace('\n',''))
if response.js and not 'web2py-component-command' \
in http_response.headers:
http_response.headers['web2py-component-command'] = \
if response.js and \
not 'web2py-component-command' in readers:
readers['web2py-component-command'] = \
response.js.replace('\n','')
rcookies = response.cookies
if session._forget and \
response.session_id_name in response.cookies:
del response.cookies[response.session_id_name]
del rcookies[response.session_id_name]
elif session._secure:
response.cookies[response.session_id_name]['secure'] = True
rcookies[response.session_id_name]['secure'] = True
http_response.cookies2headers(response.cookies)
http_response.cookies2headers(rcookies)
ticket=None
except RestrictedError, e:
@@ -583,7 +597,7 @@ def wsgibase(environ, responder):
BaseAdapter.close_all_instances('rollback')
http_response = \
HTTP(500, rewrite.thread.routes.error_message_ticket % \
HTTP(500, rwthread.routes.error_message_ticket % \
dict(ticket=ticket),
web2py_error='ticket %s' % ticket)
@@ -606,7 +620,7 @@ def wsgibase(environ, responder):
e = RestrictedError('Framework', '', '', locals())
ticket = e.log(request) or 'unrecoverable'
http_response = \
HTTP(500, rewrite.thread.routes.error_message_ticket \
HTTP(500, rwthread.routes.error_message_ticket \
% dict(ticket=ticket),
web2py_error='ticket %s' % ticket)
@@ -616,7 +630,7 @@ def wsgibase(environ, responder):
response.session_file.close()
session._unlock(response)
http_response, new_environ = rewrite.try_rewrite_on_error(
http_response, new_environ = try_rewrite_on_error(
http_response, request, environ, ticket)
if not http_response:
return wsgibase(new_environ,responder)
@@ -641,7 +655,7 @@ def save_password(password, port):
print '*********************************************************'
elif password == '<recycle>':
# reuse the current password if any
if os.path.exists(password_file):
if exists(password_file):
return
else:
password = ''
@@ -672,9 +686,9 @@ def appfactory(wsgiapp=wsgibase,
[, profilerfilename='profiler.log']]])
"""
if profilerfilename and os.path.exists(profilerfilename):
if profilerfilename and exists(profilerfilename):
os.unlink(profilerfilename)
locker = thread.allocate_lock()
locker = allocate_lock()
def app_with_logging(environ, responder):
"""
@@ -785,7 +799,7 @@ class HttpServer(object):
os.chdir(path)
[add_path_first(p) for p in (path, abspath('site-packages'), "")]
custom_import_install(web2py_path)
if os.path.exists("logging.conf"):
if exists("logging.conf"):
logging.config.fileConfig("logging.conf")
save_password(password, port)
@@ -800,9 +814,9 @@ class HttpServer(object):
logger.info('SSL is off')
elif not rocket.ssl:
logger.warning('Python "ssl" module unavailable. SSL is OFF')
elif not os.path.exists(ssl_certificate):
elif not exists(ssl_certificate):
logger.warning('unable to open SSL certificate. SSL is OFF')
elif not os.path.exists(ssl_private_key):
elif not exists(ssl_private_key):
logger.warning('unable to open SSL private key. SSL is OFF')
else:
sock_list.extend([ssl_private_key, ssl_certificate])
@@ -848,9 +862,3 @@ class HttpServer(object):
except:
pass
+13 -1
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# portalocker.py - Cross-platform (posix/nt) API for flock-style file locking.
# portalocker.py
# Cross-platform (posix/nt) API for flock-style file locking.
# Requires python 1.5.2 or better.
"""
@@ -141,6 +142,17 @@ class LockedFile(object):
def __del__(self):
self.close()
def read_locked(filename):
fp = LockedFile(filename, 'r')
data = fp.read()
fp.close()
return data
def write_locked(filename,data):
fp = LockedFile(filename, 'w')
data = fp.write(data)
fp.close()
if __name__=='__main__':
f = LockedFile('test.txt',mode='wb')
f.write('test ok')
+13 -10
View File
@@ -88,16 +88,19 @@ class TicketStorage(Storage):
ticket_id,
):
if not self.db:
ef = self._error_file(request, ticket_id, 'rb', app)
try:
ef = self._error_file(request, ticket_id, 'rb', app)
except IOError:
return {}
try:
return cPickle.load(ef)
finally:
ef.close()
table = self._get_table(self.db, self.tablename, app)
rows = self.db(table.ticket_id == ticket_id).select()
if rows:
return cPickle.loads(rows[0].ticket_data)
return None
else:
table = self._get_table(self.db, self.tablename, app)
rows = self.db(table.ticket_id == ticket_id).select()
return cPickle.loads(rows[0].ticket_data) if rows else {}
class RestrictedError(Exception):
@@ -164,10 +167,10 @@ class RestrictedError(Exception):
ticket_storage = TicketStorage(db=request.tickets_db)
d = ticket_storage.load(request, app, ticket_id)
self.layer = d['layer']
self.code = d['code']
self.output = d['output']
self.traceback = d['traceback']
self.layer = d.get('layer')
self.code = d.get('code')
self.output = d.get('output')
self.traceback = d.get('traceback')
self.snapshot = d.get('snapshot')
def __str__(self):
+68 -49
View File
@@ -27,9 +27,17 @@ from http import HTTP
from fileutils import abspath, read_file
from settings import global_settings
logger = logging.getLogger('web2py.rewrite')
isdir = os.path.isdir
isfile = os.path.isfile
exists = os.path.exists
pjoin = os.path.join
thread = threading.local() # thread-local storage for routing parameters
logger = logging.getLogger('web2py.rewrite')
thread = threading.local() # thread-local storage for routing params
regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*')
regex_anything = re.compile(r'(?<!\\)\$anything')
regex_redirect = re.compile(r'(\d+)->(.*)')
def _router_default():
"return new copy of default base router"
@@ -75,7 +83,7 @@ def _params_default(app=None):
params_apps = dict()
params = _params_default(app=None) # regex rewrite parameters
thread.routes = params # default to base regex rewrite parameters
thread.routes = params # default to base regex rewrite parameters
routers = None
def log_rewrite(string):
@@ -97,13 +105,18 @@ def log_rewrite(string):
else:
logger.debug(string)
ROUTER_KEYS = set(('default_application', 'applications', 'default_controller', 'controllers',
'default_function', 'functions', 'default_language', 'languages',
'domain', 'domains', 'root_static', 'path_prefix',
'exclusive_domain', 'map_hyphen', 'map_static',
'acfe_match', 'file_match', 'args_match'))
ROUTER_KEYS = set(
('default_application', 'applications',
'default_controller', 'controllers',
'default_function', 'functions',
'default_language', 'languages',
'domain', 'domains', 'root_static', 'path_prefix',
'exclusive_domain', 'map_hyphen', 'map_static',
'acfe_match', 'file_match', 'args_match'))
ROUTER_BASE_KEYS = set(('applications', 'default_application', 'domains', 'path_prefix'))
ROUTER_BASE_KEYS = set(
('applications', 'default_application',
'domains', 'path_prefix'))
# The external interface to rewrite consists of:
#
@@ -223,9 +236,7 @@ def try_redirect_on_error(http_object, request, ticket=None):
(redir,status,ticket,
urllib.quote_plus(request.env.request_uri),
request.url)
return HTTP(303,
'You are being redirected <a href="%s">here</a>' % url,
Location=url)
return HTTP(303,'You are being redirected <a href="%s">here</a>' % url,Location=url)
return http_object
@@ -258,7 +269,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
path = abspath(routes)
else:
path = abspath('applications', app, routes)
if not os.path.exists(path):
if not exists(path):
return
data = read_file(path).replace('\r\n','\n')
@@ -309,9 +320,11 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
# parse the app-specific routes.py if present
#
all_apps = []
for appname in [app for app in os.listdir(abspath('applications')) if not app.startswith('.')]:
if os.path.isdir(abspath('applications', appname)) and \
os.path.isdir(abspath('applications', appname, 'controllers')):
apppath = abspath('applications')
for appname in os.listdir(apppath):
if not appname.startswith('.') and \
isdir(abspath(apppath,appname)) and \
isdir(abspath(apppath,appname,'controllers')):
all_apps.append(appname)
if routers:
router = Storage(routers.BASE) # new copy
@@ -321,7 +334,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
raise SyntaxError, "BASE-only key '%s' in router '%s'" % (key, appname)
router.update(routers[appname])
routers[appname] = router
if os.path.exists(abspath('applications', appname, routes)):
if exists(abspath('applications', appname, routes)):
load(routes, appname)
if routers:
@@ -336,14 +349,9 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
log_rewrite('URL rewrite is on. configuration in %s' % path)
regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*')
regex_anything = re.compile(r'(?<!\\)\$anything')
regex_redirect = re.compile(r'(\d+)->(.*)')
def compile_regex(k, v):
"""
Preprocess and compile the regular expressions in routes_app/in/out
The resulting regex will match a pattern of the form:
[remote address]:[protocol]://[host]:[method] [path]
@@ -380,8 +388,9 @@ def compile_regex(k, v):
def load_routers(all_apps):
"load-time post-processing of routers"
for app in routers.keys():
# initialize apps with routers that aren't present, on behalf of unit tests
for app in routers:
# initialize apps with routers that aren't present,
# on behalf of unit tests
if app not in all_apps:
all_apps.append(app)
router = Storage(routers.BASE) # new copy
@@ -420,10 +429,10 @@ def load_routers(all_apps):
routers.BASE.domains[router.domain] = app
if isinstance(router.controllers, str) and router.controllers == 'DEFAULT':
router.controllers = set()
if os.path.isdir(abspath('applications', app)):
if isdir(abspath('applications', app)):
cpath = abspath('applications', app, 'controllers')
for cname in os.listdir(cpath):
if os.path.isfile(abspath(cpath, cname)) and cname.endswith('.py'):
if isfile(abspath(cpath, cname)) and cname.endswith('.py'):
router.controllers.add(cname[:-3])
if router.controllers:
router.controllers.add('static')
@@ -458,16 +467,20 @@ def load_routers(all_apps):
#
domains = dict()
if routers.BASE.domains:
for (domain, app) in [(d.strip(':'), a.strip('/')) for (d, a) in routers.BASE.domains.items()]:
port = None
for (d, a) in routers.BASE.domains.iteritems():
(domain, app) = (d.strip(':'), a.strip('/'))
if ':' in domain:
(domain, port) = domain.split(':')
ctlr = None
fcn = None
else:
port = None
if '/' in app:
(app, ctlr) = app.split('/', 1)
else:
ctlr = None
if ctlr and '/' in ctlr:
(ctlr, fcn) = ctlr.split('/')
else:
fcn = None
if app not in all_apps and app not in routers:
raise SyntaxError, "unknown app '%s' in domains" % app
domains[(domain, port)] = (app, ctlr, fcn)
@@ -514,7 +527,8 @@ def regex_filter_in(e):
query = e.get('QUERY_STRING', None)
e['WEB2PY_ORIGINAL_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '')
if thread.routes.routes_in:
path = regex_uri(e, thread.routes.routes_in, "routes_in", e['PATH_INFO'])
path = regex_uri(e, thread.routes.routes_in,
"routes_in", e['PATH_INFO'])
rmatch = regex_redirect.match(path)
if rmatch:
raise HTTP(int(rmatch.group(1)),location=rmatch.group(2))
@@ -581,6 +595,9 @@ regex_args = re.compile(r'''
/?$) # trailing slash
''', re.X)
def sluggify(key):
return key.lower().replace('.','_')
def regex_url_in(request, environ):
"rewrite and parse incoming URL"
@@ -594,9 +611,8 @@ def regex_url_in(request, environ):
if thread.routes.routes_in:
environ = regex_filter_in(environ)
for (key, value) in environ.items():
request.env[key.lower().replace('.', '_')] = value
request.env.update((sluggify(k),v) for k,v in environ.iteritems())
path = request.env.path_info.replace('\\', '/')
@@ -606,7 +622,7 @@ def regex_url_in(request, environ):
match = regex_static.match(regex_space.sub('_', path))
if match and match.group('x'):
static_file = os.path.join(request.env.applications_parent,
static_file = pjoin(request.env.applications_parent,
'applications', match.group('b'),
'static', match.group('x'))
return (static_file, environ)
@@ -908,7 +924,7 @@ class MapUrlIn(object):
'''
if len(self.args) == 1 and self.arg0 in self.router.root_static:
self.controller = self.request.controller = 'static'
root_static_file = os.path.join(self.request.env.applications_parent,
root_static_file = pjoin(self.request.env.applications_parent,
'applications', self.application,
self.controller, self.arg0)
log_rewrite("route: root static=%s" % root_static_file)
@@ -962,7 +978,8 @@ class MapUrlIn(object):
bad_static = bad_static or name in ('', '.', '..') or not self.router._file_match.match(name)
if bad_static:
log_rewrite('bad static path=%s' % file)
raise HTTP(400, thread.routes.error_message % 'invalid request',
raise HTTP(400,
thread.routes.error_message % 'invalid request',
web2py_error='invalid static file')
#
# support language-specific static subdirectories,
@@ -970,13 +987,13 @@ class MapUrlIn(object):
# if language-specific file doesn't exist, try same file in static
#
if self.language:
static_file = os.path.join(self.request.env.applications_parent,
'applications', self.application,
'static', self.language, file)
if not self.language or not os.path.isfile(static_file):
static_file = os.path.join(self.request.env.applications_parent,
'applications', self.application,
'static', file)
static_file = pjoin(self.request.env.applications_parent,
'applications', self.application,
'static', self.language, file)
if not self.language or not isfile(static_file):
static_file = pjoin(self.request.env.applications_parent,
'applications', self.application,
'static', file)
log_rewrite("route: static=%s" % static_file)
return static_file
@@ -1040,12 +1057,14 @@ class MapUrlIn(object):
uri += '.' + self.extension
if self.language:
uri = '/%s%s' % (self.language, uri)
uri = '/%s%s' % (app, uri)
uri += self.args and urllib.quote('/' + '/'.join([str(x) for x in self.args])) or ''
uri += (self.query and ('?' + self.query) or '')
uri = '/%s%s%s%s' % (
app,
uri,
urllib.quote('/'+'/'.join(str(x) for x in self.args)) if self.args else '',
('?' + self.query) if self.query else '')
self.env['REQUEST_URI'] = uri
for (key, value) in self.env.items():
self.request.env[key.lower().replace('.', '_')] = value
self.request.env.update(
(sluggify(k),v) for k,v in self.env.iteritems())
@property
def arg0(self):
+3 -2
View File
@@ -1407,7 +1407,7 @@ class Worker(Thread):
raise BadRequest
req = match.groupdict()
for k,v in req.items():
for k,v in req.iteritems():
if not v:
req[k] = ""
if k == 'path':
@@ -1694,7 +1694,8 @@ class FileSystemWorker(Worker):
try:
# Get our file path
headers = dict([(str(k.lower()), v) for k, v in self.read_headers(sock_file).items()])
reader = self.read_headers(sock_file)
headers = dict((k.lower(),v) for k,v in reader.iteritems())
rpath = request.get('path', '').lstrip('/')
filepath = os.path.join(self.root, rpath)
filepath = os.path.abspath(filepath)
+9 -6
View File
@@ -18,7 +18,8 @@ if not hasattr(os, 'mkdir'):
if global_settings.db_sessions is not True:
global_settings.db_sessions = set()
global_settings.gluon_parent = os.environ.get('web2py_path', os.getcwd())
global_settings.gluon_parent = \
os.environ.get('web2py_path', os.getcwd())
global_settings.applications_parent = global_settings.gluon_parent
@@ -26,12 +27,14 @@ global_settings.app_folders = set()
global_settings.debugging = False
global_settings.is_pypy = hasattr(platform,'python_implementation') and \
platform.python_implementation() == 'PyPy'
global_settings.is_pypy = \
hasattr(platform,'python_implementation') and \
platform.python_implementation() == 'PyPy'
global_settings.is_jython = 'java' in sys.platform.lower() or \
hasattr(sys, 'JYTHON_JAR') or \
str(sys.copyright).find('Jython') > 0
global_settings.is_jython = \
'java' in sys.platform.lower() or \
hasattr(sys, 'JYTHON_JAR') or \
str(sys.copyright).find('Jython') > 0
+4 -2
View File
@@ -36,6 +36,8 @@ import re
import cStringIO
from gluon import current, redirect, A, URL, DIV, H3, UL, LI, SPAN, INPUT
import inspect
import settings
is_gae = settings.global_settings.web2py_runtime_gae
table_field = re.compile('[\w_]+\.[\w_]+')
widget_class = re.compile('^\w*')
@@ -581,7 +583,7 @@ class AutocompleteWidget(object):
def callback(self):
if self.keyword in self.request.vars:
field = self.fields[0]
if settings.global_settings.web2py_runtime_gae:
if is_gae:
rows = self.db(field.__ge__(self.request.vars[self.keyword])&field.__lt__(self.request.vars[self.keyword]+ u'\ufffd')).select(orderby=self.orderby,limitby=self.limitby,*self.fields)
else:
rows = self.db(field.like(self.request.vars[self.keyword]+'%')).select(orderby=self.orderby,limitby=self.limitby,distinct=self.distinct,*self.fields)
@@ -1834,7 +1836,7 @@ class SQLFORM(FORM):
else:
rows = dbset.select(left=left,orderby=orderby,*columns)
if exportManager.has_key(export_type):
if export_type in exportManager:
value = exportManager[export_type]
clazz = value[0] if hasattr(value, '__getitem__') else value
oExp = clazz(rows)
+60 -129
View File
@@ -15,10 +15,10 @@ Contributors:
"""
import os
import re
import cgi
import cStringIO
import logging
from re import compile, sub, escape, DOTALL
try:
# have web2py
from restricted import RestrictedError
@@ -57,6 +57,18 @@ class SuperNode(Node):
def __repr__(self):
return "%s->%s" % (self.name, self.value)
def output_aux(node,blocks):
# If we have a block level
# If we can override this block.
# Override block from vars.
# Else we take the default
# Else its just a string
return (blocks[node.name].output(blocks)
if node.name in blocks else
node.output(blocks)) \
if isinstance(node, BlockNode) \
else str(node)
class BlockNode(Node):
"""
Block Container.
@@ -81,8 +93,7 @@ class BlockNode(Node):
def __repr__(self):
lines = ['%sblock %s%s' % (self.left,self.name,self.right)]
for node in self.nodes:
lines.append(str(node))
lines += [str(node) for node in self.nodes]
lines.append('%send%s' % (self.left, self.right))
return ''.join(lines)
@@ -90,11 +101,8 @@ class BlockNode(Node):
"""
Get this BlockNodes content, not including child Nodes
"""
lines = []
for node in self.nodes:
if not isinstance(node, BlockNode):
lines.append(str(node))
return ''.join(lines)
return ''.join(str(node) for node in self.nodes \
if not isinstance(node, BlockNode))
def append(self, node):
"""
@@ -122,30 +130,14 @@ class BlockNode(Node):
else:
raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other)
def output(self, blocks):
"""
Merges all nodes into a single string.
blocks -- Dictionary of blocks that are extending
from this template.
"""
lines = []
# Get each of our nodes
for node in self.nodes:
# If we have a block level node.
if isinstance(node, BlockNode):
# If we can override this block.
if node.name in blocks:
# Override block from vars.
lines.append(blocks[node.name].output(blocks))
# Else we take the default
else:
lines.append(node.output(blocks))
# Else its just a string
else:
lines.append(str(node))
# Now combine all of our lines together.
return ''.join(lines)
return ''.join(output_aux(node,blocks) for node in self.nodes)
class Content(BlockNode):
"""
@@ -165,29 +157,13 @@ class Content(BlockNode):
self.pre_extend = pre_extend
def __str__(self):
lines = []
# For each of our nodes
for node in self.nodes:
# If it is a block node.
if isinstance(node, BlockNode):
# And the node has a name that corresponds with a block in us
if node.name in self.blocks:
# Use the overriding output.
lines.append(self.blocks[node.name].output(self.blocks))
else:
# Otherwise we just use the nodes output.
lines.append(node.output(self.blocks))
else:
# It is just a string, so include it.
lines.append(str(node))
# Merge our list together.
return ''.join(lines)
return ''.join(output_aux(node,self.blocks) for node in self.nodes)
def _insert(self, other, index = 0):
"""
Inserts object at index.
"""
if isinstance(other, str) or isinstance(other, Node):
if isinstance(other, (str, Node)):
self.nodes.insert(index, other)
else:
raise TypeError("Invalid type, must be instance of ``str`` or ``Node``.")
@@ -201,8 +177,7 @@ class Content(BlockNode):
if isinstance(other, (list, tuple)):
# Must reverse so the order stays the same.
other.reverse()
for item in other:
self._insert(item, index)
(self._insert(item, index) for item in other)
else:
self._insert(other, index)
@@ -210,7 +185,7 @@ class Content(BlockNode):
"""
Adds a node to list. If it is a BlockNode then we assign a block for it.
"""
if isinstance(node, str) or isinstance(node, Node):
if isinstance(node, (str, Node)):
self.nodes.append(node)
if isinstance(node, BlockNode):
self.blocks[node.name] = node
@@ -233,18 +208,18 @@ class Content(BlockNode):
class TemplateParser(object):
default_delimiters = ('{{','}}')
r_tag = re.compile(r'(\{\{.*?\}\})', re.DOTALL)
r_tag = compile(r'(\{\{.*?\}\})', DOTALL)
r_multiline = re.compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', re.DOTALL)
r_multiline = compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', DOTALL)
# These are used for re-indentation.
# Indent + 1
re_block = re.compile('^(elif |else:|except:|except |finally:).*$',
re.DOTALL)
re_block = compile('^(elif |else:|except:|except |finally:).*$',DOTALL)
# Indent - 1
re_unblock = re.compile('^(return|continue|break|raise)( .*)?$', re.DOTALL)
re_unblock = compile('^(return|continue|break|raise)( .*)?$', DOTALL)
# Indent - 1
re_pass = re.compile('^pass( .*)?$', re.DOTALL)
re_pass = compile('^pass( .*)?$', DOTALL)
def __init__(self, text,
name = "ParserContainer",
@@ -291,13 +266,16 @@ class TemplateParser(object):
# allow optional alternative delimiters
self.delimiters = delimiters
if delimiters != self.default_delimiters:
escaped_delimiters = (re.escape(delimiters[0]),re.escape(delimiters[1]))
self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters, re.DOTALL)
elif context.has_key('response') and hasattr(context['response'],'delimiters'):
escaped_delimiters = (escape(delimiters[0]),
escape(delimiters[1]))
self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, DOTALL)
elif hasattr(context.get('response',None),'delimiters'):
if context['response'].delimiters != self.default_delimiters:
escaped_delimiters = (re.escape(context['response'].delimiters[0]),
re.escape(context['response'].delimiters[1]))
self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters,re.DOTALL)
escaped_delimiters = (
escape(context['response'].delimiters[0]),
escape(context['response'].delimiters[1]))
self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters,
DOTALL)
# Create a root level Content that everything will go into.
self.content = Content(name=name)
@@ -524,17 +502,19 @@ class TemplateParser(object):
# the parent nodes.
self.content.nodes = []
t_content = t.content
# Set our include, unique by filename
t.content.blocks['__include__' + filename] = buf
t_content.blocks['__include__' + filename] = buf
# Make sure our pre_extended nodes go first
t.content.insert(pre)
t_content.insert(pre)
# Then we extend our blocks
t.content.extend(self.content)
t_content.extend(self.content)
# Work off the parent node.
self.content = t.content
self.content = t_content
def parse(self, text):
@@ -553,69 +533,20 @@ class TemplateParser(object):
ij = self.r_tag.split(text)
# j = current index
# i = current item
stack = self.stack
for j in range(len(ij)):
i = ij[j]
if i:
if len(self.stack) == 0:
if not stack:
self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag')
# Our current element in the stack.
top = self.stack[-1]
top = stack[-1]
if in_tag:
line = i
# If we are missing any strings!!!!
# This usually happens with the following example
# template code
#
# {{a = '}}'}}
# or
# {{a = '}}blahblah{{'}}
#
# This will fix these
# This is commented out because the current template
# system has this same limitation. Since this has a
# performance hit on larger templates, I do not recommend
# using this code on production systems. This is still here
# for "i told you it *can* be fixed" purposes.
#
#
# if line.count("'") % 2 != 0 or line.count('"') % 2 != 0:
#
# # Look ahead
# la = 1
# nextline = ij[j+la]
#
# # As long as we have not found our ending
# # brackets keep going
# while '}}' not in nextline:
# la += 1
# nextline += ij[j+la]
# # clear this line, so we
# # don't attempt to parse it
# # this is why there is an "if i"
# # around line 530
# ij[j+la] = ''
#
# # retrieve our index.
# index = nextline.index('}}')
#
# # Everything before the new brackets
# before = nextline[:index+2]
#
# # Everything after
# after = nextline[index+2:]
#
# # Make the next line everything after
# # so it parses correctly, this *should* be
# # all html
# ij[j+1] = after
#
# # Add everything before to the current line
# line += before
# Get rid of '{{' and '}}'
line = line[2:-2].strip()
@@ -633,9 +564,9 @@ class TemplateParser(object):
# Perform block comment escaping.
# This performs escaping ON anything
# in between """ and """
line = re.sub(TemplateParser.r_multiline,
remove_newline,
line)
line = sub(TemplateParser.r_multiline,
remove_newline,
line)
if line.startswith('='):
# IE: {{=response.title}}
@@ -672,7 +603,7 @@ class TemplateParser(object):
self.lexers[name](parser = self,
value = value,
top = top,
stack = self.stack,)
stack = stack)
elif name == '=':
# So we have a variable to insert into
@@ -693,7 +624,7 @@ class TemplateParser(object):
# so anything after this gets added
# to this node. This allows us to
# "nest" nodes.
self.stack.append(node)
stack.append(node)
elif name == 'end' and not value.startswith('='):
# We are done with this node.
@@ -702,7 +633,7 @@ class TemplateParser(object):
self.blocks[top.name] = top
# Pop it.
self.stack.pop()
stack.pop()
elif name == 'super' and not value.startswith('='):
# Get our correct target name
@@ -757,17 +688,17 @@ class TemplateParser(object):
# So we can properly put a response.write() in place.
continuation = False
len_parsed = 0
for k in range(len(tokens)):
for k, token in enumerate(tokens):
tokens[k] = tokens[k].strip()
len_parsed += len(tokens[k])
token = tokens[k] = token.strip()
len_parsed += len(token)
if tokens[k].startswith('='):
if tokens[k].endswith('\\'):
if token.startswith('='):
if token.endswith('\\'):
continuation = True
tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip())
tokens[k] = "\n%s(%s" % (self.writer, token[1:].strip())
else:
tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip())
tokens[k] = "\n%s(%s)" % (self.writer, token[1:].strip())
elif continuation:
tokens[k] += ')'
continuation = False
@@ -912,7 +843,7 @@ def render(content = "hello world",
# Add it to the context so we can use it.
if not 'NOESCAPE' in context:
context['NOESCAPE'] = XML
context['NOESCAPE'] = NOESCAPE
# save current response class
if context and 'response' in context:
+31
View File
@@ -129,6 +129,26 @@ class TestFields(unittest.TestCase):
)
self.assertEqual(db.t.insert(a=t0), 1)
self.assertEqual(db().select(db.t.a)[0].a, t0)
## Row APIs
row = db().select(db.t.a)[0]
self.assertEqual(db.t[1].a,t0)
self.assertEqual(db.t['a'],db.t.a)
self.assertEqual(db.t(1).a,t0)
self.assertTrue(db.t(1,a=None)==None)
self.assertFalse(db.t(1,a=t0)==None)
self.assertEqual(row.a,t0)
self.assertEqual(row['a'],t0)
self.assertEqual(row['t.a'],t0)
self.assertEqual(row('t.a'),t0)
## Lazy and Virtual fields
db.t.b = Field.Virtual(lambda row: row.t.a)
db.t.c = Field.Lazy(lambda row: row.t.a)
row = db().select(db.t.a)[0]
self.assertEqual(row.b,t0)
self.assertEqual(row.c(),t0)
db.t.drop()
db.define_table('t', Field('a', 'time', default='11:30'))
t0 = datetime.time(10, 30, 55)
@@ -369,6 +389,17 @@ class TestJoin(unittest.TestCase):
db.t1.drop()
db.t2.drop()
db.define_table('person',Field('name'))
id = db.person.insert(name="max")
self.assertEqual(id.name,'max')
db.define_table('dog',Field('name'),Field('owner','reference person'))
db.dog.insert(name='skipper',owner=1)
row = db(db.person.id==db.dog.owner).select().first()
self.assertEqual(row[db.person.name],'max')
self.assertEqual(row['person.name'],'max')
db.dog.drop()
self.assertEqual(len(db.person._referenced_by),0)
db.person.drop()
class TestMinMaxSum(unittest.TestCase):
+34
View File
@@ -17,6 +17,7 @@ import languages
import tempfile
import threading
import logging
from storage import Storage
try:
import multiprocessing
@@ -53,6 +54,39 @@ try:
for result in results:
self.assertTrue(result)
class TestTranslations(unittest.TestCase):
def setUp(self):
self.request = Storage()
self.request.folder = 'applications/welcome'
self.request.env = Storage()
self.request.env.http_accept_language = 'en'
def tearDown(self):
pass
def test_plain(self):
T = languages.translator(self.request)
self.assertEqual(str(T('Hello World')),
'Hello World')
self.assertEqual(str(T('Hello World## comment')),
'Hello World')
self.assertEqual(str(T('%s %%{shop}', 1)),
'1 shop')
self.assertEqual(str(T('%s %%{shop}', 2)),
'2 shops')
self.assertEqual(str(T('%s %%{shop[0]}', 1)),
'1 shop')
self.assertEqual(str(T('%s %%{shop[0]}', 2)),
'2 shops')
self.assertEqual(str(T.M('**Hello World**')),
'<strong>Hello World</strong>')
T.force('it')
self.assertEqual(str(T('Hello World')),
'Salve Mondo')
except ImportError:
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 gluon import *
from gluon.contrib.autolinks import expand_one
from gluon.dal import Row
import serializers
@@ -798,6 +799,141 @@ def addrow(form, a, b, c, style, _id, position=-1):
class Auth(object):
default_settings = {
'hideerror': False,
'password_min_length': 4,
'cas_maps': None,
'reset_password_requires_verification': False,
'registration_requires_verification': False,
'registration_requires_approval': False,
'login_after_registration': False,
'login_after_password_change': True,
'alternate_requires_registration': False,
'create_user_groups': "user_%(id)s",
'everybody_group_id': None,
'login_captcha': None,
'register_captcha': None,
'retrieve_username_captcha': None,
'retrieve_password_captcha': None,
'captcha': None,
'expiration': 3600, # one hour
'long_expiration': 3600*30*24, # one month
'remember_me_form': True,
'allow_basic_login': False,
'allow_basic_login_only': False,
'on_failed_authentication': lambda x: redirect(x),
'formstyle': 'table3cols',
'label_separator': ': ',
'password_field': 'password',
'table_user_name': 'auth_user',
'table_group_name': 'auth_group',
'table_membership_name': 'auth_membership',
'table_permission_name': 'auth_permission',
'table_event_name': 'auth_event',
'table_cas_name': 'auth_cas',
'table_user': None,
'table_group': None,
'table_membership': None,
'table_permission': None,
'table_event': None,
'table_cas': None,
'showid': False,
'login_email_validate': True,
'login_userfield': None,
'logout_onlogout': None,
'register_fields': None,
'register_verify_password': True,
'profile_fields': None,
'email_case_sensitive': True,
'username_case_sensitive': True,
}
# ## these are messages that can be customized
default_messages = {
'login_button': 'Login',
'register_button': 'Register',
'password_reset_button': 'Request reset password',
'password_change_button': 'Change password',
'profile_save_button': 'Save profile',
'submit_button': 'Submit',
'verify_password': 'Verify Password',
'delete_label': 'Check to delete',
'function_disabled': 'Function disabled',
'access_denied': 'Insufficient privileges',
'registration_verifying': 'Registration needs verification',
'registration_pending': 'Registration is pending approval',
'login_disabled': 'Login disabled by administrator',
'logged_in': 'Logged in',
'email_sent': 'Email sent',
'unable_to_send_email': 'Unable to send email',
'email_verified': 'Email verified',
'logged_out': 'Logged out',
'registration_successful': 'Registration successful',
'invalid_email': 'Invalid email',
'unable_send_email': 'Unable to send email',
'invalid_login': 'Invalid login',
'invalid_user': 'Invalid user',
'invalid_password': 'Invalid password',
'is_empty': "Cannot be empty",
'mismatched_password': "Password fields don't match",
'verify_email': 'Click on the link %(link)s to verify your email',
'verify_email_subject': 'Email verification',
'username_sent': 'Your username was emailed to you',
'new_password_sent': 'A new password was emailed to you',
'password_changed': 'Password changed',
'retrieve_username': 'Your username is: %(username)s',
'retrieve_username_subject': 'Username retrieve',
'retrieve_password': 'Your password is: %(password)s',
'retrieve_password_subject': 'Password retrieve',
'reset_password': \
'Click on the link %(link)s to reset your password',
'reset_password_subject': 'Password reset',
'invalid_reset_password': 'Invalid reset password',
'profile_updated': 'Profile updated',
'new_password': 'New password',
'old_password': 'Old password',
'group_description': 'Group uniquely assigned to user %(id)s',
'register_log': 'User %(id)s Registered',
'login_log': 'User %(id)s Logged-in',
'login_failed_log': None,
'logout_log': 'User %(id)s Logged-out',
'profile_log': 'User %(id)s Profile updated',
'verify_email_log': 'User %(id)s Verification email sent',
'retrieve_username_log': 'User %(id)s Username retrieved',
'retrieve_password_log': 'User %(id)s Password retrieved',
'reset_password_log': 'User %(id)s Password reset',
'change_password_log': 'User %(id)s Password changed',
'add_group_log': 'Group %(group_id)s created',
'del_group_log': 'Group %(group_id)s deleted',
'add_membership_log': None,
'del_membership_log': None,
'has_membership_log': None,
'add_permission_log': None,
'del_permission_log': None,
'has_permission_log': None,
'impersonate_log': 'User %(id)s is impersonating %(other_id)s',
'label_first_name': 'First name',
'label_last_name': 'Last name',
'label_username': 'Username',
'label_email': 'E-mail',
'label_password': 'Password',
'label_registration_key': 'Registration key',
'label_reset_password_key': 'Reset Password key',
'label_registration_id': 'Registration identifier',
'label_role': 'Role',
'label_description': 'Description',
'label_user_id': 'User ID',
'label_group_id': 'Group ID',
'label_name': 'Name',
'label_table_name': 'Object or table name',
'label_record_id': 'Record ID',
'label_time_stamp': 'Timestamp',
'label_client_ip': 'Client IP',
'label_origin': 'Origin',
'label_remember_me': "Remember me (for 30 days)",
'verify_password_comment': 'please input your password again',
}
"""
Class for authentication, authorization, role based access control.
@@ -894,10 +1030,11 @@ class Auth(object):
open(filename,'w').write(key)
return key
def url(self, f=None, args=None, vars=None):
def url(self, f=None, args=None, vars=None, scheme=False):
if args is None: args=[]
if vars is None: vars={}
return URL(c=self.settings.controller, f=f, args=args, vars=vars)
return URL(c=self.settings.controller,
f=f, args=args, vars=vars,scheme=scheme)
def here(self):
return URL(args=current.request.args,vars=current.request.vars)
@@ -933,221 +1070,67 @@ class Auth(object):
else:
self.user = None
session.auth = None
settings = self.settings = Settings()
# ## what happens after login?
self.next = current.request.vars._next
if isinstance(self.next,(list,tuple)):
self.next = self.next[0]
url_index = URL(controller,'index')
url_login = URL(controller,function,args='login')
# ## what happens after registration?
settings.hideerror = False
settings.password_min_length = 4
settings.cas_domains = [request.env.http_host]
settings.cas_provider = cas_provider
settings.cas_actions = {'login':'login',
'validate':'validate',
'servicevalidate':'serviceValidate',
'proxyvalidate':'proxyValidate',
'logout':'logout'}
settings.cas_maps = None
settings.extra_fields = {}
settings.actions_disabled = []
settings.reset_password_requires_verification = False
settings.registration_requires_verification = False
settings.registration_requires_approval = False
settings.login_after_registration = False
settings.alternate_requires_registration = False
settings.create_user_groups = "user_%(id)s"
settings.everybody_group_id = None
settings.controller = controller
settings.function = function
settings.login_url = self.url(function, args='login')
settings.logged_url = self.url(function, args='profile')
settings.download_url = self.url('download')
settings.mailer = (mailer==True) and Mail() or mailer
settings.login_captcha = None
settings.register_captcha = None
settings.retrieve_username_captcha = None
settings.retrieve_password_captcha = None
settings.captcha = None
settings.expiration = 3600 # one hour
settings.long_expiration = 3600*30*24 # one month
settings.remember_me_form = True
settings.allow_basic_login = False
settings.allow_basic_login_only = False
settings.on_failed_authorization = \
self.url(function, args='not_authorized')
settings.on_failed_authentication = lambda x: redirect(x)
settings.formstyle = 'table3cols'
settings.label_separator = ': '
# ## table names to be used
settings.password_field = 'password'
settings.table_user_name = 'auth_user'
settings.table_group_name = 'auth_group'
settings.table_membership_name = 'auth_membership'
settings.table_permission_name = 'auth_permission'
settings.table_event_name = 'auth_event'
settings.table_cas_name = 'auth_cas'
# ## if none, they will be created, unless DAL(lazy_tables=True)!!!
settings.table_user = None
settings.table_group = None
settings.table_membership = None
settings.table_permission = None
settings.table_event = None
settings.table_cas = None
# ##
settings.showid = False
# ## these should be functions or lambdas
settings.login_next = self.url('index')
settings.login_onvalidation = []
settings.login_onaccept = []
settings.login_methods = [self]
settings.login_form = self
settings.login_email_validate = True
settings.login_userfield = None
settings.logout_next = self.url('index')
settings.logout_onlogout = None
settings.register_next = self.url('index')
settings.register_onvalidation = []
settings.register_onaccept = []
settings.register_fields = None
settings.register_verify_password = True
settings.verify_email_next = self.url(function, args='login')
settings.verify_email_onaccept = []
settings.profile_next = self.url('index')
settings.profile_onvalidation = []
settings.profile_onaccept = []
settings.profile_fields = None
settings.retrieve_username_next = self.url('index')
settings.retrieve_password_next = self.url('index')
settings.request_reset_password_next = self.url(function, args='login')
settings.reset_password_next = self.url(function, args='login')
settings.change_password_next = self.url('index')
settings.change_password_onvalidation = []
settings.change_password_onaccept = []
settings.retrieve_password_onvalidation = []
settings.reset_password_onvalidation = []
settings.reset_password_onaccept = []
settings.email_case_sensitive = True
settings.username_case_sensitive = True
settings.hmac_key = hmac_key
settings = self.settings = Settings()
settings.update(Auth.default_settings)
settings.update({
'cas_domains': [request.env.http_host],
'cas_provider': cas_provider,
'cas_actions': {'login':'login',
'validate':'validate',
'servicevalidate':'serviceValidate',
'proxyvalidate':'proxyValidate',
'logout':'logout'},
'extra_fields': {},
'actions_disabled': [],
'controller': controller,
'function': function,
'login_url': url_login,
'logged_url': URL(controller, function, args='profile'),
'download_url': URL(controller,'download'),
'mailer': (mailer==True) and Mail() or mailer,
'on_failed_authorization': \
URL(controller,function, args='not_authorized'),
'login_next': url_index,
'login_onvalidation': [],
'login_onaccept': [],
'login_methods': [self],
'login_form': self,
'logout_next': url_index,
'logout_onlogout': None,
'register_next': url_index,
'register_onvalidation': [],
'register_onaccept': [],
'verify_email_next': url_login,
'verify_email_onaccept': [],
'profile_next': url_index,
'profile_onvalidation': [],
'profile_onaccept': [],
'retrieve_username_next': url_index,
'retrieve_password_next': url_index,
'request_reset_password_next': url_login,
'reset_password_next': url_index,
'change_password_next': url_index,
'change_password_onvalidation': [],
'change_password_onaccept': [],
'retrieve_password_onvalidation': [],
'reset_password_onvalidation': [],
'reset_password_onaccept': [],
'hmac_key': hmac_key,
})
settings.lock_keys = True
# ## these are messages that can be customized
messages = self.messages = Messages(current.T)
messages.login_button = 'Login'
messages.register_button = 'Register'
messages.password_reset_button = 'Request reset password'
messages.password_change_button = 'Change password'
messages.profile_save_button = 'Save profile'
messages.submit_button = 'Submit'
messages.verify_password = 'Verify Password'
messages.delete_label = 'Check to delete'
messages.function_disabled = 'Function disabled'
messages.access_denied = 'Insufficient privileges'
messages.registration_verifying = 'Registration needs verification'
messages.registration_pending = 'Registration is pending approval'
messages.login_disabled = 'Login disabled by administrator'
messages.logged_in = 'Logged in'
messages.email_sent = 'Email sent'
messages.unable_to_send_email = 'Unable to send email'
messages.email_verified = 'Email verified'
messages.logged_out = 'Logged out'
messages.registration_successful = 'Registration successful'
messages.invalid_email = 'Invalid email'
messages.unable_send_email = 'Unable to send email'
messages.invalid_login = 'Invalid login'
messages.invalid_user = 'Invalid user'
messages.invalid_password = 'Invalid password'
messages.is_empty = "Cannot be empty"
messages.mismatched_password = "Password fields don't match"
messages.verify_email = \
'Click on the link ' + \
URL('default','user',args='verify_email',scheme=True) + \
'/%(key)s to verify your email'
messages.verify_email_subject = 'Email verification'
messages.username_sent = 'Your username was emailed to you'
messages.new_password_sent = 'A new password was emailed to you'
messages.password_changed = 'Password changed'
messages.retrieve_username = 'Your username is: %(username)s'
messages.retrieve_username_subject = 'Username retrieve'
messages.retrieve_password = 'Your password is: %(password)s'
messages.retrieve_password_subject = 'Password retrieve'
messages.reset_password = \
'Click on the link ' + \
URL('default','user',args='reset_password',scheme=True) + \
'/%(key)s to reset your password'
messages.reset_password_subject = 'Password reset'
messages.invalid_reset_password = 'Invalid reset password'
messages.profile_updated = 'Profile updated'
messages.new_password = 'New password'
messages.old_password = 'Old password'
messages.group_description = \
'Group uniquely assigned to user %(id)s'
messages.register_log = 'User %(id)s Registered'
messages.login_log = 'User %(id)s Logged-in'
messages.login_failed_log = None
messages.logout_log = 'User %(id)s Logged-out'
messages.profile_log = 'User %(id)s Profile updated'
messages.verify_email_log = 'User %(id)s Verification email sent'
messages.retrieve_username_log = 'User %(id)s Username retrieved'
messages.retrieve_password_log = 'User %(id)s Password retrieved'
messages.reset_password_log = 'User %(id)s Password reset'
messages.change_password_log = 'User %(id)s Password changed'
messages.add_group_log = 'Group %(group_id)s created'
messages.del_group_log = 'Group %(group_id)s deleted'
messages.add_membership_log = None
messages.del_membership_log = None
messages.has_membership_log = None
messages.add_permission_log = None
messages.del_permission_log = None
messages.has_permission_log = None
messages.impersonate_log = 'User %(id)s is impersonating %(other_id)s'
messages.label_first_name = 'First name'
messages.label_last_name = 'Last name'
messages.label_username = 'Username'
messages.label_email = 'E-mail'
messages.label_password = 'Password'
messages.label_registration_key = 'Registration key'
messages.label_reset_password_key = 'Reset Password key'
messages.label_registration_id = 'Registration identifier'
messages.label_role = 'Role'
messages.label_description = 'Description'
messages.label_user_id = 'User ID'
messages.label_group_id = 'Group ID'
messages.label_name = 'Name'
messages.label_table_name = 'Object or table name'
messages.label_record_id = 'Record ID'
messages.label_time_stamp = 'Timestamp'
messages.label_client_ip = 'Client IP'
messages.label_origin = 'Origin'
messages.label_remember_me = "Remember me (for 30 days)"
messages['T'] = current.T
messages.verify_password_comment = 'please input your password again'
messages.update(Auth.default_messages)
messages.lock_keys = True
# for "remember me" option
@@ -1336,37 +1319,39 @@ class Auth(object):
settings = self.settings
request = current.request
T = current.T
def lazy_user (auth = self): return auth.user_id
reference_user = 'reference %s' % settings.table_user_name
def lazy_user (auth = self):
return auth.user_id
def represent(id,record=None,s=settings):
try:
user = s.table_user(id)
return '%(first_name)s %(last_name)s' % user
except: return id
self.signature = db.Table(self.db,'auth_signature',
Field('is_active','boolean',
default=True,
readable=False, writable=False,
label=T('Is Active')),
Field('created_on','datetime',
default=request.now,
writable=False, readable=False,
label=T('Created On')),
Field('created_by',
reference_user,
default=lazy_user, represent=represent,
writable=False, readable=False,
label=T('Created By')),
Field('modified_on','datetime',
update=request.now,default=request.now,
writable=False,readable=False,
label=T('Modified On')),
Field('modified_by',
reference_user,represent=represent,
default=lazy_user,update=lazy_user,
writable=False,readable=False,
label=T('Modified By')))
except:
return id
self.signature = db.Table(
self.db,'auth_signature',
Field('is_active','boolean',
default=True,
readable=False, writable=False,
label=T('Is Active')),
Field('created_on','datetime',
default=request.now,
writable=False, readable=False,
label=T('Created On')),
Field('created_by',
reference_user,
default=lazy_user, represent=represent,
writable=False, readable=False,
label=T('Created By')),
Field('modified_on','datetime',
update=request.now,default=request.now,
writable=False,readable=False,
label=T('Modified On')),
Field('modified_by',
reference_user,represent=represent,
default=lazy_user,update=lazy_user,
writable=False,readable=False,
label=T('Modified By')))
def define_tables(self, username=False, signature=None,
migrate=True, fake_migrate=False):
@@ -1396,8 +1381,7 @@ class Auth(object):
elif isinstance(signature,self.db.Table):
signature_list = [signature]
else:
signature_list = signature
lazy_tables, db._lazy_tables = db._lazy_tables, False
signature_list = signature
is_not_empty = IS_NOT_EMPTY(error_message=self.messages.is_empty)
is_crypted = CRYPT(key=settings.hmac_key,
min_length=settings.password_min_length)
@@ -1600,7 +1584,6 @@ class Auth(object):
actions=actions,
maps=maps)
def log_event(self, description, vars=None, origin='auth'):
"""
usage:
@@ -1667,6 +1650,11 @@ class Auth(object):
return user
def basic(self):
"""
perform basic login.
reads current.request.env.http_authorization
and returns basic_allowed,basic_accepted,user
"""
if not self.settings.allow_basic_login:
return (False,False,False)
basic = current.request.env.http_authorization
@@ -1675,11 +1663,23 @@ class Auth(object):
(username, password) = base64.b64decode(basic[6:]).split(':')
return (True, True, self.login_bare(username, password))
def login_user(self,user):
"""
login the user = db.auth_user(id)
"""
# user=Storage(self.table_user()._filter_fields(user,id=True))
current.session.auth = Storage(
user = user,
last_visit = current.request.now,
expiration = self.settings.expiration,
hmac_key = web2py_uuid())
self.user = user
self.update_groups()
def login_bare(self, username, password):
"""
logins user
logins user as specified by usernname (or email) and password
"""
request = current.request
session = current.session
table_user = self.table_user()
@@ -1694,12 +1694,7 @@ class Auth(object):
if user and user.get(passfield,False):
password = table_user[passfield].validate(password)[0]
if not user.registration_key and password == user[passfield]:
user = Storage(table_user._filter_fields(user, id=True))
session.auth = Storage(user=user, last_visit=request.now,
expiration=self.settings.expiration,
hmac_key = web2py_uuid())
self.user = user
self.update_groups()
self.login_user(user)
return user
else:
# user not in database try other login methods
@@ -1738,15 +1733,15 @@ class Auth(object):
renew=interactivelogin)
service = session._cas_service
del session._cas_service
if request.vars.has_key('warn') and not interactivelogin:
if 'warn' in request.vars and not interactivelogin:
response.headers['refresh'] = "5;URL=%s"%service+"?ticket="+ticket
return A("Continue to %s"%service,
_href=service+"?ticket="+ticket)
else:
redirect(service+"?ticket="+ticket)
if self.is_logged_in() and not request.vars.has_key('renew'):
if self.is_logged_in() and not 'renew' in request.vars:
return allow_access()
elif not self.is_logged_in() and request.vars.has_key('gateway'):
elif not self.is_logged_in() and 'gateway' in request.vars:
redirect(service)
def cas_onaccept(form, onaccept=onaccept):
if not onaccept is DEFAULT: onaccept(form)
@@ -1759,7 +1754,7 @@ class Auth(object):
db, table = self.db, self.table_cas()
current.response.headers['Content-Type']='text'
ticket = request.vars.ticket
renew = True if request.vars.has_key('renew') else False
renew = 'renew' in request.vars
row = table(ticket=ticket)
success = False
if row:
@@ -1973,25 +1968,18 @@ class Auth(object):
# process authenticated users
if user:
user = Storage(table_user._filter_fields(user, id=True))
user = Row(table_user._filter_fields(user, id=True))
# process authenticated users
# user wants to be logged in for longer
session.auth = Storage(
user = user,
last_visit = request.now,
expiration = request.vars.get("remember",False) and \
self.settings.long_expiration or self.settings.expiration,
remember = request.vars.has_key("remember"),
hmac_key = web2py_uuid()
)
self.user = user
self.login_user(user)
session.auth.expiration = \
request.vars.get('remember',False) and \
self.settings.long_expiration or \
self.settings.expiration
session.auth.remember = 'remember' in request.vars
self.log_event(log, user)
session.flash = self.messages.logged_in
self.update_groups()
# how to continue
if self.settings.login_form == self:
if accepted_form:
@@ -2131,11 +2119,14 @@ class Auth(object):
if self.settings.everybody_group_id:
self.add_membership(self.settings.everybody_group_id, form.vars.id)
if self.settings.registration_requires_verification:
link = self.url('user',args=('verify_email',key),scheme=True)
if not self.settings.mailer or \
not self.settings.mailer.send(to=form.vars.email,
subject=self.messages.verify_email_subject,
message=self.messages.verify_email
% dict(key=key)):
not self.settings.mailer.send(
to=form.vars.email,
subject=self.messages.verify_email_subject,
message=self.messages.verify_email \
% dict(key=key,link=link)):
self.db.rollback()
response.flash = self.messages.unable_send_email
return form
@@ -2149,13 +2140,10 @@ class Auth(object):
if not self.settings.registration_requires_verification:
table_user[form.vars.id] = dict(registration_key='')
session.flash = self.messages.registration_successful
user = self.db(table_user[username] == form.vars[username]).select().first()
user = Storage(table_user._filter_fields(user, id=True))
session.auth = Storage(user=user, last_visit=request.now,
expiration=self.settings.expiration,
hmac_key = web2py_uuid())
self.user = user
self.update_groups()
user = self.db(
table_user[username] == form.vars[username]
).select().first()
self.login_user(user)
session.flash = self.messages.logged_in
self.log_event(log, form.vars)
callback(onaccept,form)
@@ -2192,7 +2180,7 @@ class Auth(object):
key = getarg(-1)
table_user = self.table_user()
user = self.db(table_user.registration_key == key).select().first()
user = table_user(registration_key=key)
if not user:
redirect(self.settings.login_url)
if self.settings.registration_requires_approval:
@@ -2267,7 +2255,7 @@ class Auth(object):
if form.accepts(request, session,
formname='retrieve_username', dbio=False,
onvalidation=onvalidation,hideerror=self.settings.hideerror):
user = self.db(table_user.email == form.vars.email).select().first()
user = table_user(email=form.vars.email)
if not user:
current.session.flash = \
self.messages.invalid_email
@@ -2345,7 +2333,7 @@ class Auth(object):
if form.accepts(request, session,
formname='retrieve_password', dbio=False,
onvalidation=onvalidation,hideerror=self.settings.hideerror):
user = self.db(table_user.email == form.vars.email).select().first()
user = table_user(email=form.vars.email)
if not user:
current.session.flash = \
self.messages.invalid_email
@@ -2398,12 +2386,12 @@ class Auth(object):
session = current.session
if next is DEFAULT:
next = self.next or self.settings.reset_password_next
next = self.settings.reset_password_next
try:
key = request.vars.key or getarg(-1)
t0 = int(key.split('-')[0])
if time.time()-t0 > 60*60*24: raise Exception
user = self.db(table_user.reset_password_key == key).select().first()
user = table_user(reset_password_key=key)
if not user: raise Exception
except Exception:
session.flash = self.messages.invalid_reset_password
@@ -2422,11 +2410,15 @@ class Auth(object):
formstyle=self.settings.formstyle,
separator=self.settings.label_separator
)
if form.accepts(request,session,hideerror=self.settings.hideerror):
user.update_record(**{passfield:str(form.vars.new_password),
'registration_key':'',
'reset_password_key':''})
if form.accepts(request,session,
hideerror=self.settings.hideerror):
user.update_record(
**{passfield:str(form.vars.new_password),
'registration_key':'',
'reset_password_key':''})
session.flash = self.messages.password_changed
if self.settings.login_after_password_change:
self.login_user(user)
redirect(next)
return form
@@ -2481,7 +2473,7 @@ class Auth(object):
formname='reset_password', dbio=False,
onvalidation=onvalidation,
hideerror=self.settings.hideerror):
user = self.db(table_user.email == form.vars.email).select().first()
user = table_user(email=form.vars.email)
if not user:
session.flash = self.messages.invalid_email
redirect(self.url(args=request.args))
@@ -2504,11 +2496,14 @@ class Auth(object):
def email_reset_password(self,user):
reset_password_key = str(int(time.time()))+'-' + web2py_uuid()
link = self.url('user',
args=('reset_password',reset_password_key),
scheme=True)
if self.settings.mailer.send(
to=user.email,
subject=self.messages.reset_password_subject,
message=self.messages.reset_password % \
dict(key=reset_password_key)):
dict(key=reset_password_key,link=link)):
user.update_record(reset_password_key=reset_password_key)
return True
return False
@@ -2684,7 +2679,8 @@ class Auth(object):
self.user = auth.user
if self.settings.login_onaccept:
form = Storage(dict(vars=self.user))
self.settings.login_onaccept(form)
for callback in self.settings.login_onaccept:
callback(form)
log = self.messages.impersonate_log
self.log_event(log,dict(id=current_id, other_id=auth.user.id))
elif user_id in (0, '0') and self.is_impersonating():
@@ -3142,18 +3138,21 @@ class Auth(object):
elif form.record and fieldname in form.record:
new_record[fieldname]=form.record[fieldname]
if fields:
for key,value in fields.items():
new_record[key] = value
new_record.update(fields)
id = archive_table.insert(**new_record)
return id
def wiki(self,slug=None,env=None,manage_permissions=False,force_prefix=''):
def wiki(self,slug=None,env=None,manage_permissions=False,force_prefix='', resolve=True):
if not hasattr(self,'_wiki'):
self._wiki = Wiki(self,
manage_permissions=manage_permissions,
force_prefix=force_prefix,env=env)
else:
self._wiki.env.update(env or {})
return self._wiki.read(slug)['content'] if slug else self._wiki()
# if resolve is set to True, process request as wiki call
# resolve=False allows initial setup without wiki redirection
if resolve:
return self._wiki.read(slug)['content'] if slug else self._wiki()
class Crud(object):
@@ -3714,14 +3713,14 @@ def universal_caller(f, *a, **b):
# There might be pos_args left, that are sent as named_values. Gather them as well.
# If a argument already is populated with values we simply replaces them.
for arg_name in pos_args[len(arg_dict):]:
if b.has_key(arg_name):
if arg_name in b:
arg_dict[arg_name] = b[arg_name]
if len(arg_dict) >= len(pos_args):
# All the positional arguments is found. The function may now be called.
# However, we need to update the arg_dict with the values from the named arguments as well.
for arg_name in named_args:
if b.has_key(arg_name):
if arg_name in b:
arg_dict[arg_name] = b[arg_name]
return f(**arg_dict)
@@ -4136,7 +4135,7 @@ class Service(object):
prefix='pys',
documentation = documentation,
ns = True)
for method, (function, returns, args, doc) in procedures.items():
for method, (function, returns, args, doc) in procedures.iteritems():
dispatcher.register_function(method, function, returns, args, doc)
if request.env.request_method == 'POST':
# Process normal Soap Operation
@@ -4392,8 +4391,9 @@ class PluginManager(object):
self.__dict__.clear()
settings = self.__getattr__(plugin)
settings.installed = True
[settings.update({key:value}) for key,value in defaults.items() \
if not key in settings]
settings.update(
(k,v) for k,v in defaults.items() if not k in settings)
def __getattr__(self, key):
if not key in self.__dict__:
self.__dict__[key] = Storage()
@@ -4494,32 +4494,48 @@ class Wiki(object):
self.host = current.request.env.http_host
perms = self.manage_permissions = manage_permissions
db = auth.db
db.define_table(
'wiki_page',
Field('slug',
requires=[IS_SLUG(),IS_NOT_IN_DB(db,'wiki_page.slug')],
readable=False,writable=False),
Field('title',unique=True),
Field('body','text',notnull=True),
Field('tags','list:string'),
Field('can_read','list:string',writable=perms,readable=perms,
default=[Wiki.everybody]),
Field('can_edit','list:string',writable=perms,readable=perms,
default=[Wiki.everybody]),
Field('changelog'),
Field('html','text',readable=False,writable=False,compute=render),
auth.signature,format='%(title)s')
db.define_table(
'wiki_tag',
Field('name'),
Field('wiki_page',db.wiki_page),
auth.signature,format='%(name)s')
db.define_table(
'wiki_media',
Field('wiki_page',db.wiki_page),
Field('title',required=True),
Field('file','upload',required=True),
auth.signature,format='%(title)s')
table_definitions = {
'wiki_page':{
'args':[
Field('slug',
requires=[IS_SLUG(),
IS_NOT_IN_DB(db,'wiki_page.slug')],
readable=False,writable=False),
Field('title',unique=True),
Field('body','text',notnull=True),
Field('tags','list:string'),
Field('can_read','list:string',
writable=perms,
readable=perms,
default=[Wiki.everybody]),
Field('can_edit', 'list:string',
writable=perms,readable=perms,
default=[Wiki.everybody]),
Field('changelog'),
Field('html','text',compute=render,
readable=False, writable=False),
auth.signature],
'vars':{'format':'%(title)s'}},
'wiki_tag':{
'args':[
Field('name'),
Field('wiki_page','reference wiki_page'),
auth.signature],
'vars':{'format':'%(name)s'}},
'wiki_media':{
'args':[
Field('wiki_page','reference wiki_page'),
Field('title',required=True),
Field('file','upload',required=True),
auth.signature],
'vars':{'format':'%(title)s'}}
}
# define only non-existent tables
for key, value in table_definitions.iteritems():
if not key in db.tables():
db.define_table(key, *value['args'], **value['vars'])
def update_tags_insert(page,id,db=db):
for tag in page.tags or []:
tag = tag.strip().lower()