Merge github.com:web2py/web2py
This commit is contained in:
13
Makefile
13
Makefile
@@ -29,7 +29,7 @@ update:
|
||||
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
|
||||
echo "remember that pymysql was tweaked"
|
||||
src:
|
||||
echo 'Version 2.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
|
||||
|
||||
2
VERSION
2
VERSION
@@ -1 +1 @@
|
||||
Version 2.0.9 (2012-09-21 09:37:29) stable
|
||||
Version 2.0.9 (2012-09-30 14:45:39) dev
|
||||
|
||||
@@ -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;">×</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');
|
||||
}
|
||||
|
||||
|
||||
@@ -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;">×</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');
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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%}
|
||||
|
||||
@@ -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 |
BIN
applications/welcome/static/images/gplus-32.png
Normal file
BIN
applications/welcome/static/images/gplus-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
28
applications/welcome/static/js/web2py_bootstrap.js
Normal file
28
applications/welcome/static/js/web2py_bootstrap.js
Normal 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');
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
@@ -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>™ </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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
19
gluon/contrib/pyuca/LICENSE
Normal file
19
gluon/contrib/pyuca/LICENSE
Normal 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.
|
||||
43
gluon/contrib/pyuca/README.markmin
Normal file
43
gluon/contrib/pyuca/README.markmin
Normal 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
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
713
gluon/dal.py
713
gluon/dal.py
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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.")
|
||||
|
||||
|
||||
106
gluon/sqlhtml.py
106
gluon/sqlhtml.py
@@ -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(' ') # 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())
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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+)')
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user