Merge github.com:web2py/web2py

This commit is contained in:
Michele Comitini
2012-09-30 22:01:09 +02:00
41 changed files with 1113 additions and 880 deletions

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.0.9 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION
echo 'Version 2.0.9 ('`date +%Y-%m-%d\ %H:%M:%S`') dev' > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
@@ -107,10 +107,6 @@ win:
cp applications/__init__.py ../web2py_win/web2py/applications
cd ../web2py_win; zip -r web2py_win.zip web2py
mv ../web2py_win/web2py_win.zip .
pip:
# create Web2py distribution for upload to Pypi
# after upload clean Web2py sources with rm -R ./dist
python setup.py sdist
run:
python2.5 web2py.py -a hello
commit:
@@ -129,3 +125,10 @@ tag:
hg tag -l '$(S)'
make commit S='$(S)'
make push
pip:
# create Web2py distribution for upload to Pypi
# after upload clean Web2py sources with rm -R ./dist
# http://guide.python-distribute.org/creation.html
python setup.py sdist
python setup.py register
python setup.py sdist upload

View File

@@ -1 +1 @@
Version 2.0.9 (2012-09-21 09:37:29) stable
Version 2.0.9 (2012-09-30 14:45:39) dev

View File

@@ -39,7 +39,7 @@ function web2py_ajax_init(target) {
function web2py_event_handlers() {
var doc = jQuery(document)
doc.on('click', '.flash', function(e){jQuery(this).fadeOut('slow'); e.preventDefault();});
doc.on('click', '.flash', function(e){var t=jQuery(this); if(t.css('top')=='0px') t.slideUp('slow'); else t.fadeOut(); e.preventDefault();});
doc.on('keyup', 'input.integer', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();});
doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();});
var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?";
@@ -55,7 +55,7 @@ function web2py_event_handlers() {
jQuery(function() {
var flash = jQuery('.flash');
flash.hide();
if(flash.html()) flash.slideDown();
if(flash.html()) flash.append('<span style="float:right;">&times;</span>').slideDown();
web2py_ajax_init(document);
web2py_event_handlers();
});
@@ -67,20 +67,20 @@ function web2py_trap_form(action,target) {
form.submit(function(e){
jQuery('.flash').hide().html('');
web2py_ajax_page('post',action,form.serialize(),target);
e.preventDefault();
e.preventDefault();
});
});
}
function web2py_trap_link(target) {
jQuery('#'+target+' a.w2p_trap').each(function(i){
var link=jQuery(this);
link.click(function(e) {
jQuery('.flash').hide().html('');
web2py_ajax_page('get',link.attr('href'),[],target);
e.preventDefault();
});
});
var link=jQuery(this);
link.click(function(e) {
jQuery('.flash').hide().html('');
web2py_ajax_page('get',link.attr('href'),[],target);
e.preventDefault();
});
});
}
function web2py_ajax_page(method, action, data, target) {
@@ -101,9 +101,9 @@ function web2py_ajax_page(method, action, data, target) {
web2py_trap_link(target);
web2py_ajax_init('#'+target);
if(command)
eval(decodeURIComponent(command));
eval(decodeURIComponent(command));
if(flash)
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
}
});
}
@@ -151,11 +151,11 @@ function web2py_component(action, target, timeout, times){
}
} else {
// run once (no timeout specified)
element.reload_counter = Infinity;
element.reload_counter = Infinity;
web2py_ajax_page('get', action, null, target);
} }); }
function web2py_comet(url,onmessage,onopen,onclose) {
function web2py_websocket(url,onmessage,onopen,onclose) {
if ("WebSocket" in window) {
var ws = new WebSocket(url);
ws.onopen = onopen?onopen:(function(){});
@@ -165,3 +165,38 @@ function web2py_comet(url,onmessage,onopen,onclose) {
} else return false; // not supported
}
function web2py_calc_entropy(mystring) {
//calculate a simple entropy for a given string
var csets = new Array(
'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/',
'0123456789abcdefghijklmnopqrstuvwxyz');
var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split('');
for (var i=0;i<mystringlist.length;i++) { // classify this character
var c = mystringlist[i], inset=5;
for(var j = 0; j<csets.length; j++)
if (csets[j].indexOf(c) != -1) {inset = j; break;}
//calculate effect of character on alphabet size
if(!(inset in seen)) {seen[inset] = 1;score += csets[inset].length;}
else if (!(c in other)) {score += 1;other[c] = 1;}
if (inset != lastset) {score += 1;lastset = inset;}
}
var entropy = mystring.length*Math.log(score)/0.6931471805599453;
return Math.round(entropy*100)/100
}
function web2py_validate_entropy(myfield, req_entropy) {
var validator = function () {
var v = (web2py_calc_entropy(myfield.val())||0)/req_entropy;
var r=0,g=0,b=0,rs=function(x){return Math.round(x*15).toString(16)};
if(v<=0.5) {r=1.0; g=2.0*v;}
else {r=(1.0-2.0*(Math.max(v,0)-0.5)); g=1.0;}
var color = '#'+rs(r)+rs(g)+rs(b);
myfield.css('background-color',color);
entropy_callback = myfield.data('entropy_callback');
if(entropy_callback) entropy_callback(v);
}
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
}

View File

@@ -39,7 +39,7 @@ function web2py_ajax_init(target) {
function web2py_event_handlers() {
var doc = jQuery(document)
doc.on('click', '.flash', function(e){jQuery(this).fadeOut('slow'); e.preventDefault();});
doc.on('click', '.flash', function(e){var t=jQuery(this); if(t.css('top')=='0px') t.slideUp('slow'); else t.fadeOut(); e.preventDefault();});
doc.on('keyup', 'input.integer', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();});
doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();});
var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?";
@@ -55,7 +55,7 @@ function web2py_event_handlers() {
jQuery(function() {
var flash = jQuery('.flash');
flash.hide();
if(flash.html()) flash.slideDown();
if(flash.html()) flash.append('<span style="float:right;">&times;</span>').slideDown();
web2py_ajax_init(document);
web2py_event_handlers();
});
@@ -67,20 +67,20 @@ function web2py_trap_form(action,target) {
form.submit(function(e){
jQuery('.flash').hide().html('');
web2py_ajax_page('post',action,form.serialize(),target);
e.preventDefault();
e.preventDefault();
});
});
}
function web2py_trap_link(target) {
jQuery('#'+target+' a.w2p_trap').each(function(i){
var link=jQuery(this);
link.click(function(e) {
jQuery('.flash').hide().html('');
web2py_ajax_page('get',link.attr('href'),[],target);
e.preventDefault();
});
});
var link=jQuery(this);
link.click(function(e) {
jQuery('.flash').hide().html('');
web2py_ajax_page('get',link.attr('href'),[],target);
e.preventDefault();
});
});
}
function web2py_ajax_page(method, action, data, target) {
@@ -101,9 +101,9 @@ function web2py_ajax_page(method, action, data, target) {
web2py_trap_link(target);
web2py_ajax_init('#'+target);
if(command)
eval(decodeURIComponent(command));
eval(decodeURIComponent(command));
if(flash)
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
}
});
}
@@ -151,11 +151,11 @@ function web2py_component(action, target, timeout, times){
}
} else {
// run once (no timeout specified)
element.reload_counter = Infinity;
element.reload_counter = Infinity;
web2py_ajax_page('get', action, null, target);
} }); }
function web2py_comet(url,onmessage,onopen,onclose) {
function web2py_websocket(url,onmessage,onopen,onclose) {
if ("WebSocket" in window) {
var ws = new WebSocket(url);
ws.onopen = onopen?onopen:(function(){});
@@ -165,3 +165,38 @@ function web2py_comet(url,onmessage,onopen,onclose) {
} else return false; // not supported
}
function web2py_calc_entropy(mystring) {
//calculate a simple entropy for a given string
var csets = new Array(
'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/',
'0123456789abcdefghijklmnopqrstuvwxyz');
var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split('');
for (var i=0;i<mystringlist.length;i++) { // classify this character
var c = mystringlist[i], inset=5;
for(var j = 0; j<csets.length; j++)
if (csets[j].indexOf(c) != -1) {inset = j; break;}
//calculate effect of character on alphabet size
if(!(inset in seen)) {seen[inset] = 1;score += csets[inset].length;}
else if (!(c in other)) {score += 1;other[c] = 1;}
if (inset != lastset) {score += 1;lastset = inset;}
}
var entropy = mystring.length*Math.log(score)/0.6931471805599453;
return Math.round(entropy*100)/100
}
function web2py_validate_entropy(myfield, req_entropy) {
var validator = function () {
var v = (web2py_calc_entropy(myfield.val())||0)/req_entropy;
var r=0,g=0,b=0,rs=function(x){return Math.round(x*15).toString(16)};
if(v<=0.5) {r=1.0; g=2.0*v;}
else {r=(1.0-2.0*(Math.max(v,0)-0.5)); g=1.0;}
var color = '#'+rs(r)+rs(g)+rs(b);
myfield.css('background-color',color);
entropy_callback = myfield.data('entropy_callback');
if(entropy_callback) entropy_callback(v);
}
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
}

View File

@@ -70,6 +70,7 @@
</li><li>Ian Reinhart Geiser (html helpers)
</li><li>Ionel Anton (Romanian translation)
</li><li>Jan Beilicke (markmin)
</li><li>Jeremy Dillworth
</li><li>Jonathan Benn (is_url validator and tests)
</li><li>Jonathan Lundell (multiple contributions)
</li><li>Josh Goldfoot (xaml/html sanitizer)
@@ -156,6 +157,7 @@
<li><a href="http://www.danga.com/memcached/">memcache</a> developed by Evan Martin</li>
<li><a href="http://jquery.com/">jQuery</a> developed by John Resig</li>
<li>A syntax highlighter inspired by the code of <a href="http://www.petersblog.org/node/763">Peter Wilkinson</a></li>
<li><a href="https://github.com/jtauber/pyuca">pyUCA</a> developed by <a href="http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/">James Tauber</a></li>
</ul>

View File

@@ -11,7 +11,7 @@
if not request.env.web2py_runtime_gae:
## if NOT running on Google App Engine use SQLite or other DB
db = DAL('sqlite://storage.sqlite')
db = DAL('sqlite://storage.sqlite')
else:
## connect to Google BigTable (optional 'google:datastore://namespace')
db = DAL('google:datastore')

View File

@@ -61,7 +61,7 @@ input[type=text],input[type=password],select{width:300px; margin-right:5px}
border-top:1px #DEDEDE solid;
}
.header {
// background:<fill here for header image>;
/* background:<fill here for header image>; */
}
@@ -182,7 +182,7 @@ div.error {
* will look better with the declarations below
* if needed to remove base.css consider keeping these following lines in some css file.
*/
// .web2py_table {border:1px solid #ccc}
/* .web2py_table {border:1px solid #ccc} */
.web2py_paginator {}
.web2py_grid {width:100%}
.web2py_grid table {width:100%}

View File

@@ -1,8 +1,8 @@
//=======================================================
// CUSTOM RULES
//=======================================================
/*=============================================================
CUSTOM RULES
==============================================================*/
body{height:auto;} // to avoid vertical scroll bar
body{height:auto;} /* to avoid vertical scroll bar */
div.flash.flash-center{left:25%;right:25%;}
div.flash.flash-top,div.flash.flash-top:hover{
position:relative;
@@ -36,18 +36,16 @@ div.flash.flash-top,div.flash.flash-top:hover{
font-size:20px;
font-weight:300;
}
// auth navbar - primitive style
/* auth navbar - primitive style */
.auth_navbar,.auth_navbar a{color:inherit;}
.ie-lte7 .auth_navbar,.auth_navbar a{color:expression(this.parentNode.currentStyle['color']); // ie7 doesn't support inherit
}
.auth_navbar a{white-space:nowrap;} // to avoid the nav split on more lines
.ie-lte7 .auth_navbar,.auth_navbar a{color:expression(this.parentNode.currentStyle['color']); /* ie7 doesn't support inherit */}
.auth_navbar a{white-space:nowrap;} /* to avoid the nav split on more lines */
.auth_navbar a:hover{color:white;text-decoration:none;}
ul#navbar>.auth_navbar{
display:inline-block;
padding:5px;
}
// form errors message box customization
/* form errors message box customization */
div.error_wrapper{margin-bottom:9px;}
div.error{
border-radius: 4px;
@@ -55,9 +53,8 @@ div.error{
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
}
// below rules are only for formstyle = bootstrap
// trying to make errors look like bootstrap ones
/* below rules are only for formstyle = bootstrap
trying to make errors look like bootstrap ones */
div.controls .error_wrapper{
display:inline-block;
margin-bottom:0;
@@ -70,46 +67,44 @@ div.controls .error{
border:none;
padding:0;
margin:0;
//display:inline; // uncommenting this, the animation effect is lost
//display:inline; /* uncommenting this, the animation effect is lost */
}
div.controls .inline-help{color:#3A87AD;}
div.controls .error_wrapper+.inline-help{margin-left:-99999px;}
// beautify brand
/* beautify brand */
.navbar-inverse .brand{color:#c6cecc;}
.navbar-inverse .brand b{display:inline-block;margin-top:-1px;}
.navbar-inverse .brand b>span{font-size:22px;color:white}
.navbar-inverse .brand:hover b>span{color:white}
// beautify web2py link in navbar
/* beautify web2py link in navbar */
span.highlighted{color:#d8d800;}
.open span.highlighted{color:#ffff00;}
//===========================================================
// OVERRIDING WEB2PY.CSS RULES
//===========================================================
/*=============================================================
OVERRIDING WEB2PY.CSS RULES
==============================================================*/
// reset to default
/* reset to default */
a{white-space:normal;}
li{margin-bottom:0;}
textarea,button{display:block;}
// reset ul padding
/*reset ul padding */
ul#navbar{padding:0;}
// label aligned to related input
/* label aligned to related input */
td.w2p_fl,td.w2p_fc {padding:0;}
#web2py_user_form td{vertical-align:middle;}
//==========================================================
// OVERRIDING BOOTSTRAP.CSS RULES
//==========================================================
/*=============================================================
OVERRIDING BOOTSTRAP.CSS RULES
==============================================================*/
// because web2py handles this via js
/* because web2py handles this via js */
.hidden{visibility:visible;}
// right folder for bootstrap black images/icons
/* right folder for bootstrap black images/icons */
[class^="icon-"],[class*=" icon-"]{
background-image:url("../images/glyphicons-halflings.png")
}
// right folder for bootstrap white images/icons
/* right folder for bootstrap white images/icons */
.icon-white,
.nav-tabs > .active > a > [class^="icon-"],
.nav-tabs > .active > a > [class*=" icon-"],
@@ -125,20 +120,20 @@ td.w2p_fl,td.w2p_fc {padding:0;}
.dropdown-menu > .active > a > [class*=" icon-"] {
background-image:url("../images/glyphicons-halflings-white.png");
}
// bootstrap has a label as input's wrapper while web2py has a div
/* bootstrap has a label as input's wrapper while web2py has a div */
div>input[type="radio"],div>input[type="checkbox"]{margin:0;}
// bootstrap has button instead of input
/* bootstrap has button instead of input */
input[type="button"], input[type="submit"]{margin-right:8px;}
//===========================================================
// SOLVING CONFLICTS BETWEEN WEB2PY.CSS AND BOOTSTRAP.CSS
//===========================================================
/*=============================================================
RULES FOR SOLVING CONFLICTS BETWEEN WEB2PY.CSS AND BOOTSTRAP.CSS
==============================================================*/
// when formstyle=table3cols
/*when formstyle=table3cols*/
tr#auth_user_remember__row>td.w2p_fw>div{padding-bottom:8px;}
td.w2p_fw div>label{vertical-align:middle;}
td.w2p_fc {padding-bottom:5px;}
// when formstyle=divs
/*when formstyle=divs*/
div#auth_user_remember__row{margin-top:4px;}
div#auth_user_remember__row>.w2p_fl{display:none;}
div#auth_user_remember__row>.w2p_fw{min-height:39px;}
@@ -151,8 +146,7 @@ div.w2p_fc{
padding-left:5px;
margin-top:-8px;
}
// when formstyle=ul
/*when formstyle=ul*/
form>ul{
list-style:none;
margin:0;
@@ -160,30 +154,28 @@ form>ul{
li#auth_user_remember__row{margin-top:4px;}
li#auth_user_remember__row>.w2p_fl{display:none;}
li#auth_user_remember__row>.w2p_fw{min-height:39px;}
// when formstyle=bootstrap
/*when formstyle=bootstrap*/
#auth_user_remember__row label.checkbox{display:block;}
span.inline-help{display:inline-block;}
input[type="text"].input-xlarge,input[type="password"].input-xlarge{width:270px;}
// when recaptcha is used
/*when recaptcha is used*/
#recaptcha{min-height:30px;display:inline-block;margin-bottom:0;line-height:30px;vertical-align:middle;}
td>#recaptcha{margin-bottom:6px;}
div>#recaptcha{margin-bottom:9px;}
//==========================================================
// OTHER RULES
//==========================================================
/*=============================================================
OTHER RULES
==============================================================*/
.navbar-inner{
position:relative; // unnecessary
position:relative; /*unnecessary ??*/
}
// fixed alignment in forms with list:string
/* Massimo Di Pierro fixed alignment in forms with list:string */
form table tr{margin-bottom:9px;}
td.w2p_fw ul{margin-left:0px;}
// web2py_console in grid and smartgrid
/* web2py_console in grid and smartgrid */
.hidden{visibility:visible;}
.web2py_console input{
display: inline-block;
margin-bottom: 0;
@@ -203,12 +195,12 @@ td.w2p_fw ul{margin-left:0px;}
margin:3px 0 0 2px;
}
.web2py_grid form table{width:auto;}
// auth_user_remember checkbox extrapadding in IE fix
/* auth_user_remember checkbox extrapadding in IE fix */
.ie-lte9 input#auth_user_remember.checkbox {padding-left:0;}
//===========================================================
// MEDIA QUERIES
//===========================================================
/*=============================================================
MEDIA QUERIES
==============================================================*/
@media only screen and (max-width:979px){
body{padding-top:0px;}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -21,8 +21,8 @@ jQuery(function(){
var title = escape(jQuery('title').text());
var twit = 'http://twitter.com/home?status='+title+'%20'+url;
var facebook = 'http://www.facebook.com/sharer.php?u='+url;
var buzz = 'https://plus.google.com/share?url='+url;
var tbar = '<div id="socialdrawer"><span>Share<br/></span><div id="sicons"><a href="'+twit+'" id="twit" title="Share on twitter"><img src="'+path+'/twitter.png" alt="Share on Twitter" width="32" height="32" /></a><a href="'+facebook+'" id="facebook" title="Share on Facebook"><img src="'+path+'/facebook.png" alt="Share on facebook" width="32" height="32" /></a><a href="'+buzz+'" id="buzz" title="Share on Buzz"><img src="'+path+'/google-buzz.png" alt="Share on Buzz" width="32" height="32" /></a></div></div>';
var gplus = 'https://plus.google.com/share?url='+url;
var tbar = '<div id="socialdrawer"><span>Share<br/></span><div id="sicons"><a href="'+twit+'" id="twit" title="Share on twitter"><img src="'+path+'/twitter.png" alt="Share on Twitter" width="32" height="32" /></a><a href="'+facebook+'" id="facebook" title="Share on Facebook"><img src="'+path+'/facebook.png" alt="Share on facebook" width="32" height="32" /></a><a href="'+gplus+'" id="gplus" title="Share on Google Plus"><img src="'+path+'/gplus-32.png" alt="Share on Google Plus" width="32" height="32" /></a></div></div>';
// Add the share tool bar.
jQuery('body').append(tbar);
var st = jQuery('#socialdrawer');

View File

@@ -67,20 +67,20 @@ function web2py_trap_form(action,target) {
form.submit(function(e){
jQuery('.flash').hide().html('');
web2py_ajax_page('post',action,form.serialize(),target);
e.preventDefault();
e.preventDefault();
});
});
}
function web2py_trap_link(target) {
jQuery('#'+target+' a.w2p_trap').each(function(i){
var link=jQuery(this);
link.click(function(e) {
jQuery('.flash').hide().html('');
web2py_ajax_page('get',link.attr('href'),[],target);
e.preventDefault();
});
});
var link=jQuery(this);
link.click(function(e) {
jQuery('.flash').hide().html('');
web2py_ajax_page('get',link.attr('href'),[],target);
e.preventDefault();
});
});
}
function web2py_ajax_page(method, action, data, target) {
@@ -101,9 +101,9 @@ function web2py_ajax_page(method, action, data, target) {
web2py_trap_link(target);
web2py_ajax_init('#'+target);
if(command)
eval(decodeURIComponent(command));
eval(decodeURIComponent(command));
if(flash)
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
}
});
}
@@ -151,11 +151,11 @@ function web2py_component(action, target, timeout, times){
}
} else {
// run once (no timeout specified)
element.reload_counter = Infinity;
element.reload_counter = Infinity;
web2py_ajax_page('get', action, null, target);
} }); }
function web2py_comet(url,onmessage,onopen,onclose) {
function web2py_websocket(url,onmessage,onopen,onclose) {
if ("WebSocket" in window) {
var ws = new WebSocket(url);
ws.onopen = onopen?onopen:(function(){});
@@ -165,3 +165,38 @@ function web2py_comet(url,onmessage,onopen,onclose) {
} else return false; // not supported
}
function web2py_calc_entropy(mystring) {
//calculate a simple entropy for a given string
var csets = new Array(
'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/',
'0123456789abcdefghijklmnopqrstuvwxyz');
var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split('');
for (var i=0;i<mystringlist.length;i++) { // classify this character
var c = mystringlist[i], inset=5;
for(var j = 0; j<csets.length; j++)
if (csets[j].indexOf(c) != -1) {inset = j; break;}
//calculate effect of character on alphabet size
if(!(inset in seen)) {seen[inset] = 1;score += csets[inset].length;}
else if (!(c in other)) {score += 1;other[c] = 1;}
if (inset != lastset) {score += 1;lastset = inset;}
}
var entropy = mystring.length*Math.log(score)/0.6931471805599453;
return Math.round(entropy*100)/100
}
function web2py_validate_entropy(myfield, req_entropy) {
var validator = function () {
var v = (web2py_calc_entropy(myfield.val())||0)/req_entropy;
var r=0,g=0,b=0,rs=function(x){return Math.round(x*15).toString(16)};
if(v<=0.5) {r=1.0; g=2.0*v;}
else {r=(1.0-2.0*(Math.max(v,0)-0.5)); g=1.0;}
var color = '#'+rs(r)+rs(g)+rs(b);
myfield.css('background-color',color);
entropy_callback = myfield.data('entropy_callback');
if(entropy_callback) entropy_callback(v);
}
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
}

View File

@@ -0,0 +1,28 @@
// this code improves bootstrap menus and adds dropdown support
jQuery(function(){
jQuery('.nav>li>a').each(function(){
if(jQuery(this).parent().find('ul').length)
jQuery(this).attr({'class':'dropdown-toggle','data-toggle':'dropdown'}).append('<b class="caret"></b>');
});
jQuery('.nav li li').each(function(){
if(jQuery(this).find('ul').length)
jQuery(this).addClass('dropdown-submenu');
});
function hoverMenu(){
var wid = document.documentElement.clientWidth; //faster than $(window).width() and cross browser
if (wid>=980){
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function(){
mi = jQuery(this);
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
});
};
}
hoverMenu(); // first page load
jQuery(window).resize(hoverMenu); // on resize event
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
// make all buttons bootstrap buttons
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
});

View File

@@ -15,92 +15,9 @@ pass
</div>
<script language="javascript"><!--
jQuery("#web2py_user_form input:visible:enabled:first").focus();
function calc_entropy(mystring) {
/*" calculate a simple entropy for a given string "*/
var lowerset = 'abcdefghijklmnopqrstuvwxyz';
var upperset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var numberset = '0123456789';
var sym1set = '!@#$%^&*()';
var sym2set = '~`-_=+[]{}\\|;:\'",.<>?/';
var otherset = '0123456789abcdefghijklmnopqrstuvwxyz' /*anything else*/
var alphabet = 0;
var other = {};
var seen = {};
var lastset = null;
var mystringlist = mystring.split('');
for (var i=0;i<mystringlist.length;i++) {
var c = mystringlist[i];
//console.log('we are at char', c);
/* classify this character*/
var inset = otherset;
while (1) {
/*lowerset*/
if (lowerset.indexOf(c) != -1) {
inset = lowerset;
break
};
/*upperset*/
if (upperset.indexOf(c) != -1) {
inset = upperset;
break
};
/*numberset*/
if (numberset.indexOf(c) != -1) {
inset = numberset;
break
};
/*sym1set*/
if (sym1set.indexOf(c) != -1) {
inset = sym1set;
break
};
/*sym2set*/
if (sym2set.indexOf(c) != -1) {
inset = sym2set;
break
} else {break};
}
//console.log('inset detected as', inset, seen);
/*calculate effect of character on alphabet size*/
if (!(inset in seen)) {
//console.log('credit for a new character set');
seen[inset] = 1;
alphabet += inset.length; /*credit for a new character set*/
}
else if (!(c in other)) {
//console.log('credit for unique characters');
alphabet += 1;
other[c] = 1;
}
if (inset != lastset) {
//console.log('credit for set transitions');
alphabet += 1; /*credit for set transitions*/
lastset = inset;
}
}
var entropy = mystring.length / 1.0 * Math.log(alphabet) / 0.6931471805599453; /*math.log(2)*/
return Math.round(entropy*100)/100
}
function validate_entropy(myfield, midrange) {
var k = 256.0/midrange;
var validator = function () {
var value = calc_entropy(myfield.val());
var color;
if(value<30) {
var c = Math.floor(value*k).toString(16);
color = '#ff'+((c.length==1)?'0':''+c)+'00';
} else {
var c = Math.floor(Math.max(0,255-(value-midrange)*k)).toString(16);
color = '#'+((c.length==1)?'0':'')+c+'ff00';
}
myfield.css('background-color',color);
}
myfield.on('keyup', validator);
myfield.on('keydown', validator);
}
validate_entropy(jQuery('#auth_user_password'),50);
//--></script>
{{if request.args(0)=='register':}}
web2py_validate_entropy(jQuery('#auth_user_password'),100);
{{elif request.args(0)=='change_password':}}
web2py_validate_entropy(jQuery('#no_table_new_password'),100);
{{pass}}
//--></script>

View File

@@ -39,7 +39,7 @@
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
<!-- include stylesheets -->
{{
{{
response.files.append(URL('static','css/web2py.css'))
response.files.append(URL('static','css/bootstrap.min.css'))
response.files.append(URL('static','css/bootstrap-responsive.min.css'))
@@ -71,11 +71,11 @@
<div class="navbar-inner">
<div class="container">
<!-- the next tag is necessary for bootstrap menus, do not remove -->
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
</button>
<a class="brand" href="http://www.web2py.com/"><b>web<span>2</span>py</b>&trade;&nbsp;</a>
<ul id="navbar" class="nav pull-right">{{='auth' in globals() and auth.navbar(mode="dropdown") or ''}}</ul>
<div class="nav-collapse">
@@ -146,32 +146,8 @@
<!-- The javascript =============================================
(Placed at the end of the document so the pages load faster) -->
<script>
// this code improves bootstrap menus and adds dropdown support
jQuery(function(){
jQuery('.nav>li>a').each(function(){
if(jQuery(this).parent().find('ul').length)
jQuery(this).attr({'class':'dropdown-toggle','data-toggle':'dropdown'}).append('<b class="caret"></b>');
});
jQuery('.nav li li').each(function(){
if(jQuery(this).find('ul').length)
jQuery(this).addClass('dropdown-submenu');
});
if(jQuery(document).width()>=980) {
jQuery('ul.nav a.dropdown-toggle').parent().hover(function() {
mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function() {
mi = jQuery(this);
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
});
}
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
// make all buttons bootstrap buttons
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
});
</script>
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
<script src="{{=URL('static','js/web2py_bootstrap.js')}}"></script>
<!--[if lt IE 7 ]>
<script src="{{=URL('static','js/dd_belatedpng.js')}}"></script>
<script> DD_belatedPNG.fix('img, .png_bg'); //fix any <img> or .png_bg background-images </script>

View File

@@ -2,9 +2,9 @@
# coding: utf8
"""
Dropbox Authentication for web2py
Developed by Massimo Di Pierro (2012)
Same License as Web2py License
Dropbox Authentication for web2py
Developed by Massimo Di Pierro (2012)
Same License as Web2py License
"""
# mind here session is dropbox session, not current.session
@@ -27,7 +27,7 @@ class DropboxAccount(object):
key="...",
secret="...",
access_type="...",
url = "http://localhost:8000/%s/default/user/login" % request.application)
login_url = "http://localhost:8000/%s/default/user/login" % request.application)
when logged in
client = auth.settings.login_form.client
"""
@@ -40,7 +40,7 @@ class DropboxAccount(object):
login_url = "",
on_login_failure=None,
):
self.request=request
self.key=key
self.secret=secret
@@ -53,39 +53,48 @@ class DropboxAccount(object):
def get_user(self):
request = self.request
token = current.session.dropbox_token
if not token:
return None
try:
self.sess.set_token(token[0],token[1])
except:
# invalid token, should never happen
if not current.session.dropbox_request_token:
return None
elif not current.session.dropbox_access_token:
request_token = current.session.dropbox_request_token
self.sess.set_request_token(request_token[0],request_token[1])
access_token = self.sess.obtain_access_token(self.sess.token)
current.session.dropbox_access_token = \
(access_token.key,access_token.secret)
else:
user = Storage()
self.client = client.DropboxClient(self.sess)
data = self.client.account_info()
display_name = data.get('display_name','').split(' ',1)
user = dict(email = data.get('email',None),
first_name = display_name[0],
last_name = display_name[-1],
registration_id = data.get('uid',None))
if not user['registration_id'] and self.on_login_failure:
redirect(self.on_login_failure)
return user
access_token = current.session.dropbox_access_token
self.sess.set_token(access_token[0],access_token[1])
user = Storage()
self.client = client.DropboxClient(self.sess)
data = self.client.account_info()
display_name = data.get('display_name','').split(' ',1)
user = dict(email = data.get('email',None),
first_name = display_name[0],
last_name = display_name[-1],
registration_id = data.get('uid',None))
if not user['registration_id'] and self.on_login_failure:
redirect(self.on_login_failure)
return user
def login_form(self):
token = self.sess.obtain_request_token()
current.session.dropbox_token = (token.key,token.secret)
dropbox_url = self.sess.build_authorize_url(token,self.login_url)
request_token = self.sess.obtain_request_token()
current.session.dropbox_request_token = \
(request_token.key,request_token.secret)
dropbox_url = self.sess.build_authorize_url(request_token,
self.login_url)
redirect(dropbox_url)
form = IFRAME(_src=dropbox_url,
_scrolling="no",
_frameborder="no",
_style="width:400px;height:240px;")
return form
def logout_url(self, next = "/"):
current.session.dropbox_token=None
current.session.dropbox_request_token=None
current.session.auth=None
redirect('https://www.dropbox.com/logout')
return next

View File

@@ -26,8 +26,10 @@ class MemcacheClientObj(Client):
def __init__(self, request, servers, debug=0, pickleProtocol=0,
pickler=pickle.Pickler, unpickler=pickle.Unpickler,
pload=None, pid=None):
pload=None, pid=None,
default_time_expire = DEFAULT_TIME_EXPIRE):
self.request=request
self.default_time_expire = time_expire
if request:
app = request.application
else:
@@ -43,8 +45,9 @@ class MemcacheClientObj(Client):
else:
self.storage = self.meta_storage[app]
def __call__(self, key, f,
time_expire=DEFAULT_TIME_EXPIRE):
def __call__(self, key, f, time_expire = 'default'):
if time_expire == 'default':
time_expire = self.default_time_expire
if time_expire == None:
time_expire = self.max_time_expire
# this must be commented because get and set are redefined
@@ -70,8 +73,10 @@ class MemcacheClientObj(Client):
self.set(key, (now,value), self.max_time_expire)
return value
def increment(self, key, value=1, time_expire=DEFAULT_TIME_EXPIRE):
def increment(self, key, value=1, time_expire='default'):
""" time_expire is ignored """
if time_expire == 'default':
time_expire = self.default_time_expire
newKey = self.__keyFormat__(key)
obj = Client.get(self, newKey)
if obj:
@@ -86,7 +91,9 @@ class MemcacheClientObj(Client):
Client.set(self, newKey, value, self.max_time_expire)
return value
def set(self, key, value, time_expire=DEFAULT_TIME_EXPIRE):
def set(self, key, value, time_expire='default'):
if time_expire == 'default':
time_expire = self.default_time_expire
newKey = self.__keyFormat__(key)
return Client.set(self, newKey, value, time_expire)

View File

@@ -12,6 +12,7 @@ import cssmin
import jsmin
import os
import hashlib
import re
def read_binary_file(filename):
f = open(filename,'rb')
@@ -25,7 +26,8 @@ def write_binary_file(filename,data):
f.close()
def fix_links(css,static_path):
return css.replace('../',static_path+'../')
return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css)
def minify(files, path_info, folder, optimize_css, optimize_js,
ignore_concat = [],
@@ -60,16 +62,18 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
processed = []
for k,filename in enumerate(files):
if not filename.startswith('/') or \
any(filename.endswith(x) for x in ignore_concat):
any(filename.endswith(x) \
for x in ignore_concat):
new_files.append(filename)
continue
abs_filename = os.path.join(folder,'static',
filename[len(static_path)+1:])
abs_filename = os.path.join(
folder,'static', filename[len(static_path)+1:])
if filename.lower().endswith('.css'):
processed.append(filename)
spath_info, sfilename = path_info.split('/'), filename.split('/')
spath_info, sfilename = \
path_info.split('/'), filename.split('/')
u = 0
for i,a in enumerate(sfilename):
try:
@@ -80,7 +84,7 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
pass
if concat_css:
contents = read_binary_file(abs_filename)
replacement = '../'*len(spath_info[u:]) + '/'.join(sfilename[u:-1]) + '/'
replacement = '/'.join(spath_info[:u]) + '/'
contents = fix_links(contents, replacement)
if minify_css:
css.append(cssmin.cssmin(contents))
@@ -91,9 +95,12 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
elif filename.lower().endswith('.js'):
processed.append(filename)
if concat_js:
contents = read_binary_file(abs_filename)
if minify_js and not filename.endswith('.min.js') and \
not any(filename.endswith(x) for x in ignore_minify):
contents = read_binary_file(abs_filename)
if minify_js and \
not filename.endswith('.min.js') and \
not any(filename.endswith(x) \
for x in ignore_minify):
js.append(jsmin.jsmin(contents))
else:
js.append(contents)
@@ -104,12 +111,15 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
css = '\n\n'.join(contents for contents in css)
if not inline_css:
temppath = os.path.join(folder,'static',temp)
if not os.path.exists(temppath): os.mkdir(temppath)
if not os.path.exists(temppath):
os.mkdir(temppath)
dest = "compressed_%s.css" % dest_key
tempfile = os.path.join(temppath, dest)
write_binary_file(tempfile,css)
css = path_info+'/%s' % dest
new_files.append(css)
new_files.append(css)
else:
new_files.append(('css:inline',css))
else:
new_files += css
if js and concat_js:
@@ -118,7 +128,8 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
js = ('js:inline',js)
else:
temppath = os.path.join(folder,'static',temp)
if not os.path.exists(temppath): os.mkdir(temppath)
if not os.path.exists(temppath):
os.mkdir(temppath)
dest = "compressed_%s.js" % dest_key
tempfile = os.path.join(folder,'static',temp,dest)
write_binary_file(tempfile,js)

View File

@@ -0,0 +1,19 @@
# Copyright (c) 2006-2012 James Tauber and contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

View File

@@ -0,0 +1,43 @@
# pyuca: Python Unicode Collation Algorithm implementation
(http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/)
This is my preliminary attempt at a Python implementation of the
[Unicode Collation Algorithm (UCA)](http://unicode.org/reports/tr10/).
I originally posted it to my blog in 2006 but it seems to get enough
usage it really belongs here (and in PyPI).
What do you use it for? In short, sorting non-English strings properly.
The core of the algorithm involves multi-level comparison. For example,
``café`` comes before ``caff`` because at the primary level, the accent
is ignored and the first word is treated as if it were ``cafe``.
The secondary level (which considers accents) only applies then to words
that are equivalent at the primary level.
The Unicode Collation Algorithm and pyuca also support contraction and
expansion. **Contraction** is where multiple letters are treated as a
single unit. In Spanish, ``ch`` is treated as a letter coming between
``c`` and ``d`` so that, for example, words beginning ``ch`` should
sort after all other words beginnings with ``c``. **Expansion** is where
a single letter is treated as though it were multiple letters. In German,
``ä`` is sorted as if it were ``ae``, i.e. after ``ad`` but before ``af``.
## Here is how to use the ``pyuca`` module:
``
git clone https://github.com/jtauber/pyuca.git
cd pyuca
pip install pyuca
``
**Usage example:**
``
from pyuca import Collator
c = Collator("allkeys.txt")
sorted_words = sorted(words, key=c.sort_key)
``
``allkeys.txt`` (1 MB) is available at
http://www.unicode.org/Public/UCA/latest/allkeys.txt

View File

@@ -1,5 +1,10 @@
import os
import pyuca
unicode_collator = pyuca.Collator(os.path.join(os.path.dirname(__file__), 'allkeys.txt'))
unicode_collator = None
def set_unicode_collator(file):
global unicode_collator
unicode_collator = pyuca.Collator(file)
set_unicode_collator(os.path.join(os.path.dirname(__file__), 'allkeys.txt'))

View File

@@ -1,21 +1,21 @@
# pyuca - Unicode Collation Algorithm
# Version: 2006-02-13
# Version: 2012-06-21
#
# James Tauber
# http://jtauber.com/
# Copyright (c) 2006 James Tauber
#
# Copyright (c) 2006-2012 James Tauber and contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -28,7 +28,6 @@
"""
Preliminary implementation of the Unicode Collation Algorithm.
This only implements the simple parts of the algorithm but I have successfully
tested it using the Default Unicode Collation Element Table (DUCET) to collate
Ancient Greek correctly.
@@ -48,31 +47,39 @@ but you can always subset this for just the characters you are dealing with.
"""
class Trie:
class Node:
def __init__(self):
self.root = [None, {}]
self.value = None
self.children = {}
class Trie:
def __init__(self):
self.root = Node()
def add(self, key, value):
curr_node = self.root
for part in key:
curr_node = curr_node[1].setdefault(part, [None, {}])
curr_node[0] = value
curr_node = curr_node.children.setdefault(part, Node())
curr_node.value = value
def find_prefix(self, key):
curr_node = self.root
remainder = key
for part in key:
if part not in curr_node[1]:
if part not in curr_node.children:
break
curr_node = curr_node[1][part]
curr_node = curr_node.children[part]
remainder = remainder[1:]
return (curr_node[0], remainder)
return (curr_node.value, remainder)
class Collator:
def __init__(self, filename):
self.table = Trie()
self.load(filename)
@@ -85,7 +92,7 @@ class Collator:
line = line[:line.find("#")] + "\n"
line = line[:line.find("%")] + "\n"
line = line.strip()
if line.startswith("@"):
pass
else:
@@ -96,32 +103,36 @@ class Collator:
while True:
begin = x.find("[")
if begin == -1:
break
break
end = x[begin:].find("]")
collElement = x[begin:begin+end+1]
x = x[begin + 1:]
alt = collElement[1]
chars = collElement[2:-1].split(".")
collElements.append((alt, chars))
integer_points = [int(ch, 16) for ch in charList]
self.table.add(integer_points, collElements)
def sort_key(self, string):
collation_elements = []
lookup_key = [ord(ch) for ch in string]
while lookup_key:
value, lookup_key = self.table.find_prefix(lookup_key)
if not value:
# @@@
raise ValueError, map(hex, lookup_key)
# Calculate implicit weighting for CJK Ideographs
# contributed by David Schneider 2009-07-27
# http://www.unicode.org/reports/tr10/#Implicit_Weights
value = []
value.append((".", ["%X" % (0xFB40 + (lookup_key[0] >> 15)), "0020", "0002", "0001"]))
value.append((".", ["%X" % ((lookup_key[0] & 0x7FFF) | 0x8000), "0000", "0000", "0000"]))
lookup_key = lookup_key[1:]
collation_elements.extend(value)
sort_key = []
for level in range(4):
if level:
sort_key.append(0) # level separator
@@ -129,5 +140,5 @@ class Collator:
ce_l = int(element[1][level], 16)
if ce_l:
sort_key.append(ce_l)
return tuple(sort_key)

View File

@@ -12,18 +12,18 @@ Attention: Requires Chrome or Safari. For IE of Firefox you need https://github.
2) start this app:
python gluon/contrib/comet_messaging.py -k mykey -p 8888
python gluon/contrib/websocket_messaging.py -k mykey -p 8888
3) from any web2py app you can post messages with
from gluon.contrib.comet_messaging import comet_send
comet_send('http://127.0.0.1:8888','Hello World','mykey','mygroup')
from gluon.contrib.websocket_messaging import websocket_send
websocket_send('http://127.0.0.1:8888','Hello World','mykey','mygroup')
4) from any template you can receive them with
<script>
$(document).ready(function(){
if(!web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',function(e){alert(e.data)}))
if(!web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup',function(e){alert(e.data)}))
alert("html5 websocket not supported by your browser, try Google Chrome");
});
</script>
@@ -34,14 +34,14 @@ Or if you want to send json messages and store evaluated json in a var called da
<script>
$(document).ready(function(){
var data;
web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',function(e){data=eval('('+e.data+')')});
web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup',function(e){data=eval('('+e.data+')')});
});
</script>
- All communications between web2py and comet_messaging will be digitally signed with hmac.
- All validation is handled on the web2py side and there is no need to modify comet_messaging.py
- Multiple web2py instances can talk with one or more comet_messaging servers.
- "ws://127.0.0.1:8888/realtime/" must be contain the IP of the comet_messaging server.
- All communications between web2py and websocket_messaging will be digitally signed with hmac.
- All validation is handled on the web2py side and there is no need to modify websocket_messaging.py
- Multiple web2py instances can talk with one or more websocket_messaging servers.
- "ws://127.0.0.1:8888/realtime/" must be contain the IP of the websocket_messaging server.
- Via group='mygroup' name you can support multiple groups of clients (think of many chat-rooms)
Here is a complete sample web2py action:
@@ -51,7 +51,7 @@ Here is a complete sample web2py action:
script=SCRIPT('''
jQuery(document).ready(function(){
var callback=function(e){alert(e.data)};
if(!web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',callback))
if(!web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup',callback))
alert("html5 websocket not supported by your browser, try Google Chrome");
});
''')
@@ -60,8 +60,8 @@ Here is a complete sample web2py action:
def ajax_form():
form=SQLFORM.factory(Field('message'))
if form.accepts(request,session):
from gluon.contrib.comet_messaging import comet_send
comet_send('http://127.0.0.1:8888',form.vars.message,'mykey','mygroup')
from gluon.contrib.websocket_messaging import websocket_send
websocket_send('http://127.0.0.1:8888',form.vars.message,'mykey','mygroup')
return form
Acknowledgements:
@@ -83,7 +83,7 @@ listeners = {}
names = {}
tokens = {}
def comet_send(url,message,hmac_key=None,group='default'):
def websocket_send(url,message,hmac_key=None,group='default'):
sig = hmac_key and hmac.new(hmac_key,message).hexdigest() or ''
params = urllib.urlencode({'message': message, 'signature': sig, 'group':group})
f = urllib.urlopen(url, params)

File diff suppressed because it is too large Load Diff

View File

@@ -229,7 +229,7 @@ class Response(Storage):
for k,v in (self.meta or {}).iteritems())
self.write(s,escape=False)
def include_files(self):
def include_files(self, extensions=None):
"""
Caching method for writing out files.
@@ -240,11 +240,22 @@ class Response(Storage):
from gluon import URL
files = []
has_js = has_css = False
for item in self.files:
if not item in files: files.append(item)
if have_minify and (self.optimize_css or self.optimize_js):
if extensions and not item.split('.')[-1] in extensions:
continue
if item in files:
continue
if item.endswith('.js'):
has_js = True
if item.endswith('.css'):
has_css = True
files.append(item)
if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
# cache for 5 minutes by default
key = hashlib.md5(repr(files)).hexdigest()
cache = self.cache_includes or (current.cache.ram, 60*5)
def call_minify(files=files):
return minify.minify(files,
@@ -263,6 +274,8 @@ class Response(Storage):
for item in files:
if isinstance(item,str):
f = item.lower().split('?')[0]
if self.static_version:
item = item.replace('/static/', '/static/_%s/' % self.static_version, 1)
if f.endswith('.css'): s += css_template % item
elif f.endswith('.js'): s += js_template % item
elif f.endswith('.coffee'): s += coffee_template % item
@@ -406,11 +419,11 @@ class Response(Storage):
BUTTON = TAG.button
admin = URL("admin","default","design",
args=current.request.application)
from gluon.dal import thread
if hasattr(thread,'instances'):
from gluon.dal import THREAD_LOCAL
if hasattr(THREAD_LOCAL,'instances'):
dbstats = [TABLE(*[TR(PRE(row[0]),'%.2fms' % (row[1]*1000)) \
for row in i.db._timings]) \
for i in thread.instances]
for i in THREAD_LOCAL.instances]
dbtables = dict([(regex_nopasswd.sub('******',i.uri),
{'defined':
sorted(list(set(i.db.tables) -
@@ -418,7 +431,7 @@ class Response(Storage):
'[no defined tables]',
'lazy': sorted(i.db._LAZY_TABLES.keys()) or
'[no lazy tables]'})
for i in thread.instances])
for i in THREAD_LOCAL.instances])
else:
dbstats = [] # if no db or on GAE
dbtables = {}
@@ -703,8 +716,3 @@ class Session(Storage):
del response.session_file
except:
pass

View File

@@ -1943,6 +1943,7 @@ class FORM(DIV):
# check formname and formkey
status = True
changed = False
request_vars = self.request_vars
if session:
formkey = session.get('_formkey[%s]' % formname, None)
@@ -1955,18 +1956,23 @@ class FORM(DIV):
# check if editing a record that has been modified by the server
if hasattr(self,'record_hash') and self.record_hash != formkey:
status = False
self.record_changed = True
self.record_changed = changed = True
status = self._traverse(status,hideerror)
status = self.assert_status(status, request_vars)
if onvalidation:
if isinstance(onvalidation, dict):
onsuccess = onvalidation.get('onsuccess', None)
onfailure = onvalidation.get('onfailure', None)
onchange = onvalidation.get('onchange', None)
if onsuccess and status:
onsuccess(self)
if onfailure and request_vars and not status:
onfailure(self)
status = len(self.errors) == 0
if changed:
if onchange and self.record_changed and \
self.detect_record_change:
onchange(self)
elif status:
if isinstance(onvalidation, (list, tuple)):
[f(self) for f in onvalidation]
@@ -2035,8 +2041,13 @@ class FORM(DIV):
onfailure = 'flash' - will show message_onfailure in response.flash
None - will do nothing
can be a function (lambda form: pass)
onchange = 'flash' - will show message_onchange in response.flash
None - will do nothing
can be a function (lambda form: pass)
message_onsuccess
message_onfailure
message_onchange
next = where to redirect in case of success
any other kwargs will be passed for form.accepts(...)
"""
@@ -2047,13 +2058,17 @@ class FORM(DIV):
onsuccess = kwargs.get('onsuccess','flash')
onfailure = kwargs.get('onfailure','flash')
onchange = kwargs.get('onchange', 'flash')
message_onsuccess = kwargs.get('message_onsuccess',
current.T("Success!"))
message_onfailure = kwargs.get('message_onfailure',
current.T("Errors in form, please check it out."))
message_onchange = kwargs.get('message_onchange',
current.T("Form consecutive submissions not allowed. " +
"Try re-submitting or refreshing the form page."))
next = kwargs.get('next',None)
for key in ('message_onsuccess','message_onfailure','onsuccess',
'onfailure','next'):
'onfailure','next', 'message_onchange', 'onchange'):
if key in kwargs:
del kwargs[key]
@@ -2080,6 +2095,13 @@ class FORM(DIV):
elif callable(onfailure):
onfailure(self)
return False
elif hasattr(self, "record_changed"):
if self.record_changed and self.detect_record_change:
if onchange == 'flash':
current.response.flash = message_onchange
elif callable(onchange):
onchange(self)
return False
def process(self, **kwargs):
"""

View File

@@ -42,6 +42,7 @@ defined_status = {
415: 'UNSUPPORTED MEDIA TYPE',
416: 'REQUESTED RANGE NOT SATISFIABLE',
417: 'EXPECTATION FAILED',
422: 'UNPROCESSABLE ENTITY',
500: 'INTERNAL SERVER ERROR',
501: 'NOT IMPLEMENTED',
502: 'BAD GATEWAY',

View File

@@ -132,7 +132,7 @@ def get_client(env):
"""
g = regex_client.search(env.get('http_x_forwarded_for', ''))
if g:
client = g.group()
client = (g.group() or '').split(',')[0]
else:
g = regex_client.search(env.get('remote_addr', ''))
if g:
@@ -153,7 +153,10 @@ def copystream_progress(request, chunk_size= 10**5):
if not env.content_length:
return cStringIO.StringIO()
source = env.wsgi_input
size = int(env.content_length)
try:
size = int(env.content_length)
except ValueError:
raise HTTP(400,"Invalid Content-Length header")
dest = tempfile.TemporaryFile()
if not 'X-Progress-ID' in request.vars:
copystream(source, dest, size, chunk_size)
@@ -325,10 +328,12 @@ def parse_get_post_vars(request, environ):
dpk = dpost[key]
# if en element is not a file replace it with its value else leave it alone
if isinstance(dpk, list):
if not dpk[0].filename:
value = [x.value for x in dpk]
else:
value = [x for x in dpk]
value = []
for _dpk in dpk:
if not _dpk.filename:
value.append(_dpk.value)
else:
value.append(_dpk)
elif not dpk.filename:
value = dpk.value
else:
@@ -400,7 +405,7 @@ def wsgibase(environ, responder):
# ##################################################
fixup_missing_path_info(environ)
(static_file, environ) = url_in(request, environ)
(static_file, version, environ) = url_in(request, environ)
response.status = env.web2py_status_code or response.status
if static_file:
@@ -408,24 +413,30 @@ def wsgibase(environ, responder):
'attachment'):
response.headers['Content-Disposition'] \
= 'attachment'
if version:
response.headers['Cache-Control'] = 'max-age=315360000'
response.headers['Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT'
response.stream(static_file, request=request)
# ##################################################
# fill in request items
# ##################################################
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.gethostname())
except TypeError:
pass
try:
local_hosts.append(
socket.gethostbyname(http_host))
except socket.gaierror:
socket.gethostbyname(http_host))
except (socket.gaierror,TypeError):
pass
client = get_client(env)
client = get_client(env)
x_req_with = str(env.http_x_requested_with).lower()
request.update(

View File

@@ -140,8 +140,9 @@ class LockedFile(object):
self.file.close()
self.file = None
def __del__(self):
self.close()
if not self.file is None:
self.close()
def read_locked(filename):
fp = LockedFile(filename, 'r')
data = fp.read()

View File

@@ -39,6 +39,7 @@ regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*')
regex_anything = re.compile(r'(?<!\\)\$anything')
regex_redirect = re.compile(r'(\d+)->(.*)')
regex_full_url = re.compile(r'^(?P<scheme>http|https|HTTP|HTTPS)\://(?P<host>[^/]*)(?P<uri>.*)')
regex_version = re.compile(r'^(_[\d]+\.[\d]+\.[\d]+)$')
def _router_default():
"return new copy of default base router"
@@ -107,16 +108,16 @@ def log_rewrite(string):
logger.debug(string)
ROUTER_KEYS = set(
('default_application', 'applications',
('default_application', 'applications',
'default_controller', 'controllers',
'default_function', 'functions',
'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',
('applications', 'default_application',
'domains', 'path_prefix'))
# The external interface to rewrite consists of:
@@ -140,8 +141,8 @@ def fixup_missing_path_info(environ):
path_info = eget('PATH_INFO')
request_uri = eget('REQUEST_URI')
if not path_info and request_uri:
# for fcgi, get path_info and
# query_string from request_uri
# for fcgi, get path_info and
# query_string from request_uri
items = request_uri.split('?')
path_info = environ['PATH_INFO'] = items[0]
environ['QUERY_STRING'] = items[1] if len(items) > 1 else ''
@@ -154,15 +155,15 @@ def fixup_missing_path_info(environ):
if not eget('HTTP_HOST'):
environ['HTTP_HOST'] = \
'%s:%s' % (eget('SERVER_NAME'),eget('SERVER_PORT'))
def url_in(request, environ):
"parse and rewrite incoming URL"
if routers:
return map_url_in(request, environ)
return regex_url_in(request, environ)
def url_out(request, env, application, controller, function,
def url_out(request, env, application, controller, function,
args, other, scheme, host, port):
"assemble and rewrite outgoing URL"
if routers:
@@ -555,7 +556,7 @@ 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,
path = regex_uri(e, thread.routes.routes_in,
"routes_in", e['PATH_INFO'])
rmatch = regex_redirect.match(path)
if rmatch:
@@ -587,10 +588,11 @@ regex_space = re.compile('(\+|\s|%20)+')
# apps in routes_apps_raw must parse raw_args into args
regex_static = re.compile(r'''
(^ # static pages
/(?P<b> \w+) # b=app
/static # /b/static
/(?P<x> (\w[\-\=\./]?)* ) # x=file
(^ # static pages
/(?P<b> \w+) # b=app
/static # /b/static
(/(?P<v>_[\d]+\.[\d]+\.[\d]+))? # version ?
/(?P<x> (\w[\-\=\./]?)* ) # x=file
$)
''', re.X)
@@ -639,7 +641,7 @@ def regex_url_in(request, environ):
if thread.routes.routes_in:
environ = regex_filter_in(environ)
request.env.update((sluggify(k),v) for k,v in environ.iteritems())
path = request.env.path_info.replace('\\', '/')
@@ -650,10 +652,11 @@ def regex_url_in(request, environ):
match = regex_static.match(regex_space.sub('_', path))
if match and match.group('x'):
version = match.group('v')
static_file = pjoin(request.env.applications_parent,
'applications', match.group('b'),
'static', match.group('x'))
return (static_file, environ)
return (static_file, version, environ)
# ##################################################
# parse application, controller and function
@@ -692,7 +695,7 @@ def regex_url_in(request, environ):
raise HTTP(400,
thread.routes.error_message % 'invalid request',
web2py_error='invalid path (args)')
return (None, environ)
return (None, None, environ)
def regex_filter_out(url, e=None):
@@ -723,9 +726,9 @@ def regex_filter_out(url, e=None):
return url
def filter_url(url, method='get', remote='0.0.0.0',
def filter_url(url, method='get', remote='0.0.0.0',
out=False, app=False, lang=None,
domain=(None,None), env=False, scheme=None,
domain=(None,None), env=False, scheme=None,
host=None, port=None):
"""
doctest/unittest interface to regex_filter_in() and regex_filter_out()
@@ -794,7 +797,7 @@ def filter_url(url, method='get', remote='0.0.0.0',
# rewrite inbound URL
#
(static, e) = url_in(request, e)
(static, version, e) = url_in(request, e)
if static:
return static
result = "/%s/%s/%s" % (request.application, request.controller, request.function)
@@ -957,14 +960,15 @@ class MapUrlIn(object):
a root-static file is one whose incoming URL expects it to be at the root,
typically robots.txt & favicon.ico
'''
if len(self.args) == 1 and self.arg0 in self.router.root_static:
self.controller = self.request.controller = 'static'
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)
return root_static_file
return None
return root_static_file, None
return None, None
def map_language(self):
"handle language (no hyphen mapping)"
@@ -999,8 +1003,12 @@ class MapUrlIn(object):
file_match but no hyphen mapping
'''
if self.controller != 'static':
return None
file = '/'.join(self.args)
return None, None
version = regex_version.match(self.args(0))
if self.args and version:
file = '/'.join(self.args[1:])
else:
file = '/'.join(self.args)
if len(self.args) == 0:
bad_static = True # require a file name
elif '/' in self.file_match:
@@ -1031,7 +1039,7 @@ class MapUrlIn(object):
'static', file)
self.extension = None
log_rewrite("route: static=%s" % static_file)
return static_file
return static_file, version
def map_function(self):
"handle function.extension"
@@ -1075,7 +1083,7 @@ class MapUrlIn(object):
""
self.request.env.update(
(sluggify(k),v) for k,v in self.env.iteritems())
def update_request(self):
'''
update request from self
@@ -1099,7 +1107,7 @@ class MapUrlIn(object):
if self.language:
uri = '/%s%s' % (self.language, uri)
uri = '/%s%s%s%s' % (
app,
app,
uri,
urllib.quote('/'+'/'.join(str(x) for x in self.args)) if self.args else '',
('?' + self.query) if self.query else '')
@@ -1307,10 +1315,10 @@ def map_url_in(request, env, app=False):
if app:
return map.application
root_static_file = map.map_root_static() # handle root-static files
root_static_file, version = map.map_root_static() # handle root-static files
if root_static_file:
map.update_request()
return (root_static_file, map.env)
return (root_static_file, version, map.env)
# handle mapping of lang/static to static/lang in externally-rewritten URLs
# in case we have to handle them ourselves
if map.languages and map.map_static is False and map.arg0 == 'static' and map.args(1) in map.languages:
@@ -1319,14 +1327,14 @@ def map_url_in(request, env, app=False):
else:
map.map_language()
map.map_controller()
static_file = map.map_static()
static_file, version = map.map_static()
if static_file:
map.update_request()
return (static_file, map.env)
return (static_file, version, map.env)
map.map_function()
map.validate_args()
map.update_request()
return (None, map.env)
return (None, None, map.env)
def map_url_out(request, env, application, controller,
function, args, other, scheme, host, port):
@@ -1364,10 +1372,3 @@ def get_effective_router(appname):
if not routers or appname not in routers:
return None
return Storage(routers[appname]) # return a copy

View File

@@ -1457,33 +1457,29 @@ class Worker(Thread):
def read_headers(self, sock_file):
try:
headers = dict()
l = sock_file.readline()
lname = None
lval = None
lname = lval = ''
while True:
line = sock_file.readline()
if PY3K:
try:
l = str(l, 'ISO-8859-1')
line = str(line, 'ISO-8859-1')
except UnicodeDecodeError:
self.err_log.warning('Client sent invalid header: ' + repr(l))
if l == '\r\n':
self.err_log.warning('Client sent invalid header: ' + repr(line))
if line == '\r\n':
if lname: headers[str(lname)] = str(lval)
break
if l[0] in ' \t' and lname:
elif line.strip() == '' or '\0' in line:
raise BadRequest("Empty line in hader")
elif line[0] in ' \t' and lname:
# Some headers take more than one line
lval += ',' + l.strip()
else:
# HTTP header values are latin-1 encoded
l = l.split(':', 1)
lval += ' ' + line.strip()
elif ':' in line:
if lname: headers[str(lname)] = str(lval)
lname, lval = line.split(':', 1)
# HTTP header names are us-ascii encoded
lname = l[0].strip().upper().replace('-', '_')
lval = l[-1].strip()
headers[str(lname)] = str(lval)
l = sock_file.readline()
lname = lname.strip().upper().replace('-', '_')
# HTTP header values are latin-1 encoded
lval = lval.strip()
except socket.timeout:
raise SocketTimeout("Socket timed out before request.")

View File

@@ -28,7 +28,7 @@ from dal import DAL, Field, Table, Row, CALLABLETYPES, smart_query, \
from storage import Storage
from utils import md5_hash
from validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \
IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE
IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE, IS_STRONG
import datetime
import urllib
@@ -174,8 +174,7 @@ class TextWidget(FormWidget):
"""
default = dict(value = value)
attr = cls._attributes(field, default,
**attributes)
attr = cls._attributes(field, default,**attributes)
return TEXTAREA(**attr)
@@ -380,12 +379,11 @@ class CheckboxesWidget(OptionsWidget):
requires = field.requires
if not isinstance(requires, (list, tuple)):
requires = [requires]
if requires:
if hasattr(requires[0], 'options'):
options = requires[0].options()
else:
raise SyntaxError, 'widget cannot determine options of %s' \
% field
if requires and hasattr(requires[0], 'options'):
options = requires[0].options()
else:
raise SyntaxError, 'widget cannot determine options of %s' \
% field
options = [(k, v) for k, v in options if k != '']
opts = []
@@ -442,14 +440,23 @@ class PasswordWidget(FormWidget):
see also: :meth:`FormWidget.widget`
"""
# detect if attached a IS_STRONG with entropy
default=dict(
_type='password',
_value=(value and cls.DEFAULT_PASSWORD_DISPLAY) or '',
)
attr = cls._attributes(field, default, **attributes)
output = CAT(INPUT(**attr))
return INPUT(**attr)
# deal with entropy check!
requires = field.requires
if not isinstance(requires,(list,tuple)): requires = [requires]
is_strong = [r for r in requires if isinstance(r, IS_STRONG)]
if is_strong:
output.append(SCRIPT("web2py_validate_entropy(jQuery('#%s'),%s);" \
% (attr['_id'],is_strong[0].entropy)))
# end entropy check
return output
class UploadWidget(FormWidget):
@@ -682,6 +689,16 @@ def formstyle_divs(form, fields):
table.append(DIV(_label, _controls, _help, _id=id))
return table
def formstyle_inline(form, fields):
''' divs only '''
if len(fields)!=2:
raise RuntimeError, "Not possible"
id, label, controls, help = fields[0]
submit_button = fields[1][2]
return CAT(DIV(controls,_style='display:inline'),
submit_button)
def formstyle_ul(form, fields):
''' unordered list '''
table = UL()
@@ -807,6 +824,7 @@ class SQLFORM(FORM):
divs = formstyle_divs,
ul = formstyle_ul,
bootstrap = formstyle_bootstrap,
inline = formstyle_inline,
))
FIELDNAME_REQUEST_DELETE = 'delete_this_record'
@@ -868,6 +886,8 @@ class SQLFORM(FORM):
self.ignore_rw = ignore_rw
self.formstyle = formstyle
self.readonly = readonly
# Default dbio setting
self.detect_record_change = None
nbsp = XML('&nbsp;') # Firefox2 does not display fields with blanks
FORM.__init__(self, *[], **attributes)
@@ -1001,11 +1021,11 @@ class SQLFORM(FORM):
else:
inp = field.formatter(default)
elif field.type == 'upload':
if hasattr(field, 'widget') and field.widget:
if field.widget:
inp = field.widget(field, default, upload)
else:
inp = self.widgets.upload.widget(field, default, upload)
elif hasattr(field, 'widget') and field.widget:
elif field.widget:
inp = field.widget(field, default)
elif field.type == 'boolean':
inp = self.widgets.boolean.widget(field, default)
@@ -1121,16 +1141,16 @@ class SQLFORM(FORM):
if defaults and len(args) - len(defaults) == 4 or len(args) == 4:
table = TABLE()
for id,a,b,c in xfields:
raw_b = self.field_parent[id] = b
newrows = formstyle(id,a,raw_b,c)
newrows = formstyle(id,a,b,c)
self.field_parent[id] = getattr(b,'parent',None)
if type(newrows).__name__ != "tuple":
newrows = [newrows]
for newrow in newrows:
table.append(newrow)
else:
for id,a,b,c in xfields:
self.field_parent[id] = b
table = formstyle(self, xfields)
for id,a,b,c in xfields:
self.field_parent[id] = getattr(b,'parent',None)
else:
raise RuntimeError, 'formstyle not supported'
return table
@@ -1145,6 +1165,7 @@ class SQLFORM(FORM):
dbio=True,
hideerror=False,
detect_record_change=False,
**kwargs
):
"""
@@ -1169,7 +1190,8 @@ class SQLFORM(FORM):
# implement logic to detect whether record exist but has been modified
# server side
self.record_changed = None
if detect_record_change:
self.detect_record_change = detect_record_change
if self.detect_record_change:
if self.record:
self.record_changed = False
serialized = '|'.join(str(self.record[k]) for k in self.table.fields())
@@ -1225,6 +1247,7 @@ class SQLFORM(FORM):
keepvalues,
onvalidation,
hideerror=hideerror,
**kwargs
)
self.deleted = \
@@ -1234,31 +1257,41 @@ class SQLFORM(FORM):
auch = record_id and self.errors and self.deleted
# auch is true when user tries to delete a record
# that does not pass validation, yet it should be deleted
if not ret and not auch:
if self.record_changed and self.detect_record_change:
message_onchange = \
kwargs.setdefault("message_onchange",
current.T("A record change was detected. " +
"Consecutive update self-submissions " +
"are not allowed. Try re-submitting or " +
"refreshing the form page."))
if message_onchange is not None:
current.response.flash = message_onchange
return ret
elif (not ret) and (not auch):
# auch is true when user tries to delete a record
# that does not pass validation, yet it should be deleted
for fieldname in self.fields:
field = self.table[fieldname]
### this is a workaround! widgets should always have default not None!
if not field.widget and field.type.startswith('list:') and \
not OptionsWidget.has_options(field):
field.widget = self.widgets.list.widget
if hasattr(field, 'widget') and field.widget and fieldname in request_vars:
if field.widget and fieldname in request_vars:
if fieldname in self.vars:
value = self.vars[fieldname]
elif self.record:
value = self.record[fieldname]
else:
value = self.table[fieldname].default
if field.type.startswith('list:') and \
isinstance(value, str):
if field.type.startswith('list:') and isinstance(value, str):
value = [value]
row_id = '%s_%s%s' % (self.table, fieldname, SQLFORM.ID_ROW_SUFFIX)
widget = field.widget(field, value)
self.field_parent[row_id].components = [ widget ]
self.field_parent[row_id]._traverse(False, hideerror)
self.custom.widget[ fieldname ] = widget
parent = self.field_parent[row_id]
if parent:
parent.components = [ widget ]
parent._traverse(False, hideerror)
self.custom.widget[fieldname] = widget
self.accepted = ret
return ret
@@ -1893,7 +1926,7 @@ class SQLFORM(FORM):
search_widget = search_widget[tablename]
if search_widget=='default':
search_menu = SQLFORM.search_menu(sfields)
search_widget = lambda sfield, url: CAT(add,FORM(
search_widget = lambda sfield, url: CAT(FORM(
INPUT(_name='keywords',_value=request.vars.keywords,
_id='web2py_keywords',_onfocus="jQuery('#w2p_query_fields').change();jQuery('#w2p_query_panel').slideDown();"),
INPUT(_type='submit',_value=T('Search'),_class="btn"),
@@ -1901,6 +1934,7 @@ class SQLFORM(FORM):
_onclick="jQuery('#web2py_keywords').val('');"),
_method="GET",_action=url),search_menu)
form = search_widget and search_widget(sfields,url()) or ''
console.append(add)
console.append(form)
keywords = request.vars.get('keywords','')
try:
@@ -1945,8 +1979,7 @@ class SQLFORM(FORM):
if columns and not str(field) in columns: continue
if not field.readable: continue
key = str(field)
header = headers.get(str(field),
hasattr(field,'label') and field.label or key)
header = headers.get(str(field), field.label or key)
if sortable:
if key == order:
key, marker = '~'+order, sorter_icons[0]
@@ -2229,7 +2262,9 @@ class SQLFORM(FORM):
else:
break
if nargs>len(args)+1:
query = (field == id)
query = (field == id)
if isinstance(linked_tables,dict):
linked_tables = linked_tables.get(table._tablename,[])
if linked_tables is None or referee in linked_tables:
field.represent = lambda id,r=None,referee=referee,rep=field.represent: A(callable(rep) and rep(id) or id,_class=trap_class(),_href=url(args=['view',referee,id]))
except (KeyError,ValueError,TypeError):
@@ -2254,6 +2289,8 @@ class SQLFORM(FORM):
if rfield.readable:
check[rfield.tablename] = \
check.get(rfield.tablename,[])+[rfield.name]
if isinstance(linked_tables,dict):
linked_tables = linked_tables.get(table._tablename,[])
for tablename in sorted(check):
linked_fieldnames = check[tablename]
tb = db[tablename]
@@ -2702,8 +2739,3 @@ class ExporterXML(ExportClass):
out.write('</row>\n')
out.write('</rows>')
return str(out.getvalue())

View File

@@ -46,7 +46,9 @@ class Storage(dict):
__repr__ = lambda self: '<Storage %s>' % dict.__repr__(self)
# http://stackoverflow.com/questions/5247250/why-does-pickle-getstate-accept-as-a-return-value-the-very-instance-it-requi
__getstate__ = lambda self: None
__copy__ = lambda self: Storage(self)
def getlist(self,key):
"""
Return a Storage value as a list.

View File

@@ -448,18 +448,22 @@ class TestMigrations(unittest.TestCase):
db = DAL('sqlite://.storage.db')
db.define_table('t', Field('a'), migrate='.storage.table')
db.commit()
db.close()
db = DAL('sqlite://.storage.db')
db.define_table('t', Field('a'), Field('b'),
migrate='.storage.table')
db.commit()
db.close()
db = DAL('sqlite://.storage.db')
db.define_table('t', Field('a'), Field('b', 'text'),
migrate='.storage.table')
db.commit()
db.close()
db = DAL('sqlite://.storage.db')
db.define_table('t', Field('a'), migrate='.storage.table')
db.t.drop()
db.commit()
db.close()
def tearDown(self):
if os.path.exists('.storage.db'):

View File

@@ -1607,7 +1607,7 @@ class Auth(object):
if settings.cas_provider: ### THIS IS NOT LAZY
settings.actions_disabled = \
['profile','register','change_password',
'request_reset_password']
'request_reset_password','retrieve_username']
from gluon.contrib.login_methods.cas_auth import CasAuth
maps = settings.cas_maps
if not maps:
@@ -1709,7 +1709,8 @@ class Auth(object):
"""
login the user = db.auth_user(id)
"""
# user=Storage(self.table_user()._filter_fields(user,id=True))
user = Storage(self.table_user()._filter_fields(user,id=True))
if 'password' in user: del user.password
current.session.auth = Storage(
user = user,
last_visit = current.request.now,
@@ -3343,6 +3344,7 @@ class Crud(object):
message=DEFAULT,
deletable=DEFAULT,
formname=DEFAULT,
**attributes
):
"""
method: Crud.update(table, record, [next=DEFAULT
@@ -3395,7 +3397,8 @@ class Crud(object):
deletable=deletable,
upload=self.settings.download_url,
formstyle=self.settings.formstyle,
separator=self.settings.label_separator
separator=self.settings.label_separator,
**attributes
)
self.accepted = False
self.deleted = False
@@ -3453,6 +3456,7 @@ class Crud(object):
log=DEFAULT,
message=DEFAULT,
formname=DEFAULT,
**attributes
):
"""
method: Crud.create(table, [next=DEFAULT [, onvalidation=DEFAULT
@@ -3479,6 +3483,7 @@ class Crud(object):
message=message,
deletable=False,
formname=formname,
**attributes
)
def read(self, table, record):
@@ -4338,34 +4343,39 @@ def prettydate(d,T=lambda x:x):
return ''
else:
return '[invalid date]'
if dt.days < 0:
suffix = ' from now'
dt = -dt
else:
suffix = ' ago'
if dt.days >= 2*365:
return T('%d years ago') % int(dt.days / 365)
return T('%d years'+suffix) % int(dt.days / 365)
elif dt.days >= 365:
return T('1 year ago')
return T('1 year'+suffix)
elif dt.days >= 60:
return T('%d months ago') % int(dt.days / 30)
return T('%d months'+suffix) % int(dt.days / 30)
elif dt.days > 21:
return T('1 month ago')
return T('1 month'+suffix)
elif dt.days >= 14:
return T('%d weeks ago') % int(dt.days / 7)
return T('%d weeks'+suffix) % int(dt.days / 7)
elif dt.days >= 7:
return T('1 week ago')
return T('1 week'+suffix)
elif dt.days > 1:
return T('%d days ago') % dt.days
return T('%d days'+suffix) % dt.days
elif dt.days == 1:
return T('1 day ago')
return T('1 day'+suffix)
elif dt.seconds >= 2*60*60:
return T('%d hours ago') % int(dt.seconds / 3600)
return T('%d hours'+suffix) % int(dt.seconds / 3600)
elif dt.seconds >= 60*60:
return T('1 hour ago')
return T('1 hour'+suffix)
elif dt.seconds >= 2*60:
return T('%d minutes ago') % int(dt.seconds / 60)
return T('%d minutes'+suffix) % int(dt.seconds / 60)
elif dt.seconds >= 60:
return T('1 minute ago')
return T('1 minute'+suffix)
elif dt.seconds > 1:
return T('%d seconds ago') % dt.seconds
return T('%d seconds'+suffix) % dt.seconds
elif dt.seconds == 1:
return T('1 second ago')
return T('1 second'+suffix)
else:
return T('now')
@@ -4755,7 +4765,8 @@ class Wiki(object):
db.wiki_page.title.default = title_guess
db.wiki_page.slug.default = slug
if slug == 'wiki-menu':
db.wiki_page.body.default = '- Menu Item > @////index\n- - Submenu > http://web2py.com'
db.wiki_page.body.default = \
'- Menu Item > @////index\n- - Submenu > http://web2py.com'
else:
db.wiki_page.body.default = '## %s\n\npage content' % title_guess
vars = current.request.post_vars
@@ -4913,9 +4924,10 @@ class Wiki(object):
query = query|db.wiki_page.title.startswith(request.vars.q)
if self.restrict_search and not self.manage():
query = query&(db.wiki_page.created_by==self.auth.user_id)
pages = db(query).select(
pages = db(query).select(count,
*fields,**dict(orderby=orderby or ~count,
groupby=db.wiki_page.id,
groupby=reduce(lambda a,b:a|b,fields),
distinct=True,
limitby=limitby))
if request.extension in ('html','load'):
if not pages:
@@ -4923,18 +4935,19 @@ class Wiki(object):
_class='w2p_wiki_form'))
def link(t):
return A(t,_href=URL(args='_search',vars=dict(q=t)))
items = [DIV(H3(A(p.title,_href=URL(args=p.slug))),
MARKMIN(self.first_paragraph(p)) \
items = [DIV(H3(A(p.wiki_page.title,_href=URL(
args=p.wiki_page.slug))),
MARKMIN(self.first_paragraph(p.wiki_page)) \
if preview else '',
DIV(_class='w2p_wiki_tags',
*[link(t.strip()) for t in \
p.tags or [] if t.strip()]),
p.wiki_page.tags or [] if t.strip()]),
_class='w2p_wiki_search_item')
for p in pages]
content.append(DIV(_class='w2p_wiki_pages',*items))
else:
cloud=False
content = [p.as_dict() for p in pages]
content = [p.wiki_page.as_dict() for p in pages]
elif cloud:
content.append(self.cloud()['content'])
if request.extension=='load':
@@ -4945,19 +4958,23 @@ class Wiki(object):
count = db.wiki_tag.wiki_page.count(distinct=True)
ids = db(db.wiki_tag).select(
db.wiki_tag.name,count,
groupby=db.wiki_tag.name,
orderby=~count,limitby=(0,20))
distinct=True,
groupby = db.wiki_tag.name,
orderby = ~count, limitby=(0,20))
if ids:
a,b = ids[0](count), ids[-1](count)
def scale(c):
return '%.2f' % (3.0*(c-b)/max(a-b,1)+1)
items = [A(item.wiki_tag.name,
_style='padding:0.2em;font-size:%sem' \
% scale(item(count)),
_href=URL(args='_search',
vars=dict(q=item.wiki_tag.name)))
for item in ids]
return dict(content=DIV(_class='w2p_cloud',*items))
def style(c):
STYLE ='padding:0 0.2em;line-height:%.2fem;font-size:%.2fem'
size = (1.5*(c-b)/max(a-b,1)+1.3)
return STYLE % (1.3,size)
items = []
for item in ids:
items.append(A(item.wiki_tag.name,
_style=style(item(count)),
_href=URL(args='_search',
vars=dict(q=item.wiki_tag.name))))
items.append(' ')
return dict(content = DIV(_class='w2p_cloud',*items))
if __name__ == '__main__':
import doctest

View File

@@ -9,6 +9,9 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
This file specifically includes utilities for security.
"""
import string
import threading
import struct
import hashlib
import hmac
import uuid
@@ -18,7 +21,11 @@ import os
import re
import logging
import socket
from contrib.pbkdf2 import pbkdf2_hex
try:
from contrib.pbkdf2 import pbkdf2_hex
HAVE_PBKDF2 = True
except ImportError:
HAVE_PBKDF2 = False
logger = logging.getLogger("web2py")
@@ -88,7 +95,7 @@ DIGEST_ALG_BY_SIZE = {
}
### compute constant ctokens
### compute constant CTOKENS
def initialize_urandom():
"""
This function and the web2py_uuid follow from the following discussion:
@@ -108,6 +115,7 @@ def initialize_urandom():
random.seed(node_id + microseconds)
try:
os.urandom(1)
have_urandom = True
try:
# try to add process-specific entropy
frandom = open('/dev/urandom','wb')
@@ -119,14 +127,33 @@ def initialize_urandom():
# works anyway
pass
except NotImplementedError:
have_urandom = False
logger.warning(
"""Cryptographically secure session management is not possible on your system because
your system does not provide a cryptographically secure entropy source.
This is not specific to web2py; consider deploying on a different operating system.""")
return ctokens
ctokens = initialize_urandom()
unpacked_ctokens = struct.unpack('=QQ',string.join(
(chr(x) for x in ctokens),''))
return unpacked_ctokens, have_urandom
UNPACKED_CTOKENS, HAVE_URANDOM = initialize_urandom()
def web2py_uuid():
def fast_urandom16(urandom=[], locker = threading.RLock()):
"""
this is 4x faster than calling os.urandom(16) and prevents
the "too many files open" issue with concurrent access to os.urandom()
"""
try:
return urandom.pop()
except IndexError:
try:
locker.acquire()
ur = os.urandom(16*1024)
urandom += [ur[i:i+16] for i in xrange(16,1024*16,16)]
return ur[0:16]
finally:
locker.release()
def web2py_uuid(ctokens=UNPACKED_CTOKENS):
"""
This function follows from the following discussion:
http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09
@@ -134,15 +161,18 @@ def web2py_uuid():
It works like uuid.uuid4 except that tries to use os.urandom() if possible
and it XORs the output with the tokens uniquely associated with this machine.
"""
bytes = [random.randrange(256) for i in range(16)]
try:
ubytes = [ord(c) for c in os.urandom(16)] # use /dev/urandom if possible
bytes = [bytes[i] ^ ubytes[i] for i in range(16)]
except NotImplementedError:
pass
## xor bytes with constant ctokens
bytes = ''.join(chr(c ^ ctokens[i]) for i,c in enumerate(bytes))
return str(uuid.UUID(bytes=bytes, version=4))
rand_longs = struct.unpack('=QQ', string.join(
(chr(random.randrange(256)) for i in xrange(16)),''))
if HAVE_URANDOM:
urand_longs = struct.unpack('=QQ', fast_urandom16())
byte_s = struct.pack('=QQ',
rand_longs[0]^urand_longs[0]^ctokens[0],
rand_longs[1]^urand_longs[1]^ctokens[1])
else:
byte_s = struct.pack('=QQ',
rand_longs[0]^ctokens[0],
rand_longs[1]^ctokens[1])
return str(uuid.UUID(bytes=byte_s, version=4))
REGEX_IPv4 = re.compile('(\d+)\.(\d+)\.(\d+)\.(\d+)')

View File

@@ -117,6 +117,10 @@ class Validator(object):
"""
return value
def __call__(self,value):
raise NotImplementedError
return (value, None)
class IS_MATCH(Validator):
"""
@@ -508,7 +512,7 @@ class IS_IN_DB(Validator):
def count(values, s=self.dbset, f=field):
return s(f.belongs(map(int,values))).count()
if isinstance(self.dbset.db._adapter, GoogleDatastoreAdapter):
range_ids = range(0,len(ids),30)
range_ids = range(0,len(values),30)
total = sum(count(values[i:i+30]) for i in range_ids)
if total == len(values):
return (values, None)

View File

@@ -955,6 +955,19 @@ def check_existent_app(options,appname):
if os.path.isdir(os.path.join(options.folder, 'applications', appname)):
return True
def get_code_for_scheduler(app, options):
if len(app) == 1 or app[1] == None:
code = "from gluon import current;current._scheduler.loop()"
else:
code = "from gluon import current;current._scheduler.group_names = ['%s'];"
code += "current._scheduler.loop()"
code = code % ("','".join(app[1:]))
app_ = app[0]
if not check_existent_app(options, app_):
print "Application '%s' doesn't exist, skipping" % (app_)
return None, None
return app_, code
def start_schedulers(options):
try:
from multiprocessing import Process
@@ -965,20 +978,21 @@ def start_schedulers(options):
apps = [(app.strip(), None) for app in options.scheduler.split(',')]
if options.scheduler_groups:
apps = options.scheduler_groups
code = "from gluon import current;current._scheduler.loop()"
logging.getLogger().setLevel(options.debuglevel)
if len(apps) == 1 and not options.with_scheduler:
app_, code = get_code_for_scheduler(apps[0], options)
if not app_:
return
print 'starting single-scheduler for "%s"...' % app_
run(app_,True,True,None,False,code)
return
for app in apps:
if len(app) == 1 or app[1] == None:
code = "from gluon import current;current._scheduler.loop()"
else:
code = "from gluon import current;current._scheduler.group_names = ['%s'];"
code += "current._scheduler.loop()"
code = code % ("','".join(app[1:]))
app_ = app[0]
if not check_existent_app(options, app_):
print "Application '%s' doesn't exist, skipping" % (app_)
app_, code = get_code_for_scheduler(app, options)
if not app_:
continue
print 'starting scheduler for "%s"...' % app_
args = (app_,True,True,None,False,code)
logging.getLogger().setLevel(options.debuglevel)
p = Process(target=run, args=args)
processes.append(p)
print "Currently running %s scheduler processes" % (len(processes))

View File

@@ -23,7 +23,7 @@ applications/welcome/controllers/default.py
# files and folders to exclude from gluon folder (comment with # if needed)
IGNORED = """
gluon/contrib/comet_messaging.py
gluon/contrib/websocket_messaging.py
gluon/contrib/feedparser.py
gluon/contrib/generics.py
gluon/contrib/gql.py

View File

@@ -22,7 +22,8 @@ echo 'server {
root /home/www-data/web2py/applications/;
}
location / {
uwsgi_pass 127.0.0.1:9001;
#uwsgi_pass 127.0.0.1:9001;
uwsgi_pass unix:///run/uwsgi/app/web2py/web2py.socket;
include uwsgi_params;
uwsgi_param UWSGI_SCHEME $scheme;
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
@@ -36,7 +37,8 @@ server {
ssl_certificate /etc/nginx/ssl/web2py.crt;
ssl_certificate_key /etc/nginx/ssl/web2py.key;
location / {
uwsgi_pass 127.0.0.1:9001;
#uwsgi_pass 127.0.0.1:9001;
uwsgi_pass unix:///run/uwsgi/app/web2py/web2py.socket;
include uwsgi_params;
uwsgi_param UWSGI_SCHEME $scheme;
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
@@ -55,11 +57,23 @@ openssl x509 -req -days 1780 -in web2py.csr -signkey web2py.key -out web2py.crt
# Create configuration file /etc/uwsgi/apps-available/web2py.xml
echo '<uwsgi>
<plugin>python</plugin>
<socket>127.0.0.1:9001</socket>
<socket>/run/uwsgi/app/web2py/web2py.socket</socket>
<pythonpath>/home/www-data/web2py/</pythonpath>
<app mountpoint="/">
<script>wsgihandler</script>
</app>
<master/>
<processes>4</processes>
<harakiri>60</harakiri>
<reload-mercy>8</reload-mercy>
<cpu-affinity>1</cpu-affinity>
<stats>/tmp/stats.socket</stats>
<max-requests>2000</max-requests>
<limit-as>512</limit-as>
<reload-on-as>256</reload-on-as>
<reload-on-rss>192</reload-on-rss>
<no-orphans/>
<vacuum/>
</uwsgi>' >/etc/uwsgi/apps-available/web2py.xml
ln -s /etc/uwsgi/apps-available/web2py.xml /etc/uwsgi/apps-enabled/web2py.xml

View File

@@ -77,8 +77,3 @@ if __name__ == '__main__':
#s = raw_input('>')
#if s.lower()[:1]=='y':
start()