Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f93191f406 | |||
| b487583f92 | |||
| 702e7cbea2 | |||
| 18a901cce4 | |||
| f23115cb9c | |||
| ea5e86e11e | |||
| db223dc70a | |||
| bcc4ae2ec6 | |||
| 4b81f721ac | |||
| 66f231eb4b | |||
| 5fc9517803 | |||
| 98294e0c69 | |||
| 5e28112eda | |||
| dc5cac07e1 | |||
| 8058dc2ce6 | |||
| 3808b1f6ae | |||
| 10f2e4c3ad | |||
| 3c6af5f920 | |||
| a5269b1a1a | |||
| 9a079e092f | |||
| 218817753a | |||
| ef9bf73973 | |||
| f92f21b060 | |||
| 642ec2b934 | |||
| d494ec9c88 | |||
| c4d1f3f414 | |||
| 5a59149514 | |||
| 484f02cae1 | |||
| d5db67d5ea | |||
| 1480a10d6b | |||
| 7259f273f3 | |||
| 8645365f58 | |||
| 106930ed73 | |||
| f79b38a335 | |||
| 35216db750 | |||
| faa3d1d477 | |||
| 7aff79ca57 | |||
| 63bb4a7e8a | |||
| 8fc322254e | |||
| b4c28516ae | |||
| d233d3babb | |||
| f18a1d0555 | |||
| 2cb55b52e9 | |||
| 4f361b5aad | |||
| db122e7709 | |||
| 005e565a11 | |||
| 1656c6cdeb | |||
| b7a0f2043c | |||
| 05df3b3029 | |||
| ba2cb811be | |||
| 6a7c0525f5 | |||
| 5132616c6c | |||
| e528c10c21 | |||
| 41fd02fa2c | |||
| 26dab37d9f | |||
| cc40018e87 | |||
| b6db314612 | |||
| 3498666115 | |||
| 562a559169 | |||
| 47cec80939 | |||
| eceb579cdd | |||
| bd19986380 | |||
| 12acdb51d7 | |||
| 918590d1f3 | |||
| d57428e8f0 | |||
| 13e3adf22d | |||
| d4ffcaf1b1 | |||
| 17f1a51133 | |||
| d4bca008a8 | |||
| 90c33911ab | |||
| 0a263ffc8d | |||
| e94946d3d5 | |||
| 1ca0c9b0c0 | |||
| cee0f91b36 | |||
| eb49831726 | |||
| b517c898b8 |
@@ -1,3 +1,32 @@
|
|||||||
|
## trunk
|
||||||
|
- new JWT implementation (experimental)
|
||||||
|
- new gluon.contrib.redis_scheduler
|
||||||
|
- BREAKING: changes to gluon.contrib.redis_cache
|
||||||
|
BEFORE:
|
||||||
|
from gluon.contrib.redis_cache import RedisCache
|
||||||
|
cache.redis = RedisCache('localhost:6379',db=None, debug=True)
|
||||||
|
NOW:
|
||||||
|
from gluon.contrib.redis_utils import RConn
|
||||||
|
from gluon.contrib.redis_cache import RedisCache
|
||||||
|
rconn = RConn()
|
||||||
|
# or RConn(host='localhost', port=6379,
|
||||||
|
# db=0, password=None, socket_timeout=None,
|
||||||
|
# socket_connect_timeout=None, .....)
|
||||||
|
# exactly as a redis.StrictRedis instance
|
||||||
|
cache.redis = RedisCache(redis_conn=rconn, debug=True)
|
||||||
|
- BREAKING: changes to gluon.contrib.redis_session
|
||||||
|
BEFORE:
|
||||||
|
from gluon.contrib.redis_session import RedisSession
|
||||||
|
sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False)
|
||||||
|
session.connect(request, response, db = sessiondb)
|
||||||
|
NOW:
|
||||||
|
from gluon.contrib.redis_utils import RConn
|
||||||
|
from gluon.contrib.redis_session import RedisSession
|
||||||
|
rconn = RConn()
|
||||||
|
sessiondb = RedisSession(redis_conn=rconn, session_expiry=False)
|
||||||
|
session.connect(request, response, db = sessiondb)
|
||||||
|
|
||||||
|
|
||||||
## 2.13.1-2
|
## 2.13.1-2
|
||||||
|
|
||||||
- fixed a security issue in request_reset_password
|
- fixed a security issue in request_reset_password
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Version 2.13.3-stable+timestamp.2015.12.24.08.08.22
|
Version 2.13.4-stable+timestamp.2016.02.10.15.41.11
|
||||||
|
|||||||
@@ -12,29 +12,24 @@ def button(href, label):
|
|||||||
if is_mobile:
|
if is_mobile:
|
||||||
ret = A_button(SPAN(label), _href=href)
|
ret = A_button(SPAN(label), _href=href)
|
||||||
else:
|
else:
|
||||||
ret = A(SPAN(label), _class='button btn', _href=href)
|
ret = A(SPAN(label), _class='btn rounded', _href=href)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def button_enable(href, app):
|
def button_enable(href, app):
|
||||||
if os.path.exists(os.path.join(apath(app, r=request), 'DISABLED')):
|
if os.path.exists(os.path.join(apath(app, r=request), 'DISABLED')):
|
||||||
label = SPAN(T('Enable'), _style='color:red')
|
text, classes = T("Enable"), "btn rounded red"
|
||||||
else:
|
else:
|
||||||
label = SPAN(T('Disable'), _style='color:green')
|
text, classes = T("Disable"), "btn rounded gree"
|
||||||
id = 'enable_' + app
|
id = 'enable_' + app
|
||||||
return A(label, _class='button btn', _id=id, callback=href, target=id)
|
return A(text, _class=classes, _id=id, callback=href, target=id)
|
||||||
|
|
||||||
def sp_button(href, label):
|
def sp_button(href, label):
|
||||||
if request.user_agent().get('is_mobile'):
|
if request.user_agent().get('is_mobile'):
|
||||||
ret = A_button(SPAN(label), _href=href)
|
ret = A_button(SPAN(label), _href=href)
|
||||||
else:
|
else:
|
||||||
ret = A(SPAN(label), _class='button special btn btn-inverse', _href=href)
|
ret = A(SPAN(label), _class='btn pink rounded', _href=href)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def helpicon():
|
def helpicon():
|
||||||
return IMG(_src=URL('static', 'images/help.png'), _alt='help')
|
return IMG(_src=URL('static', 'images/help.png'), _alt='help')
|
||||||
|
|
||||||
def searchbox(elementid):
|
|
||||||
return SPAN(LABEL(IMG(_id="search_start", _src=URL('static', 'images/search.png'), _alt=T('filter')),
|
|
||||||
_class='icon', _for=elementid), ' ',
|
|
||||||
INPUT(_id=elementid, _type='text', _size=12, _class="input-medium"),
|
|
||||||
_class="searchbox")
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,579 +0,0 @@
|
|||||||
/*=============================================================
|
|
||||||
GENERAL
|
|
||||||
==============================================================*/
|
|
||||||
html,body{height:auto;background:transparent;}
|
|
||||||
/*=============================================================
|
|
||||||
CONTROLS
|
|
||||||
==============================================================*/
|
|
||||||
label,
|
|
||||||
input,
|
|
||||||
button,
|
|
||||||
select,
|
|
||||||
textarea,
|
|
||||||
button.btn
|
|
||||||
{
|
|
||||||
font-size:13px;
|
|
||||||
font-weight:normal;
|
|
||||||
line-height:18px;
|
|
||||||
}
|
|
||||||
textarea,
|
|
||||||
select
|
|
||||||
{
|
|
||||||
margin-bottom:9px;
|
|
||||||
}
|
|
||||||
select,
|
|
||||||
/*textarea,*/
|
|
||||||
input[type="text"],
|
|
||||||
input[type="password"],
|
|
||||||
input[type="datetime"],
|
|
||||||
input[type="datetime-local"],
|
|
||||||
input[type="date"],
|
|
||||||
input[type="month"],
|
|
||||||
input[type="time"],
|
|
||||||
input[type="week"],
|
|
||||||
input[type="number"],
|
|
||||||
input[type="email"],
|
|
||||||
input[type="url"],
|
|
||||||
input[type="search"],
|
|
||||||
input[type="tel"],
|
|
||||||
input[type="color"],
|
|
||||||
.uneditable-input,
|
|
||||||
a.btn-lnk
|
|
||||||
{
|
|
||||||
height:18px;
|
|
||||||
padding:4px;
|
|
||||||
font-size:13px;
|
|
||||||
line-height:18px;
|
|
||||||
}
|
|
||||||
.design h3,
|
|
||||||
.plugin h3
|
|
||||||
{
|
|
||||||
background-position:0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
select,
|
|
||||||
input[type="file"]
|
|
||||||
{
|
|
||||||
height:28px;
|
|
||||||
line-height:28px;
|
|
||||||
}
|
|
||||||
input[type="submit"],
|
|
||||||
input[type="button"]
|
|
||||||
{
|
|
||||||
font-size:13px;
|
|
||||||
height:28px;
|
|
||||||
line-height:18px;
|
|
||||||
padding:4px 10px;
|
|
||||||
}
|
|
||||||
input[type="radio"],
|
|
||||||
input[type="checkbox"]
|
|
||||||
{
|
|
||||||
margin-top:2px;
|
|
||||||
}
|
|
||||||
.button.btn
|
|
||||||
{
|
|
||||||
line-height:1.25em;
|
|
||||||
font-size:inherit;
|
|
||||||
border:none;
|
|
||||||
text-shadow:none;
|
|
||||||
margin-bottom:0px;
|
|
||||||
-webkit-border-radius:0px;
|
|
||||||
-moz-border-radius:0px;
|
|
||||||
border-radius:0px;
|
|
||||||
-webkit-box-shadow:none;
|
|
||||||
-moz-box-shadow:none;
|
|
||||||
box-shadow:none);
|
|
||||||
}
|
|
||||||
.button.btn:hover
|
|
||||||
{
|
|
||||||
background-color:transparent;
|
|
||||||
-webkit-transition: background-position 0s linear;
|
|
||||||
-moz-transition: background-position 0s linear;
|
|
||||||
-o-transition: background-position 0s linear;
|
|
||||||
transition: background-position 0s linear;
|
|
||||||
}
|
|
||||||
form label
|
|
||||||
{
|
|
||||||
font-weight:bold;
|
|
||||||
}
|
|
||||||
.help
|
|
||||||
{
|
|
||||||
border-color:transparent;
|
|
||||||
}
|
|
||||||
/* tree menu */
|
|
||||||
.folder
|
|
||||||
{
|
|
||||||
border:none;
|
|
||||||
}
|
|
||||||
.folder>i
|
|
||||||
{
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
.celled
|
|
||||||
{
|
|
||||||
padding-top: 2px;
|
|
||||||
}
|
|
||||||
.celled-one
|
|
||||||
{
|
|
||||||
padding-top: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.test h3
|
|
||||||
{
|
|
||||||
border:0;
|
|
||||||
padding-left:18px;
|
|
||||||
}
|
|
||||||
/*=============================================================
|
|
||||||
FLASH MESSAGEBOX
|
|
||||||
==============================================================*/
|
|
||||||
.flash
|
|
||||||
{
|
|
||||||
position:fixed;
|
|
||||||
width:50%;
|
|
||||||
top:49px;
|
|
||||||
left:25%;
|
|
||||||
right:25%;
|
|
||||||
cursor:default;
|
|
||||||
text-align:center;
|
|
||||||
padding:8px 35px 8px 14px;
|
|
||||||
z-index:5620;
|
|
||||||
}
|
|
||||||
.flash>.close
|
|
||||||
{
|
|
||||||
color:inherit;
|
|
||||||
opacity:0.7;
|
|
||||||
}
|
|
||||||
.flash>.close:hover
|
|
||||||
{
|
|
||||||
opacity:0.9;
|
|
||||||
}
|
|
||||||
/*=============================================================
|
|
||||||
NAVBAR
|
|
||||||
==============================================================*/
|
|
||||||
.navbar-fixed-top .navbar-inner,
|
|
||||||
.navbar-static-top .navbar-inner
|
|
||||||
{
|
|
||||||
/* in place of shadow image */
|
|
||||||
-webkit-box-shadow:0px 10px 20px rgba(195,195,195,1.0);
|
|
||||||
-moz-box-shadow: 0px 10px 20px rgba(195,195,195,1.0);
|
|
||||||
box-shadow: 0px 10px 20px rgba(195,195,195,1.0);
|
|
||||||
//zoom:1; /* IE6-9 */
|
|
||||||
filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=0, OffY=10, Color=#000000); /* IE6-9 */
|
|
||||||
padding:0;
|
|
||||||
}
|
|
||||||
.navbar-inverse .navbar-inner
|
|
||||||
{
|
|
||||||
min-height:33px; /* required - override */
|
|
||||||
height:33px;
|
|
||||||
filter:progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */
|
|
||||||
background:#292929 url(../images/header_bg.png) repeat-x;
|
|
||||||
border:none;
|
|
||||||
}
|
|
||||||
#header
|
|
||||||
{
|
|
||||||
background:transparent;
|
|
||||||
}
|
|
||||||
#header.navbar
|
|
||||||
{
|
|
||||||
overflow:visible;
|
|
||||||
}
|
|
||||||
.navbar-inverse .nav > li > a
|
|
||||||
{
|
|
||||||
padding:0;
|
|
||||||
line-height:1.25;
|
|
||||||
text-shadow:none;
|
|
||||||
}
|
|
||||||
.navbar .btn-navbar
|
|
||||||
{
|
|
||||||
padding:4px;
|
|
||||||
margin:5px 5px 0 5px;
|
|
||||||
}
|
|
||||||
#menu{margin-right:-7px;}
|
|
||||||
/*=============================================================
|
|
||||||
FOOTER
|
|
||||||
==============================================================*/
|
|
||||||
#footer
|
|
||||||
{
|
|
||||||
padding-bottom:0;
|
|
||||||
}
|
|
||||||
/*=============================================================
|
|
||||||
MAIN
|
|
||||||
==============================================================*/
|
|
||||||
#main
|
|
||||||
{
|
|
||||||
position:static;
|
|
||||||
padding-top:0;
|
|
||||||
padding-bottom:0;
|
|
||||||
}
|
|
||||||
/*=============================================================
|
|
||||||
SIDEBAR
|
|
||||||
==============================================================*/
|
|
||||||
.sidebar_inner
|
|
||||||
{
|
|
||||||
background:transparent;
|
|
||||||
padding:0;
|
|
||||||
min-width:auto;
|
|
||||||
}
|
|
||||||
.sidebar .box {
|
|
||||||
border-top:1px solid #EEE;
|
|
||||||
}
|
|
||||||
/*=============================================================
|
|
||||||
WIZARD
|
|
||||||
==============================================================*/
|
|
||||||
.step div.help li
|
|
||||||
{
|
|
||||||
line-height:inherit;
|
|
||||||
}
|
|
||||||
.ms-container .ms-selectable li.ms-elem-selectable,
|
|
||||||
.ms-container .ms-selection li.ms-elem-selected
|
|
||||||
{
|
|
||||||
font-size:13px;
|
|
||||||
}
|
|
||||||
.input-append a.btn
|
|
||||||
{
|
|
||||||
padding:4px;
|
|
||||||
height:18px;
|
|
||||||
font-size:13px;
|
|
||||||
line-height:18px;
|
|
||||||
}
|
|
||||||
/*=============================================================
|
|
||||||
ERRORS TABLE
|
|
||||||
==============================================================*/
|
|
||||||
.errors .table th
|
|
||||||
{
|
|
||||||
filter:progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.tablebar span.help
|
|
||||||
{
|
|
||||||
font-weight:normal;
|
|
||||||
line-height:1.25em;
|
|
||||||
text-shadow:none;
|
|
||||||
width:auto;
|
|
||||||
}
|
|
||||||
/*=============================================================
|
|
||||||
TOOLTIP
|
|
||||||
==============================================================*/
|
|
||||||
.tooltip.in
|
|
||||||
{
|
|
||||||
opacity:1;
|
|
||||||
filter:alpha(opacity=100);
|
|
||||||
}
|
|
||||||
.tooltip-inner
|
|
||||||
{
|
|
||||||
opacity:1;
|
|
||||||
text-align:left;
|
|
||||||
background:#9fb364;
|
|
||||||
color:#eef1d9;
|
|
||||||
border:1px solid #eef1d9;
|
|
||||||
font-style:italic;
|
|
||||||
padding:0.3em;
|
|
||||||
-moz-border-radius:0.5em;
|
|
||||||
border-radius:0.5em;
|
|
||||||
font-size:13px;
|
|
||||||
text-transform:none;
|
|
||||||
}
|
|
||||||
.tooltip.right .tooltip-arrow,
|
|
||||||
.tooltip.left .tooltip-arrow
|
|
||||||
{
|
|
||||||
border-color:transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
THE GRID
|
|
||||||
==============================================================*/
|
|
||||||
.w2p_grid_bottom_bar .w2p_export_menu
|
|
||||||
{
|
|
||||||
line-height:18px;
|
|
||||||
margin-left:0;
|
|
||||||
}
|
|
||||||
.w2p_export_menu .dropdown-toggle
|
|
||||||
{
|
|
||||||
cursor:pointer;
|
|
||||||
margin:0;
|
|
||||||
padding:0;
|
|
||||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(white), to(#E6E6E6));
|
|
||||||
background-image: -webkit-linear-gradient(top, white, #E6E6E6);
|
|
||||||
background-image: -o-linear-gradient(top, white, #E6E6E6);
|
|
||||||
background-image: linear-gradient(to bottom, white, #E6E6E6);
|
|
||||||
background-image: -moz-linear-gradient(top, white, #E6E6E6);
|
|
||||||
}
|
|
||||||
.w2p_export_menu ul
|
|
||||||
{
|
|
||||||
margin-top:2px;
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
.w2p_export_menu li
|
|
||||||
{
|
|
||||||
display:list-item;
|
|
||||||
margin:0;
|
|
||||||
}
|
|
||||||
div.web2py_grid
|
|
||||||
{
|
|
||||||
font-size:13px;
|
|
||||||
line-height:18px;
|
|
||||||
}
|
|
||||||
.web2py_grid a.btn
|
|
||||||
{
|
|
||||||
font-size:13px;
|
|
||||||
line-height:18px;
|
|
||||||
padding:4px 10px;
|
|
||||||
margin-left:0;
|
|
||||||
margin-right:4px;
|
|
||||||
|
|
||||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
|
|
||||||
background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
|
|
||||||
background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
|
|
||||||
background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
|
|
||||||
background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
|
|
||||||
}
|
|
||||||
.web2py_grid .input-append .btn
|
|
||||||
{
|
|
||||||
padding:4px 10px;
|
|
||||||
margin-right:0;
|
|
||||||
font-family:inherit;
|
|
||||||
color:#333;
|
|
||||||
text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);
|
|
||||||
border:1px solid #c5c5c5;
|
|
||||||
}
|
|
||||||
.web2py_grid select:focus
|
|
||||||
{
|
|
||||||
border-color:rgba(232,149,60,0.8);
|
|
||||||
outline:0;
|
|
||||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(232, 149, 60, 0.6);
|
|
||||||
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(232,149,60,0.6);
|
|
||||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(232, 149, 60, 0.6);
|
|
||||||
}
|
|
||||||
.web2py_console input[type="button"],
|
|
||||||
.web2py_grid .row_buttons a.btn
|
|
||||||
{
|
|
||||||
color:#333;
|
|
||||||
line-height:18px;
|
|
||||||
padding:4px 10px;
|
|
||||||
text-shadow:rgba(255, 255, 255, 0.74902) 0px 1px 1px;
|
|
||||||
border-color:rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.web2py_console input[type="button"]:hover,
|
|
||||||
.web2py_grid .row_buttons a.btn:hover
|
|
||||||
{
|
|
||||||
color:#333;
|
|
||||||
border-color:rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);
|
|
||||||
background:#E6E6E6;
|
|
||||||
background-position: 0 -15px !important;
|
|
||||||
-webkit-transition: background-position .1s linear;
|
|
||||||
-moz-transition: background-position .1s linear;
|
|
||||||
-o-transition: background-position .1s linear;
|
|
||||||
transition: background-position .1s linear;
|
|
||||||
}
|
|
||||||
.web2py_table
|
|
||||||
{
|
|
||||||
border:none;
|
|
||||||
}
|
|
||||||
.web2py_table table
|
|
||||||
{
|
|
||||||
/*table-layout:fixed;*/
|
|
||||||
margin-bottom:4px;
|
|
||||||
}
|
|
||||||
.web2py_table table td
|
|
||||||
{
|
|
||||||
/*word-wrap:break-word;*/ /*uncomment when "table-layout:fixed" is applied */
|
|
||||||
}
|
|
||||||
|
|
||||||
.web2py_grid thead th
|
|
||||||
{
|
|
||||||
background-color:transparent;
|
|
||||||
padding:4px 5px;
|
|
||||||
line-height:18px;
|
|
||||||
vertical-align:bottom;
|
|
||||||
border-right:0;
|
|
||||||
border-bottom:0;
|
|
||||||
word-wrap:break-word;
|
|
||||||
}
|
|
||||||
.web2py_grid .btn-group > .dropdown-menu
|
|
||||||
{
|
|
||||||
font-size:13px;
|
|
||||||
}
|
|
||||||
.web2py_grid .dropdown-menu li > a:hover,
|
|
||||||
.web2py_grid .dropdown-menu li > a:focus
|
|
||||||
{
|
|
||||||
filter:progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */
|
|
||||||
background-image:none;
|
|
||||||
background-color:#E8953C;
|
|
||||||
}
|
|
||||||
.pagination
|
|
||||||
{
|
|
||||||
margin:0;
|
|
||||||
height:30px;
|
|
||||||
}
|
|
||||||
.pagination ul > li > a
|
|
||||||
{
|
|
||||||
line-height:28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#w2p_grid_addbtn:focus,
|
|
||||||
#w2p_search-form :focus,
|
|
||||||
.btn:focus
|
|
||||||
{
|
|
||||||
outline:none;
|
|
||||||
}
|
|
||||||
.web2py_console input[type="button"]:focus,
|
|
||||||
.web2py_grid .row_buttons a.btn:focus
|
|
||||||
{
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15) inset, 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
div.web2py_counter.span6
|
|
||||||
{
|
|
||||||
min-height:20px;
|
|
||||||
}
|
|
||||||
.web2py_paginator
|
|
||||||
{
|
|
||||||
border:0;
|
|
||||||
margin:0;
|
|
||||||
padding:0;
|
|
||||||
background-color:transparent;
|
|
||||||
}
|
|
||||||
.web2py_paginator ul li a
|
|
||||||
{
|
|
||||||
margin-right:0;
|
|
||||||
padding:0 14px;
|
|
||||||
border:1px solid #DDD;
|
|
||||||
border-left-width:0;
|
|
||||||
color:#E8953C;
|
|
||||||
}
|
|
||||||
.web2py_paginator ul li a:hover
|
|
||||||
{
|
|
||||||
background: whiteSmoke;
|
|
||||||
border: 1px solid #DDD;
|
|
||||||
border-left-width:0;
|
|
||||||
color:#e2821b;
|
|
||||||
}
|
|
||||||
.web2py_paginator ul li:first-child a,
|
|
||||||
.web2py_paginator ul li:first-child a:hover
|
|
||||||
{
|
|
||||||
border-left-width:1px;
|
|
||||||
}
|
|
||||||
.web2py_paginator .current
|
|
||||||
{
|
|
||||||
font-weight:normal;
|
|
||||||
}
|
|
||||||
.web2py_paginator ul li.current a:hover
|
|
||||||
{
|
|
||||||
color:#999;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.editor-bar-column a[name="save"]
|
|
||||||
{
|
|
||||||
background-color: whiteSmoke;
|
|
||||||
background-image: -webkit-gradient(linear,0 0,0 100%,from(white),to(#E6E6E6));
|
|
||||||
background-image: -webkit-linear-gradient(top,white,#E6E6E6);
|
|
||||||
background-image: -o-linear-gradient(top,white,#E6E6E6);
|
|
||||||
background-image: linear-gradient(to bottom,white,#E6E6E6);
|
|
||||||
background-image: -moz-linear-gradient(top,white,#E6E6E6);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
padding:2px 6px;
|
|
||||||
font-size:11px;
|
|
||||||
line-height:17px;
|
|
||||||
margin:0;
|
|
||||||
}
|
|
||||||
.editor-bar-column a[name="save"]:hover
|
|
||||||
{
|
|
||||||
background-color: #E6E6E6;
|
|
||||||
background-position: 0 -15px;
|
|
||||||
-webkit-transition: background-position .1s linear;
|
|
||||||
-moz-transition: background-position .1s linear;
|
|
||||||
-o-transition: background-position .1s linear;
|
|
||||||
transition: background-position .1s linear;
|
|
||||||
}
|
|
||||||
.keybindings
|
|
||||||
{
|
|
||||||
padding:0 18px 10px;
|
|
||||||
}
|
|
||||||
.keybindings li
|
|
||||||
{
|
|
||||||
margin-bottom:0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*----- translate page ---*/
|
|
||||||
|
|
||||||
.languageform input
|
|
||||||
{
|
|
||||||
margin-bottom:0;
|
|
||||||
}
|
|
||||||
.languageform div
|
|
||||||
{
|
|
||||||
margin-bottom:9px;
|
|
||||||
}
|
|
||||||
.languageform input.untranslated
|
|
||||||
{
|
|
||||||
background-color:#FC0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step #wizard_nav .first-box
|
|
||||||
{
|
|
||||||
padding-top:0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
MEDIA QUERIES
|
|
||||||
==============================================================*/
|
|
||||||
@media (max-width: 979px)
|
|
||||||
{
|
|
||||||
/*-----------------------------------
|
|
||||||
Navbar
|
|
||||||
-------------------------------------*/
|
|
||||||
#header .navbar-inner
|
|
||||||
{
|
|
||||||
padding:0;
|
|
||||||
}
|
|
||||||
/*collapsed menu*/
|
|
||||||
.navbar .nav-collapse .nav
|
|
||||||
{
|
|
||||||
background:#222;
|
|
||||||
padding:8px 2px 8px 8px;
|
|
||||||
-webkit-border-bottom-right-radius:8px;
|
|
||||||
-webkit-border-bottom-left-radius:8px;
|
|
||||||
-moz-border-radius-bottomright:8px;
|
|
||||||
-moz-border-radius-bottomleft:8px;
|
|
||||||
border-bottom-right-radius:8px;
|
|
||||||
border-bottom-left-radius:8px;
|
|
||||||
}
|
|
||||||
#menu
|
|
||||||
{
|
|
||||||
margin-right:0;
|
|
||||||
}
|
|
||||||
#menu li
|
|
||||||
{
|
|
||||||
float:none;
|
|
||||||
}
|
|
||||||
#menu a.button,
|
|
||||||
#menu a.button span
|
|
||||||
{
|
|
||||||
background-image:url(../images/menu_responsive.png);
|
|
||||||
}
|
|
||||||
#menu a.button
|
|
||||||
{
|
|
||||||
padding:0 1em 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media(max-width:632px)
|
|
||||||
{
|
|
||||||
/*-----------------------------------
|
|
||||||
footer
|
|
||||||
-------------------------------------*/
|
|
||||||
#footer
|
|
||||||
{
|
|
||||||
height:auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#footer select
|
|
||||||
{
|
|
||||||
margin-top:8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,489 +0,0 @@
|
|||||||
|
|
||||||
/*=============================================================
|
|
||||||
GENERAL
|
|
||||||
==============================================================*/
|
|
||||||
body { /*remember to account for the hidden area underneath
|
|
||||||
fixed navbar by adding at least 40px padding to the <body>.
|
|
||||||
Be sure to add this after the core Bootstrap CSS
|
|
||||||
and before the optional responsive CSS.
|
|
||||||
An alternative solution is to set top-margin to div#main padding-top:60px; comment this for alternative solution*/ height:auto; /*uncomment this for alternative solution*/ }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
BOOTSTRAP ICONS FOLDER FIX
|
|
||||||
==============================================================*/
|
|
||||||
[class^="icon-"], [class*=" icon-"] { /* right folder for bootstrap black images/icons*/ background-image:url("../images/glyphicons-halflings.png") }
|
|
||||||
|
|
||||||
.icon-white, .nav-tabs>.active >a>[class^="icon-"], .nav-tabs>.active>a>[class*=" icon-"], .nav-pills>.active>a>[class^="icon-"], .nav-pills>.active>a>[class*=" icon-"], .nav-list>.active>a>[class^="icon-"], .nav-list>.active>a>[class*=" icon-"], .navbar-inverse .nav>.active>a>[class^="icon-"], .navbar-inverse .nav>.active>a>[class*=" icon-"], .dropdown-menu>li>a:hover>[class^="icon-"], .dropdown-menu>li>a:hover>[class*=" icon-"], .dropdown-menu>.active>a>[class^="icon-"], .dropdown-menu>.active>a>[class*=" icon-"] { /* right folder for bootstrap white images/icons*/ background-image:url("../images/glyphicons-halflings-white.png"); }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
INPUT BORDER HIGHLIGHT WHEN INPUT IS FOCUSED
|
|
||||||
==============================================================*/
|
|
||||||
textarea:focus, input[type="text"]:focus, input[type="password"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus, input[type="file"]:focus, select:focus, .uneditable-input:focus { /* outline color*/ border-color:rgba(232, 149, 60, 0.8); outline:0; /*outline:thin dotted \9;*/ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(232, 149, 60, 0.6); -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(232, 149, 60, 0.6); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(232, 149, 60, 0.6); }
|
|
||||||
|
|
||||||
.web2py_grid .dropdown-menu li > a:hover, .web2py_grid .dropdown-menu li > a:focus { filter:progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9*/ background-image:none; background-color:#E8953C; }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
COLOR OF LINKS
|
|
||||||
==============================================================*/
|
|
||||||
a, a:hover { color:#E8953C; text-decoration:none; }
|
|
||||||
|
|
||||||
a:hover { color:#e2821b; }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
CONTROLS and CONTAINERS
|
|
||||||
==============================================================*/
|
|
||||||
.row-buttons .btn { margin-bottom:7px; }
|
|
||||||
|
|
||||||
.sidebar .box { clear:right; margin-top:2em; border-top:1px solid #d1d1d1; padding:0 1em; }
|
|
||||||
|
|
||||||
.pwdchange>.button { margin-bottom:10px; }
|
|
||||||
|
|
||||||
input[type="file"] { margin-bottom:9px; }
|
|
||||||
|
|
||||||
.form-inline input[type="file"] { margin-bottom:0px; }
|
|
||||||
|
|
||||||
input + .help-block { margin-top:-10px; margin-bottom:4px; }
|
|
||||||
|
|
||||||
#confirm_form input.btn, .generatedbyw2p input { margin-right:4px; }
|
|
||||||
|
|
||||||
a[rel='tooltip'] span, div[rel='tooltip'] span { display:none; margin-left:-9999px; }
|
|
||||||
|
|
||||||
/*in-page browsing*/
|
|
||||||
[rel="pagebookmark"] { position:relative; }
|
|
||||||
|
|
||||||
[rel="pagebookmark"]>.component { cursor:pointer; }
|
|
||||||
|
|
||||||
[rel="pagebookmark"]>.hashstick { position:absolute; top:-54px; left:-9999px; visibility:visible; }
|
|
||||||
|
|
||||||
/* following 2 rules set the style of a small button for going to top of page*/
|
|
||||||
.tophashlink.btn { padding:2px 3px; visibility:hidden; }
|
|
||||||
|
|
||||||
.hashstick:target+.tophashlink.btn { visibility:visible; }
|
|
||||||
|
|
||||||
ul.act_edit { margin-top:4px; margin-left:20px; }
|
|
||||||
|
|
||||||
ul.act_edit .btn { margin-top:4px; margin-bottom:4px; }
|
|
||||||
|
|
||||||
ul.act_edit .file>a { white-space:pre; }
|
|
||||||
|
|
||||||
.right-full { text-align:right; }
|
|
||||||
|
|
||||||
.searchbox, .searchbox label, .searchbox input { display:inline-block; }
|
|
||||||
|
|
||||||
.buttons-row .btn { margin-bottom:9px; }
|
|
||||||
|
|
||||||
.li-controls { display:inline-block; width:180px; vertical-align:middle; }
|
|
||||||
|
|
||||||
.celled { display:inline-block; padding: 0 0 0 4px; vertical-align:top; margin-top:4px; width:700px; }
|
|
||||||
|
|
||||||
.folder { list-style-type:none; #border-left: 1px dotted #AAA; }
|
|
||||||
|
|
||||||
.folder li { list-style-type:none; }
|
|
||||||
|
|
||||||
.folder>i { display:inline-block; width:5px; height:5px; border:1px solid; background-color:#FAA732; margin-left:-4px; margin-top:-2px; border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); border-radius:1px; }
|
|
||||||
|
|
||||||
.folder>i+a { padding-left:0.5em; }
|
|
||||||
|
|
||||||
.folder ul { margin-top:0.5em; margin-bottom:0.5em; }
|
|
||||||
|
|
||||||
.controls-inline .btn { margin-right:5px; }
|
|
||||||
|
|
||||||
div.web2py_counter.span6 { min-height:24px; text-align:right; }
|
|
||||||
|
|
||||||
.pagination { margin:0; }
|
|
||||||
|
|
||||||
.table { margin-bottom:10px; }
|
|
||||||
|
|
||||||
.row_buttons .btn { margin-right:4px; }
|
|
||||||
|
|
||||||
.editor-bar-column { display:inline-block; vertical-align:top; margin-right:4px; }
|
|
||||||
|
|
||||||
.editor-bar-column .input-long { width:270px; }
|
|
||||||
|
|
||||||
.editor-bar-column .input-normal { width:206px; }
|
|
||||||
|
|
||||||
.keybindings li { margin-bottom:0.5em; }
|
|
||||||
|
|
||||||
.keybindings span { padding:0.3em; border:1px solid transparent; vertical-align:middle; }
|
|
||||||
|
|
||||||
.teletype-text { font-family:monospace; font-weight:bold; font-style:normal; border-color:#999; background:#333; color:#DDD; -moz-border-radius:0.3em; border-radius:0.3em; }
|
|
||||||
|
|
||||||
.edit_language .tab_row div { display:inline-block; vertical-align:top; margin-right:4px; }
|
|
||||||
|
|
||||||
.edit_language .fake-input { height:18px; padding:4px; font-size:13px; line-height:18px; overflow:hidden; white-space:nowrap; display:inline-block; margin-bottom:9px; }
|
|
||||||
|
|
||||||
.test h3 { padding-left:9px; margin:0; font-size:16px; line-height:1; border-left:9px solid transparent; }
|
|
||||||
|
|
||||||
.test h3.passed { border-color:#009900; }
|
|
||||||
|
|
||||||
.test h3.failed { border-color:#CC0000; }
|
|
||||||
|
|
||||||
.test h3.nodoctests { border-color:#CCCC99; }
|
|
||||||
|
|
||||||
.test .test_report { width:100%; overflow:auto; }
|
|
||||||
|
|
||||||
.test_report pre { white-space:pre; }
|
|
||||||
|
|
||||||
.test div[id^="output_"]>h2 { font-size:18px; line-height:1; color:grey; }
|
|
||||||
|
|
||||||
div.center { text-align:center; }
|
|
||||||
|
|
||||||
.delete h2 { word-wrap:break-word; }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
SHELL
|
|
||||||
==============================================================*/
|
|
||||||
.shell .output-wrapper { width:100%; height:30em; border:1px solid #333; }
|
|
||||||
|
|
||||||
.shell .prompt-wrapper { float:left; width:100%; overflow:hidden; height:auto; border:1px solid #333; }
|
|
||||||
|
|
||||||
.shell .prompt-container { margin-left:2.5em; }
|
|
||||||
|
|
||||||
.shell #caret { width:2.5em; float:left; margin-left:-100%; }
|
|
||||||
|
|
||||||
.shell #shellwrapper { background:white; color:#E8953C; width:100%; margin:1em 0; border:0; }
|
|
||||||
|
|
||||||
.shell #output, .shell .prompt { color:#E8953C; background:white; resize:none; border:none; width:100%; height:100%; cursor:default; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
|
|
||||||
|
|
||||||
.shell #output:focus, .shell .prompt:focus { border-color:transparent; outline:0; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }
|
|
||||||
|
|
||||||
.shell #output pre { color: #E8953C; }
|
|
||||||
|
|
||||||
.shell #autoscroll { cursor:pointer; float:right; }
|
|
||||||
|
|
||||||
.shell .prompt, .shell #output, .shell #caret { font-size: 11pt; padding: 6px; padding-right: 0em; }
|
|
||||||
|
|
||||||
.shell #caret { padding-top:9px; }
|
|
||||||
|
|
||||||
.shell .prompt, .shell #output, .shell pre, .shell #caret { font-family: monospace; }
|
|
||||||
|
|
||||||
.shell a[rel="tooltip"] { margin-left:8px; }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
PEEK
|
|
||||||
==============================================================*/
|
|
||||||
.peek .code-wrapper { width:100%; overflow:auto; white-space:pre; }
|
|
||||||
|
|
||||||
.peek table td pre { word-break:normal; white-space:pre; }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
FOOTER
|
|
||||||
==============================================================*/
|
|
||||||
#footer { border-top:1px solid; text-align:center; padding:1em 0; }
|
|
||||||
|
|
||||||
#footer span, #footer select { display:inline-block; margin-bottom:0; vertical-align:middle; }
|
|
||||||
|
|
||||||
#footer select { width:auto; }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
MAIN
|
|
||||||
==============================================================*/
|
|
||||||
#main { margin-top:60px; /*uncomment this for alternative solution to hidden area underneath fixed navbar issue*/ margin-bottom:60px; }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
WIZARD
|
|
||||||
==============================================================*/
|
|
||||||
#wizard_nav .box { border-bottom:1px dotted; }
|
|
||||||
|
|
||||||
#wizard_nav li { margin-left:1em; margin-top:0.5em; }
|
|
||||||
|
|
||||||
.step textarea { width:auto; }
|
|
||||||
|
|
||||||
select[name='layout_theme'] { vertical-align:top; }
|
|
||||||
|
|
||||||
img#preview { margin-bottom:9px; }
|
|
||||||
|
|
||||||
/* multiselect customization*/
|
|
||||||
.ms-container { margin-bottom:5px; }
|
|
||||||
|
|
||||||
.ms-selectable, .step .ms-selection { text-align:center; }
|
|
||||||
|
|
||||||
.ms-list { text-align:left; background:white; }
|
|
||||||
|
|
||||||
.ms-container li.ms-elem-selectable:not(.disabled).ms-hover, .ms-container .ms-selection li:not(.disabled).ms-hover { background-color:#E8953C; }
|
|
||||||
|
|
||||||
.ms-container .ms-selectable { margin-right:25px; }
|
|
||||||
|
|
||||||
.ms-container .ms-selectable, .ms-container .ms-selection { background:transparent; }
|
|
||||||
|
|
||||||
.ms-container .ms-list.ms-focus { border-color:rgba(232, 149, 60, 0.8); -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(232, 149, 60, 0.6); -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(232, 149, 60, 0.6); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(232, 149, 60, 0.6); }
|
|
||||||
|
|
||||||
/* grow_input*/
|
|
||||||
ul[id$="_grow_input"] { margin-left:0; }
|
|
||||||
|
|
||||||
/* generate_form*/
|
|
||||||
#generate_form .control-group { margin-bottom:0; }
|
|
||||||
|
|
||||||
#generate_form .control-label { text-align:left; }
|
|
||||||
|
|
||||||
#generate_form .controls { padding-left:18px; margin-left:0; }
|
|
||||||
|
|
||||||
#generate_form .control-label.empty { width:142px; }
|
|
||||||
|
|
||||||
.step [rel="pagebookmark"]>.hashstick { display:none; }
|
|
||||||
|
|
||||||
/*generated page*/
|
|
||||||
.generated iframe { border:1px inset #e3e3e3; }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
ERRORS TABLE / TICKET PAGE
|
|
||||||
==============================================================*/
|
|
||||||
.tablebar { margin:7px 0 7px 0; }
|
|
||||||
|
|
||||||
.tablebar input { margin-right:27px; }
|
|
||||||
|
|
||||||
.tablebar span { vertical-align:bottom; }
|
|
||||||
|
|
||||||
.table th { background: #e9e9e9; background: -moz-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #FAFAFA), color-stop(100%, #E9E9E9)); background: -webkit-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); background: -o-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); background: -ms-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); background: linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FAFAFA', endColorstr='#E9E9E9'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#FAFAFA', endColorstr='#E9E9E9')"; /*font-size:10px; color:#444; text-transform:uppercase;*/ }
|
|
||||||
|
|
||||||
td.cbcentered, th.cbcentered { text-align:center; }
|
|
||||||
|
|
||||||
td.cbcentered>input, th.cbcentered>input { margin-top:-1px; }
|
|
||||||
|
|
||||||
.traceback div { }
|
|
||||||
|
|
||||||
.ticket_code>table td:first-child { border-left:0; }
|
|
||||||
|
|
||||||
#trck_errors table td pre { word-break:normal; white-space:pre; }
|
|
||||||
|
|
||||||
.inspect pre, .errorsource pre { word-break:normal; white-space:pre; }
|
|
||||||
|
|
||||||
.ticket_code { background-color:lightyellow; }
|
|
||||||
|
|
||||||
.ticket_code table, .ticket_code td { border-width:0px; border-collapse:collapse; width:100%; }
|
|
||||||
|
|
||||||
.ticket_code tbody tr:hover td { background-color:transparent; }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
FLOT GRAPHS
|
|
||||||
==============================================================*/
|
|
||||||
.about #placeholder { width:auto; max-width:600px; height:300px; position:relative; margin:0 auto; /* for centering*/ }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
THE GRID
|
|
||||||
==============================================================*/
|
|
||||||
#w2p_query_panel { min-width:20px; min-height:20px; padding:10px; margin-top:1em; background-color:#f5f5f5; border: 1px solid #e3e3e3; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); }
|
|
||||||
|
|
||||||
#w2p_query_panel select, #w2p_query_panel input { margin-bottom:0; margin-right:4px; }
|
|
||||||
|
|
||||||
.web2py_grid .hidden { visibility:visible; }
|
|
||||||
|
|
||||||
.qry_pnl_btns { display:inline-block; }
|
|
||||||
|
|
||||||
#w2p_grid_addbtn, #w2p_search-form { margin-top:9px; margin-bottom:9px; }
|
|
||||||
|
|
||||||
#w2p_search-form { margin-bottom:0; }
|
|
||||||
|
|
||||||
#w2p_search-form form { margin-bottom:0; }
|
|
||||||
|
|
||||||
/*----- translate page ---*/
|
|
||||||
.languageform input { margin-bottom:0; }
|
|
||||||
|
|
||||||
.languageform input.untranslated { background-color:#FC0; }
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
MASKED UPLOAD INPUT (NO BOOTSTRAP RELATED)
|
|
||||||
==============================================================*/
|
|
||||||
#appupdate_file.masked {
|
|
||||||
margin: 0;
|
|
||||||
opacity: 0;
|
|
||||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /* IE 8 */
|
|
||||||
filter: alpha(opacity=0); /* IE 7 */
|
|
||||||
font-size: 100px;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 410;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fileselect {
|
|
||||||
padding: 4px 6px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #555;
|
|
||||||
cursor: default;
|
|
||||||
position: relative;
|
|
||||||
z-index: 400;
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: #fff;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fileselect span {
|
|
||||||
position: absolute;
|
|
||||||
left: 6px;
|
|
||||||
top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploadbtn {
|
|
||||||
position: absolute;
|
|
||||||
top: 3px;
|
|
||||||
right: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.txtPlaceholder {
|
|
||||||
font-style: italic;
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
EDIT PAGE SLIDING FILES MENU
|
|
||||||
==============================================================*/
|
|
||||||
@media (max-width: 979px) {
|
|
||||||
body.edit div#header {position:relative; z-index: 1030 !important;}
|
|
||||||
}
|
|
||||||
|
|
||||||
#editor_area, #edit_placeholder {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#editor_area {
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
#files {
|
|
||||||
width: auto;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
position: fixed;
|
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
z-index: 1031;
|
|
||||||
border-right: 3px solid #000;
|
|
||||||
/* animation (it doesn't work in IE<10) */
|
|
||||||
-moz-transition: all 0.4s;
|
|
||||||
-webkit-transition: all 0.4s;
|
|
||||||
-o-transition: all 0.4s;
|
|
||||||
transition: all 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#files:hover, #files:focus {
|
|
||||||
left: 0px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#files, .files-toggle {
|
|
||||||
background: #1b1b1b;
|
|
||||||
opacity: 0.98;
|
|
||||||
}
|
|
||||||
|
|
||||||
.files-toggle {
|
|
||||||
width: 18px;
|
|
||||||
height: 86px;
|
|
||||||
border-radius: 0px 4px 4px 0px;
|
|
||||||
color: #999;
|
|
||||||
position: absolute;
|
|
||||||
top: 60px;
|
|
||||||
right: -18px;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 8px;
|
|
||||||
width: 18px;
|
|
||||||
height: 70px;
|
|
||||||
background: url(../images/files_toggle.png) no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.files-menu {
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#filelist {
|
|
||||||
position: relative;
|
|
||||||
top: 60px;
|
|
||||||
padding-bottom: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#filelist li {
|
|
||||||
padding-right: 8px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#filelist li>a {
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
MEDIA QUERIES
|
|
||||||
==============================================================*/
|
|
||||||
@media (max-width: 800px) { .step [rel="pagebookmark"]>.hashstick { /*top:-54px;*/ display:block; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) { [rel="pagebookmark"]>.hashstick { top:0; }
|
|
||||||
|
|
||||||
/*-----------------------------------
|
|
||||||
main
|
|
||||||
-------------------------------------*/
|
|
||||||
#main { margin-top:0; }
|
|
||||||
|
|
||||||
/*-----------------------------------
|
|
||||||
footer
|
|
||||||
-------------------------------------*/
|
|
||||||
#footer { margin-left: -20px; margin-right: -20px; padding-left: 20px; padding-right: 20px; }
|
|
||||||
|
|
||||||
/*-----------------------------------
|
|
||||||
errors page
|
|
||||||
-------------------------------------*/
|
|
||||||
#trck_errors { table-layout:fixed; }
|
|
||||||
|
|
||||||
#trck_errors .column1 { width:20px; }
|
|
||||||
|
|
||||||
#trck_errors .column2 { width:45px; }
|
|
||||||
|
|
||||||
#trck_errors .column3 { width:150px; }
|
|
||||||
|
|
||||||
#trck_errors .columnN { width:55px; }
|
|
||||||
|
|
||||||
#trck_errors .columnN1 { width:138px; }
|
|
||||||
|
|
||||||
.ticket_code, .inspect.resp1, .inspect.controls pre, .errorsource { width:100%; overflow:auto; }
|
|
||||||
|
|
||||||
.ticket_code>table { width:100%; }
|
|
||||||
|
|
||||||
.celled { width:320px; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) { .qry_pnl_btns { display:block; margin-top:4px; }
|
|
||||||
|
|
||||||
/*-----------------------------------
|
|
||||||
wizard
|
|
||||||
-------------------------------------*/
|
|
||||||
#generate_form .control-label { float:left; width:160px; padding-top:5px; }
|
|
||||||
|
|
||||||
.inspect>code { display:block; white-space:normal; }
|
|
||||||
|
|
||||||
.li-controls { }
|
|
||||||
|
|
||||||
.celled { width:165px; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*-----------------------------------
|
|
||||||
miscellaneous
|
|
||||||
-------------------------------------*/
|
|
||||||
h4.editableapp, h4.currentapp { padding: 5px 0 5px 54px; display: inline; }
|
|
||||||
|
|
||||||
h4.editableapp { background: #fff url(../images/folder.png) no-repeat; }
|
|
||||||
|
|
||||||
h4.currentapp { background: #fff url(../images/folder_locked.png) no-repeat; }
|
|
||||||
|
|
||||||
.flash { position:fixed; width:50%; top:49px; left:25%; right:25%; cursor:default; text-align:center; z-index:5620; }
|
|
||||||
span#closeflash {position:absolute; top:1px; right:-1px; font-size:150%; border:1px solid black; border-color: transparent transparent #fbeed5 #fbeed5; border-radius: 0 0 0 4px; width:22px; }
|
|
||||||
span#closeflash:hover {font-weight:bold; cursor:pointer; }
|
|
||||||
|
|
||||||
table.twitter{ background-color: transparent; }
|
|
||||||
table.twitter tr td {vertical-align: top; padding: 5px; }
|
|
||||||
table.twitter tr { border-bottom: 1px solid #a0a0a0; }
|
|
||||||
|
|
||||||
div.error_wrapper {margin-top:-10px; margin-bottom:8px; padding-left:2px; color:#d62e2b; }
|
|
||||||
|
|
||||||
.twitter-timeline >iframe{padding: 1em 0;}
|
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
.calendar{z-index:99;position:relative;display:none;background:#fff;border:2px solid #000;font-size:11px;color:#000;cursor:default;font-family:Arial,Helvetica,sans-serif;
|
.calendar {z-index:2000;position:relative;margin-top:140px;display:none;background-color:white;border:1px solid #000;color:#000;cursor:default;box-shadow:0 0 10px #666}.calendar * {text-align: center;font-size:10px!important}
|
||||||
border-radius: 10px;
|
.calendar table {border-collapse:collapse}
|
||||||
-moz-border-radius: 10px;
|
.calendar tbody tr:hover {background-color:#fbf6d9}
|
||||||
-webkit-border-radius: 10px;
|
.calendar td, th {padding:5px; vertical-align:top; text-align:left; border:0}
|
||||||
}.calendar table{margin:0px;font-size:11px;color:#000;cursor:default;font-family:tahoma,verdana,sans-serif;}.calendar .button{text-align:center;padding:1px;color:#fff;background:#000;}.calendar .nav{background:#000;color:#fff}.calendar thead .title{font-weight:bold;padding:1px;background:#000;color:#fff;text-align:center;}.calendar thead .name{padding:2px;text-align:center;background:#bbb;}.calendar thead .weekend{color:#f00;}.calendar thead .hilite {background-color:#666;}.calendar thead .active{padding:2px 0 0 2px;background-color:#c4c0b8;}.calendar tbody .day{width:2em;text-align:right;padding:2px 4px 2px 2px;}.calendar tbody .day.othermonth{color:#aaa;}.calendar tbody .day.othermonth.oweekend{color:#faa;}.calendar table .wn{padding:2px 3px 2px 2px;background:#bbb;}.calendar tbody .rowhilite td{background:#ddd;}.calendar tbody td.hilite{background:#bbb;}.calendar tbody td.active{background:#bbb;}.calendar tbody td.selected{font-weight:bold;background:#ddd;}.calendar tbody td.weekend{color:#f00;}.calendar tbody td.today{font-weight:bold;color:#00f;}.calendar tbody .disabled{color:#999;}.calendar tbody .emptycell{visibility:hidden;}.calendar tbody .emptyrow{display:none;}.calendar tfoot .ttip{background:#bbb;padding:1px;background:#000;color:#fff;text-align:center;}.calendar tfoot .hilite{background:#ddd;}.calendar tfoot .active{}.calendar .combo{position:absolute;display:none;width:4em;top:0;left:0;cursor:default;background:#e4e0d8;padding:1px;z-index:100;}.calendar .combo .label,.calendar .combo .label-IEfix{text-align:center;padding:1px;}.calendar .combo .label-IEfix{width:4em;}.calendar .combo .active{background:#c4c0b8;}.calendar .combo .hilite{background:#048;color:#fea;}.calendar td.time{padding:1px 0;text-align:center;background-color:#bbb;}.calendar td.time .hour,.calendar td.time .minute,.calendar td.time .ampm{padding:0 3px 0 4px;font-weight:bold;}.calendar td.time .ampm{text-align:center;}.calendar td.time .colon{padding:0 2px 0 3px;font-weight:bold;}.calendar td.time span.hilite{}.calendar td.time span.active{border-color:#f00;background-color:#000;color:#0f0;}.hour,.minute{font-size:2em;}
|
.calendar thead tr {background-color:#f1f1f1}
|
||||||
|
.calendar tbody tr {border-bottom:2px solid #f1f1f1}
|
||||||
|
.calendar th {font-weight:string; padding:5px; vertical-align:bottom; text-align:left}
|
||||||
|
.calendar thead th {vertical-align:bottom}
|
||||||
|
.calendar tbody th {vertical-align:top}
|
||||||
|
|
||||||
#CP_hourcont{z-index:99;padding:0;position:absolute;border:1px dashed #666;background-color:#eee;display:none;}#CP_minutecont{z-index:99;background-color:#ddd;padding:1px;position:absolute;width:45px;display:none;}.floatleft{float:left;}.CP_hour{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;}.CP_minute{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;}.CP_over{background-color:#fff;z-index:99}
|
#CP_hourcont{z-index:2000;padding:0;position:absolute;border:1px dashed #666;background-color:#eee;display:none;}#CP_minutecont{z-index:2000;background-color:#ddd;padding:1px;position:absolute;width:45px;display:none;}.floatleft{float:left;}.CP_hour{z-index:2000;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;}.CP_minute{z-index:2000;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;}.CP_over{background-color:#fff;z-index:2000}
|
||||||
|
|||||||
@@ -0,0 +1,361 @@
|
|||||||
|
/************
|
||||||
|
Created by Massimo Di Pierro
|
||||||
|
Stupid.css is what the names says, take it with a grain of salt
|
||||||
|
License: BSD
|
||||||
|
************/
|
||||||
|
|
||||||
|
/*** basic styles ***/
|
||||||
|
* {border:0; margin:0; padding:0; font-familiy:Helvetica}
|
||||||
|
html, body {max-width: 100vw !important;overflow-x: hidden !important}
|
||||||
|
body {font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif}
|
||||||
|
p, li {margin-bottom:0.5em}
|
||||||
|
p {text-align:justify}
|
||||||
|
label, strong {font-weight:bold}
|
||||||
|
ul {list-style-type:none; padding-left:20px}
|
||||||
|
a {text-decoration:none; color:#26a69a; white-space:nowrap}
|
||||||
|
a:hover {cursor:pointer}
|
||||||
|
h1,h2,h3,h4,h5,h6{font-weight:strong; text-transform:uppercase}
|
||||||
|
h1{font-size:4em; margin:1.0em 0 0.25em 0}
|
||||||
|
h2{font-size:2.4em; margin:0.9em 0 0.25em 0}
|
||||||
|
h3{font-size:1.8em; margin:0.8em 0 0.25em 0}
|
||||||
|
h4{font-size:1.6em; margin:0.7em 0 0.25em 0}
|
||||||
|
h5{font-size:1.4em; margin:0.6em 0 0.25em 0}
|
||||||
|
h6{font-size:1.2em; margin:0.5em 0 0.25em 0}
|
||||||
|
table {border-collapse:collapse}
|
||||||
|
tbody tr:hover {background-color:#fbf6d9}
|
||||||
|
td, th {padding:5px; vertical-align:top; text-align:left; border:0}
|
||||||
|
thead tr {background-color:#f1f1f1}
|
||||||
|
tbody tr {border-bottom:2px solid #f1f1f1}
|
||||||
|
th {font-weight:string; padding:5px; vertical-align:bottom; text-align:left}
|
||||||
|
thead th {vertical-align:bottom}
|
||||||
|
tbody th {vertical-align:top}
|
||||||
|
header, footer {with:100%}
|
||||||
|
|
||||||
|
@media (max-width:599px) {
|
||||||
|
h1{font-size:2em}
|
||||||
|
h2{font-size:1.8em}
|
||||||
|
h3{font-size:1.6em}
|
||||||
|
h4{font-size:1.4em}
|
||||||
|
h5{font-size:1.2em}
|
||||||
|
h6{font-size:1.0em}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** buttons ***/
|
||||||
|
.btn, button, [type=button], [type=submit] {padding:0.5em 1em !important; margin:0 0.5em 0.5em 0; line-height:2.4em; background-color:#26a69a; color:white}
|
||||||
|
.btn:hover, button:hover, [type=button]:hover, [type=submit]:hover {box-shadow:0 0 10px #666; text-decoration:none; cursor:pointer}
|
||||||
|
.btn.small, table .btn {padding:0.25em 0.5em !important; font-size:0.8em; line-height:1.5em}
|
||||||
|
.btn.large {padding:1em 2em !important; font-size:1.2em; line-height:4em}
|
||||||
|
.btn.oval {border-radius:50%}
|
||||||
|
|
||||||
|
/*** helpers ***/
|
||||||
|
.rounded {-moz-border-radius:5px; border-radius:5px}
|
||||||
|
.padded {padding:10px 20px !important; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box}
|
||||||
|
.center {text-align:center !important; margin-left:auto; margin-right:auto}
|
||||||
|
.center>div {text-align:left}
|
||||||
|
.right {right:0; text-align:right}
|
||||||
|
.middle div {vertical-align:middle !important}
|
||||||
|
.bottom div {vertical-align:bottom !important}
|
||||||
|
.xscroll {overflow-x:scroll; width:100%}
|
||||||
|
.yscroll {overflow-y:scroll; width:100%}
|
||||||
|
.nowrap {white-space:nowrap; overflow-x:hidden}
|
||||||
|
.fill {width:100%}
|
||||||
|
.lifted {box-shadow:5px 5px 10px #666}
|
||||||
|
.relative {position:relative}
|
||||||
|
.relative>div {position:absolute}
|
||||||
|
.spaced {margin-bottom:20px; margin-top:20px}
|
||||||
|
.hidden {display:none}
|
||||||
|
|
||||||
|
/*** forms ***/
|
||||||
|
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit]), [type=file]:before {outline:none; padding:0.5em 1em; margin:0.5px; border-bottom:1px solid #ddd; width:100%}
|
||||||
|
textarea {width:100%; border:1px solid #ddd; padding:4px 8px; outline:none; outline:none}
|
||||||
|
select {-webkit-appearance:none; outline:none; padding:0.5em 1em; border-radius:0; margin:0.5px; border-bottom:1px solid #ddd}
|
||||||
|
input, textarea, select, button {font-size:12px}
|
||||||
|
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit]):hover, select:hover, textarea:hover {background-color:#fbf6d9; transition:background-color 1s ease}
|
||||||
|
|
||||||
|
/*** grid ***/
|
||||||
|
.container {margin-right:-20px}
|
||||||
|
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters, .container>.fill{display:inline-block; padding:5px 20px 5px 0; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; vertical-align:top}
|
||||||
|
.container>.fill {width:100%; margin-right:-20px}
|
||||||
|
.container img, .container video {max-width:100%}
|
||||||
|
@media (min-width:800px) {
|
||||||
|
.max900 {max-width:900px; margin-left:auto; margin-right:auto}
|
||||||
|
.quarter {width:25%; margin-right:-5px}
|
||||||
|
.half {width:50%; margin-right:-10px}
|
||||||
|
.third {width:33.33%; margin-right:-6.66px}
|
||||||
|
.twothirds {width:66.66%; margin-right:-13.33px}
|
||||||
|
.threequarters {width:75%; margin-right:-15px}
|
||||||
|
}
|
||||||
|
@media (min-width:600px) and (max-width:799px) {
|
||||||
|
.quarter.compressible {width:25%; margin-right:-5px}
|
||||||
|
.half.compressible {width:50%; margin-right:-10px}
|
||||||
|
.threequarters.compressible {width:75%; margin-right:-15px}
|
||||||
|
.quarter:not(.compressible), .half:not(.compressible), .threequarters:not(.compressible) {width:100%; margin-right:-20px}
|
||||||
|
.third {width:33.33%; margin-right:-6.66px}
|
||||||
|
.twothirds {width:66.66%; margin-right:-13.33px}
|
||||||
|
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right {float:left; text-align:left}
|
||||||
|
}
|
||||||
|
@media (max-width:599px) {
|
||||||
|
.quarter:not(.compressible), .half:not(.compressible), .third:not(.compressible), .twothirds:not(.compressible), .threequarters:not(.compressible) {width:100%;}
|
||||||
|
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right,
|
||||||
|
label.third:not(.compressible).right, label.twothirds:not(.compressible).right {float:left; text-align:left}
|
||||||
|
.quarter.compressible {width:25%; margin-right:-5px}
|
||||||
|
.half.compressible {width:50%; margin-right:-10px}
|
||||||
|
.third.compressible {width:33.33%; margin-right:-6.66px}
|
||||||
|
.twothirds.compressible {width:66.66%; margin-right:-13.33px}
|
||||||
|
.threequarters.compressible {width:75%; margin-right:-15px}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** progress bar from http://codepen.io/holdencreative/details/pvxGxy ***/
|
||||||
|
.progress {
|
||||||
|
margin-left:-15px;
|
||||||
|
margin-right:-15px;
|
||||||
|
position:relative;
|
||||||
|
height:8px;
|
||||||
|
display:block;
|
||||||
|
width:120%;
|
||||||
|
background-color:#acece6;
|
||||||
|
border-radius:0 !important;
|
||||||
|
background-clip:padding-box;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
.progress .determinate {
|
||||||
|
position:absolute;
|
||||||
|
background-color:inherit;
|
||||||
|
top:0;
|
||||||
|
bottom:0;
|
||||||
|
background-color:#26a69a;
|
||||||
|
transition:width .3s linear;
|
||||||
|
}
|
||||||
|
.progress .indeterminate {
|
||||||
|
background-color:#26a69a;
|
||||||
|
}
|
||||||
|
.progress .indeterminate:before {
|
||||||
|
content:'';
|
||||||
|
position:absolute;
|
||||||
|
background-color:inherit;
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
|
bottom:0;
|
||||||
|
will-change:left, right;
|
||||||
|
animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
|
||||||
|
}
|
||||||
|
.progress .indeterminate:after {
|
||||||
|
content:'';
|
||||||
|
position:absolute;
|
||||||
|
background-color:inherit;
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
|
bottom:0;
|
||||||
|
will-change:left, right;
|
||||||
|
animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
|
||||||
|
animation-delay:1.15s;
|
||||||
|
}
|
||||||
|
@-webkit-keyframes indeterminate {
|
||||||
|
0% {left:-35%; right:100%}
|
||||||
|
60% {left:100%; right:-90%}
|
||||||
|
100% {left:100%; right:-90%}
|
||||||
|
}
|
||||||
|
@-moz-keyframes indeterminate {
|
||||||
|
0% {left:-35%; right:100%}
|
||||||
|
60% {left:100%; right:-90%}
|
||||||
|
100% {left:100%; right:-90%}
|
||||||
|
}
|
||||||
|
@keyframes indeterminate {
|
||||||
|
0% {left:-35%; right:100%}
|
||||||
|
60% {left:100%; right:-90%}
|
||||||
|
100% {left:100%; right:-90%}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes indeterminate-short {
|
||||||
|
0% {left:-200%; right:100%}
|
||||||
|
60% {left:107%; right:-8%}
|
||||||
|
100% {left:107%; right:-8%}
|
||||||
|
}
|
||||||
|
@-moz-keyframes indeterminate-short {
|
||||||
|
0% {left:-200%; right:100%}
|
||||||
|
60% {left:107%; right:-8%}
|
||||||
|
100% {left:107%; right:-8%}
|
||||||
|
}
|
||||||
|
@keyframes indeterminate-short {
|
||||||
|
0% {left:-200%; right:100%}
|
||||||
|
60% {left:107%; right:-8%}
|
||||||
|
100% {left:107%; right:-8%}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** dropdown menu from http://codepen.io/philhoyt/pen/ujHzd ***/
|
||||||
|
.menu {list-style:none; position:relative; margin:0; padding:0}
|
||||||
|
.menu.right {float:right}
|
||||||
|
.menu a {padding:0 15px; text-decoration:none;text-align:left;font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; text-align:left}
|
||||||
|
.menu li {position:relative; float:left; margin:0; padding:0}
|
||||||
|
.menu ul {background:white; border:1px solid #e1e1e1; visibility:hidden; opacity:0; position:absolute; top:110%; padding:0; z-index:1000; transition:all 0.2s ease-out; list-style-type:none; box-shadow:5px 5px 10px #666}
|
||||||
|
.menu ul a {padding:10px 15px; color:#333; font-weight:700; font-size:12px; line-height:16px; display: block}
|
||||||
|
.menu ul li {float:none; width:200px}
|
||||||
|
.menu ul ul {top:0; left:80%; z-index:2000}
|
||||||
|
.menu li:hover > ul {visibility:visible; opacity:1}
|
||||||
|
.menu>li>ul>li:first-child:before{content:''; position:absolute; width:1px; height:1px; border:10px solid transparent; left:50px; top:-20px; margin-left:-10px; border-bottom-color:white}
|
||||||
|
.menu.dark ul {background:black; border:1px solid black}
|
||||||
|
.menu.dark ul a {color:white}
|
||||||
|
.menu.dark>li>ul>li:first-child:before{border-bottom-color:black}
|
||||||
|
|
||||||
|
@media (max-width:599px) {
|
||||||
|
header .menu li, header .menu ul {width: 100%}
|
||||||
|
header .menu.right {float:left; text-align:left}
|
||||||
|
header .menu ul ul {top:2.5em; left:-1px; z-index:2000}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width:600px) {
|
||||||
|
.ham {display:none!important}
|
||||||
|
.burger.accordion * {max-height:1000px; overflow:visible}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** pulsating ring from https://jsfiddle.net/mandynicole/7xrKP/ *******/
|
||||||
|
.pulse:after {
|
||||||
|
content:"";
|
||||||
|
border:3px solid #00e6ac;
|
||||||
|
-webkit-border-radius:30px;
|
||||||
|
height:40px;
|
||||||
|
width:40px;
|
||||||
|
position:absolute;
|
||||||
|
margin-left:-20px;
|
||||||
|
margin-top:-20px;
|
||||||
|
-webkit-animation:pulsate 1s ease-out;
|
||||||
|
-webkit-animation-iteration-count:infinite;
|
||||||
|
opacity:0.0
|
||||||
|
}
|
||||||
|
@-webkit-keyframes pulsate {
|
||||||
|
0% {-webkit-transform:scale(0.1, 0.1); opacity:0.0}
|
||||||
|
50% {opacity:1.0}
|
||||||
|
100% {-webkit-transform:scale(1.2, 1.2); opacity:0.0}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** underline effect ***/
|
||||||
|
a:not(.btn):not(.noeffect) {position:relative}
|
||||||
|
a:not(.btn):not(.noeffect):hover {color:#26a69a}
|
||||||
|
a:not(.btn):not(.noeffect):hover:after {width:100%}
|
||||||
|
a:not(.btn):not(.noeffect):after {
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
left:0;
|
||||||
|
bottom:-1px;
|
||||||
|
width:0;
|
||||||
|
height:2px;
|
||||||
|
background-color:#26a69a;
|
||||||
|
content:"";
|
||||||
|
transition:width 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** modal ***/
|
||||||
|
.modal {
|
||||||
|
position:fixed;
|
||||||
|
z-index:9999;
|
||||||
|
top:0;
|
||||||
|
bottom:0;
|
||||||
|
left:0;
|
||||||
|
right:0;
|
||||||
|
background-color:rgba(0,0,0,0.8);
|
||||||
|
padding-top:20vh;
|
||||||
|
transition:opacity 500ms;
|
||||||
|
visibility:hidden;
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
.modal:target {visibility:visible; opacity:1}
|
||||||
|
.modal div {margin-left:auto; margin-right:auto}
|
||||||
|
.modal .close:not(.btn) {position:absolute; top:10px; right:10px; font-size:20px}
|
||||||
|
.modal .close {transition:all 200ms}
|
||||||
|
|
||||||
|
/*** tooltips from http://codepen.io/trezy/pen/Khnzy ***/
|
||||||
|
[data-tooltip] {position:relative}
|
||||||
|
[data-tooltip]:hover:after,[data-tooltip]:hover:before {display:block}
|
||||||
|
[data-tooltip]:before, [data-tooltip]:after {display:none; position:absolute; top:0}
|
||||||
|
[data-tooltip]:hover:before {
|
||||||
|
border-bottom:.6em solid black;
|
||||||
|
border-bottom:.6em solid black;
|
||||||
|
border-left:7px solid transparent;
|
||||||
|
border-right:7px solid transparent;
|
||||||
|
content:"";
|
||||||
|
left:5px;
|
||||||
|
margin-top:1em;
|
||||||
|
}
|
||||||
|
[data-tooltip]:hover:after {
|
||||||
|
background-color:rgba(0,0,0,0.8) !important;
|
||||||
|
border:4px solid rgba(0,0,0,0.8) !important;
|
||||||
|
border-radius:7px !important;
|
||||||
|
color:white !important;
|
||||||
|
content:attr(data-tooltip);
|
||||||
|
text-transform:none;
|
||||||
|
font-size: 11px;
|
||||||
|
left:0 !important;
|
||||||
|
margin-top:1.5em;
|
||||||
|
padding:5px 15px;
|
||||||
|
white-space:pre-wrap;
|
||||||
|
width:100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** accordion ***/
|
||||||
|
.accordion>input ~ label:before {content:"▲ "; color:#ddd}
|
||||||
|
.accordion>input:checked ~ label:before {content:"▼ "; color:#ddd}
|
||||||
|
.accordion>input {display:none}
|
||||||
|
.accordion>input:checked ~ *:not(label) {
|
||||||
|
max-height: 1000px !important;
|
||||||
|
overflow:visible !important;
|
||||||
|
-webkit-transition: max-height .3s ease-in;
|
||||||
|
transition: max-height .3s ease-in;
|
||||||
|
}
|
||||||
|
.accordion>*:not(label) {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-transition: max-height .3s ease-out;
|
||||||
|
transition: max-height .3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*** cards from http://codepen.io/edeesims/pen/iGDzk ***/
|
||||||
|
.card {perspective: 500px; max-width:100%}
|
||||||
|
.card>div {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-shadow: 0 0 15px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 1s;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
.card:hover>div {
|
||||||
|
transform: rotateY( 180deg ) ;
|
||||||
|
transition: transform 0.5s;
|
||||||
|
}
|
||||||
|
.card>div>div {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
}
|
||||||
|
.card>div>div:nth-child(2) {
|
||||||
|
transform: rotateY( 180deg );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** colors from http://clrs.cc/ ***/
|
||||||
|
.navy{background-color:#001f3f!important;color:white}.blue{background-color:#0074d9!important;color:white}.aqua{background-color:#7fdbff!important;color:black}.teal{background-color:#39cccc!important;color:white}.olive{background-color:#3d9970!important;color:white}.green{background-color:#2ecc40!important;color:white}.aquamarine{background-color:#26a69a!important;color:white}.lime{background-color:#01ff70!important;color:black}.yellow{background-color:#ffdc00!important;color:black}.orange{background-color:#ff851b!important;color:white}.red{background-color:#cc1f00!important;color:white}.fuchsia{background-color:#f012be!important;color:white}.pink{background-color:#ee6e73!important;color:white}.purple{background-color:#b10dc9!important;color:white}.maroon{background-color:#85144b!important;color:white}.white{background-color:#fff!important;color:black;-webkit-box-shadow:inset 0px 0px 0px 1px #ddd;-moz-box-shadow:inset 0px 0px 0px 1px #ddd;box-shadow:inset 0px 0px 0px 1px #ddd}.gray{background-color:#aaa!important;color:white}.silver{background-color:#f1f1f1!important;color:black}.black{background-color:#000!important;color:white}.glass{background:rgba(255,255,255,0.5)!important;color:black}
|
||||||
|
|
||||||
|
/**** tags ****/
|
||||||
|
.tags > span, .tags > span.off:hover {
|
||||||
|
height: 30px;
|
||||||
|
padding: 4px 9px;
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 0 5px 30px 0 !important;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: white;
|
||||||
|
background-color: #26a69a;
|
||||||
|
border-radius: 5px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
.tags.dismissible > span:not(.off):hover {
|
||||||
|
background-color: #ccc !important;
|
||||||
|
}
|
||||||
|
.tags.dismissible > span:not(.off):after {
|
||||||
|
content: " \f00d";
|
||||||
|
font-family: FontAwesome;
|
||||||
|
}
|
||||||
|
.tags > span.off {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
@@ -1,84 +1,17 @@
|
|||||||
/** these MUST stay **/
|
header a {color: white; font-size:1.1em}
|
||||||
a {text-decoration:none; white-space:nowrap}
|
main {min-height: 70vh}
|
||||||
a:hover {text-decoration:underline}
|
.form-group {padding-bottom: 10px !important;}
|
||||||
a.button {text-decoration:none}
|
.w2p_hidden {display:none;visibility:visible}
|
||||||
h1,h2,h3,h4,h5,h6 {margin:0.5em 0 0.25em 0; display:block;
|
|
||||||
font-family:Helvetica}
|
|
||||||
h1 {font-size:4.00em}
|
|
||||||
h2 {font-size:3.00em}
|
|
||||||
h3 {font-size:2.00em}
|
|
||||||
h4 {font-size:1.50em}
|
|
||||||
h5 {font-size:1.25em}
|
|
||||||
h6 {font-size:1.12em}
|
|
||||||
th,label {font-weight:bold; white-space:nowrap;}
|
|
||||||
td,th {text-align:left; padding:2px 5px 2px 5px}
|
|
||||||
th {vertical-align:middle; border-right:1px solid white}
|
|
||||||
td {vertical-align:top}
|
|
||||||
form table tr td label {text-align:left}
|
|
||||||
p,table,ol,ul {padding:0; margin: 0.75em 0}
|
|
||||||
p {text-align:justify}
|
|
||||||
ol, ul {list-style-position:outside; margin-left:2em}
|
|
||||||
li {margin-bottom:0.5em}
|
|
||||||
span,input,select,textarea,button,label,a {display:inline}
|
|
||||||
img {border:0}
|
|
||||||
blockquote,blockquote p,p blockquote {
|
|
||||||
font-style:italic; margin:0.5em 30px 0.5em 30px; font-size:0.9em}
|
|
||||||
i,em {font-style:italic}
|
|
||||||
strong {font-weight:bold}
|
|
||||||
small {font-size:0.8em}
|
|
||||||
code {font-family:Courier}
|
|
||||||
textarea {width:100%}
|
|
||||||
video {width:400px}
|
|
||||||
audio {width:200px}
|
|
||||||
[type="text"], [type="password"], select {
|
|
||||||
margin-right: 5px; width: 300px;
|
|
||||||
}
|
|
||||||
.hidden {display:none;visibility:visible}
|
|
||||||
.right {float:right; text-align:right}
|
.right {float:right; text-align:right}
|
||||||
.left {float:left; text-align:left}
|
.left {float:left; text-align:left}
|
||||||
.center {width:100%; text-align:center; vertical-align:middle}
|
.center {width:100%; text-align:center; vertical-align:middle}
|
||||||
/** end **/
|
|
||||||
|
|
||||||
/* Sticky footer begin */
|
|
||||||
|
|
||||||
.main {
|
|
||||||
padding:20px 0 50px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer,.push {
|
|
||||||
height:6em;
|
|
||||||
padding:1em 0;
|
|
||||||
clear:both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-content {position:relative; bottom:-4em; width:100%}
|
|
||||||
|
|
||||||
.auth_navbar {
|
|
||||||
white-space:nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sticky footer end */
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
border-top:1px #DEDEDE solid;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
/* background:<fill here for header image>; */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fieldset {padding:16px; border-top:1px #DEDEDE solid}
|
|
||||||
fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4px 16px; background:#f1f1f1}
|
|
||||||
|
|
||||||
/* fix ie problem with menu */
|
|
||||||
|
|
||||||
td.w2p_fw {padding-bottom:1px}
|
td.w2p_fw {padding-bottom:1px}
|
||||||
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
|
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
|
||||||
td.w2p_fl {text-align:left}
|
td.w2p_fl {text-align:left}
|
||||||
td.w2p_fl, td.w2p_fw {padding-right:7px}
|
td.w2p_fl, td.w2p_fw {padding-right:7px}
|
||||||
td.w2p_fl,td.w2p_fc {padding-top:4px}
|
td.w2p_fl,td.w2p_fc {padding-top:4px}
|
||||||
div.w2p_export_menu {margin:5px 0}
|
div.w2p_export_menu {white-space: wrap; margin:5px 0}
|
||||||
div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px;}
|
div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px; font-size:0.7em; color: black}
|
||||||
|
|
||||||
/* tr#submit_record__row {border-top:1px solid #E5E5E5} */
|
/* tr#submit_record__row {border-top:1px solid #E5E5E5} */
|
||||||
#submit_record__row td {padding-top:.5em}
|
#submit_record__row td {padding-top:.5em}
|
||||||
@@ -88,54 +21,30 @@ div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; pa
|
|||||||
#web2py_user_form td {vertical-align:top}
|
#web2py_user_form td {vertical-align:top}
|
||||||
|
|
||||||
/*********** web2py specific ***********/
|
/*********** web2py specific ***********/
|
||||||
div.flash {
|
div.w2p_flash {
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
display:none;
|
display:none;
|
||||||
position:fixed;
|
padding:20px 20px 20px 50px;
|
||||||
padding:10px;
|
width:100%;
|
||||||
top:48px;
|
|
||||||
right:250px;
|
|
||||||
min-width:280px;
|
|
||||||
opacity:0.95;
|
opacity:0.95;
|
||||||
margin:0px 0px 10px 10px;
|
|
||||||
vertical-align:middle;
|
vertical-align:middle;
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
color:#fff;
|
color:#000;
|
||||||
background-color:#000;
|
background-color:#ffdc00;
|
||||||
border:2px solid #fff;
|
|
||||||
border-radius:8px;
|
|
||||||
-o-border-radius: 8px;
|
|
||||||
-moz-border-radius:8px;
|
|
||||||
-webkit-border-radius:8px;
|
|
||||||
background-image: -webkit-linear-gradient(top,#222,#000);
|
|
||||||
background-image: -o-linear-gradient(top,#222,#000);
|
|
||||||
background-image: -moz-linear-gradient(90deg, #222, #000);
|
|
||||||
background-image: linear-gradient(top,#222,#000);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
z-index:2000;
|
z-index:2000;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.flash #closeflash{color:inherit; float:right; margin-left:15px;}
|
div.w2p_flash:before{content:"×";float:right; margin-right:100px; color:black;}
|
||||||
.ie-lte7 div.flash #closeflash
|
.ie-lte7 div.flash #closeflash
|
||||||
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
|
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
|
||||||
|
|
||||||
div.flash:hover { opacity:0.25; }
|
div.w2p_flash:hover { opacity:0.80; }
|
||||||
|
|
||||||
div.error_wrapper {display:block}
|
div.error_wrapper {display:block}
|
||||||
div.error {
|
div.error {
|
||||||
width: 298px;
|
color:red;
|
||||||
background:red;
|
|
||||||
border: 2px solid #d00;
|
|
||||||
color:white;
|
|
||||||
padding:5px;
|
padding:5px;
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
background-image: -webkit-linear-gradient(left,#f00,#fdd);
|
|
||||||
background-image: -o-linear-gradient(left,#f00,#fdd);
|
|
||||||
background-image: -moz-linear-gradient(0deg, #f00, #fdd);
|
|
||||||
background-image: linear-gradient(left,#f00,#fdd);
|
|
||||||
background-repeat: repeat-y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.topbar {
|
.topbar {
|
||||||
@@ -190,34 +99,8 @@ div.error {
|
|||||||
*/
|
*/
|
||||||
/* .web2py_table {border:1px solid #ccc} */
|
/* .web2py_table {border:1px solid #ccc} */
|
||||||
.web2py_paginator {}
|
.web2py_paginator {}
|
||||||
.web2py_grid {width:100%}
|
|
||||||
.web2py_grid table {width:100%}
|
.web2py_grid table {width:100%}
|
||||||
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
|
.web2py_grid td {color: black;}
|
||||||
.web2py_grid .web2py_form td {vertical-align: top;}
|
|
||||||
|
|
||||||
.web2py_grid thead th,.web2py_grid tfoot td {
|
|
||||||
background-color:#EAEAEA;
|
|
||||||
padding:10px 5px 10px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.web2py_grid tr.odd {background-color:#F9F9F9}
|
|
||||||
.web2py_grid tr:hover {background-color:#F5F5F5}
|
|
||||||
|
|
||||||
/*
|
|
||||||
.web2py_breadcrumbs a {
|
|
||||||
line-height:20px; margin-right:5px; display:inline-block;
|
|
||||||
padding:3px 5px 3px 5px;
|
|
||||||
font-family:'lucida grande',tahoma,verdana,arial,sans-serif;
|
|
||||||
color:#3C3C3D;
|
|
||||||
text-shadow:1px 1px 0 #FFFFFF;
|
|
||||||
white-space:nowrap; overflow:visible; cursor:pointer;
|
|
||||||
background:#ECECEC;
|
|
||||||
border:1px solid #CACACA;
|
|
||||||
-webkit-border-radius:2px; -moz-border-radius:2px;
|
|
||||||
-webkit-background-clip:padding-box; border-radius:2px;
|
|
||||||
outline:none; position:relative; zoom:1; *display:inline;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
.web2py_console form {
|
.web2py_console form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -302,11 +185,6 @@ li.w2p_grid_breadcrumb_elem {
|
|||||||
.web2py_console input, .web2py_console select,
|
.web2py_console input, .web2py_console select,
|
||||||
.web2py_console a { margin: 2px; }
|
.web2py_console a { margin: 2px; }
|
||||||
|
|
||||||
.web2py_htmltable {
|
|
||||||
width: 100%;
|
|
||||||
overflow-x: auto;
|
|
||||||
-ms-overflow-x:scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
#wiki_page_body {
|
#wiki_page_body {
|
||||||
width: 600px;
|
width: 600px;
|
||||||
@@ -317,6 +195,10 @@ li.w2p_grid_breadcrumb_elem {
|
|||||||
/* fix some IE problems */
|
/* fix some IE problems */
|
||||||
|
|
||||||
.ie-lte7 .topbar .container {z-index:2}
|
.ie-lte7 .topbar .container {z-index:2}
|
||||||
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
|
.ie-lte8 div.w2p_flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
|
||||||
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
|
.ie-lte8 div.w2p_flash:hover {filter:alpha(opacity=25);}
|
||||||
.ie9 #w2p_query_panel {padding-bottom:2px}
|
.ie9 #w2p_query_panel {padding-bottom:2px}
|
||||||
|
|
||||||
|
.web2py_console .form-control {width: 20%; display: inline;}
|
||||||
|
.web2py_console #w2p_keywords {width: 50%;}
|
||||||
|
.web2py_search_actions a, .web2py_console input[type=submit], .web2py_console input[type=button], .web2py_console button { padding: 6px 12px; }
|
||||||
|
|||||||
@@ -1,264 +0,0 @@
|
|||||||
/*=============================================================
|
|
||||||
CUSTOM RULES
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
body{height:auto;} /* to avoid vertical scroll bar */
|
|
||||||
|
|
||||||
a{}
|
|
||||||
a:visited{}
|
|
||||||
a:hover{}
|
|
||||||
a:focus{}
|
|
||||||
a:active{}
|
|
||||||
|
|
||||||
h1{}
|
|
||||||
h2{}
|
|
||||||
h3{}
|
|
||||||
h4{}
|
|
||||||
h5{}
|
|
||||||
h6{}
|
|
||||||
|
|
||||||
div.flash.flash-center{left:25%;right:25%;}
|
|
||||||
div.flash.flash-top,div.flash.flash-top:hover{
|
|
||||||
position:relative;
|
|
||||||
display:block;
|
|
||||||
margin:0;
|
|
||||||
padding:1em;
|
|
||||||
top:0;
|
|
||||||
left:0;
|
|
||||||
width:100%;
|
|
||||||
text-align:center;
|
|
||||||
text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
|
|
||||||
color:#865100;
|
|
||||||
background:#feea9a;
|
|
||||||
border:1px solid;
|
|
||||||
border-top:0px;
|
|
||||||
border-left:0px;
|
|
||||||
border-right:0px;
|
|
||||||
border-radius:0;
|
|
||||||
opacity:1;
|
|
||||||
}
|
|
||||||
#header{margin-top:60px;}
|
|
||||||
.mastheader h1 {
|
|
||||||
margin-bottom:9px;
|
|
||||||
font-size:81px;
|
|
||||||
font-weight:bold;
|
|
||||||
letter-spacing:-1px;
|
|
||||||
line-height:1;
|
|
||||||
font-size:54px;
|
|
||||||
}
|
|
||||||
.mastheader small {
|
|
||||||
font-size:20px;
|
|
||||||
font-weight:300;
|
|
||||||
}
|
|
||||||
/* auth navbar - primitive style */
|
|
||||||
.auth_navbar,.auth_navbar a{color:inherit;}
|
|
||||||
.navbar-inner {-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
|
|
||||||
.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 */
|
|
||||||
div.error_wrapper{margin-bottom:9px;}
|
|
||||||
div.error_wrapper .error{
|
|
||||||
border-radius: 4px;
|
|
||||||
-o-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
}
|
|
||||||
/* 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;
|
|
||||||
vertical-align:middle;
|
|
||||||
}
|
|
||||||
div.controls .error{
|
|
||||||
min-width:5px;
|
|
||||||
background:inherit;
|
|
||||||
color:#B94A48;
|
|
||||||
border:none;
|
|
||||||
padding:0;
|
|
||||||
margin:0;
|
|
||||||
/*display:inline;*/ /* uncommenting this, the animation effect is lost */
|
|
||||||
}
|
|
||||||
div.controls .help-inline{color:#3A87AD;}
|
|
||||||
div.controls .error_wrapper +.help-inline {margin-left:-99999px;}
|
|
||||||
div.controls select +.error_wrapper {margin-left:5px;}
|
|
||||||
.ie-lte7 div.error{color:#fff;}
|
|
||||||
|
|
||||||
/* beautify brand */
|
|
||||||
.navbar {margin-bottom:0}
|
|
||||||
.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 */
|
|
||||||
span.highlighted{color:#d8d800;}
|
|
||||||
.open span.highlighted{color:#ffff00;}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
OVERRIDING WEB2PY.CSS RULES
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
/* reset to default */
|
|
||||||
a{white-space:normal;}
|
|
||||||
li{margin-bottom:0;}
|
|
||||||
textarea,button{display:block;}
|
|
||||||
/*reset ul padding */
|
|
||||||
ul#navbar{padding:0;}
|
|
||||||
/* label aligned to related input */
|
|
||||||
td.w2p_fl,td.w2p_fc {padding:0;}
|
|
||||||
#web2py_user_form td{vertical-align:middle;}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
OVERRIDING BOOTSTRAP.CSS RULES
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
/* because web2py handles this via js */
|
|
||||||
textarea { width:90%}
|
|
||||||
.hidden{visibility:visible;}
|
|
||||||
/* 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 */
|
|
||||||
.icon-white,
|
|
||||||
.nav-tabs > .active > a > [class^="icon-"],
|
|
||||||
.nav-tabs > .active > a > [class*=" icon-"],
|
|
||||||
.nav-pills > .active > a > [class^="icon-"],
|
|
||||||
.nav-pills > .active > a > [class*=" icon-"],
|
|
||||||
.nav-list > .active > a > [class^="icon-"],
|
|
||||||
.nav-list > .active > a > [class*=" icon-"],
|
|
||||||
.navbar-inverse .nav > .active > a > [class^="icon-"],
|
|
||||||
.navbar-inverse .nav > .active > a > [class*=" icon-"],
|
|
||||||
.dropdown-menu > li > a:hover > [class^="icon-"],
|
|
||||||
.dropdown-menu > li > a:hover > [class*=" icon-"],
|
|
||||||
.dropdown-menu > .active > a > [class^="icon-"],
|
|
||||||
.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 */
|
|
||||||
div>input[type="radio"],div>input[type="checkbox"]{margin:0;}
|
|
||||||
/* bootstrap has button instead of input */
|
|
||||||
input[type="button"], input[type="submit"]{margin-right:8px;}
|
|
||||||
|
|
||||||
/* web2py radio widget adjustment */
|
|
||||||
.generic-widget input[type='radio'] {margin:-1px 0 0 0; vertical-align: middle;}
|
|
||||||
.generic-widget input[type='radio'] + label {display:inline-block; margin:0 0 0 6px; vertical-align: middle;}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
RULES FOR SOLVING CONFLICTS BETWEEN WEB2PY.CSS AND BOOTSTRAP.CSS
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
/*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*/
|
|
||||||
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;}
|
|
||||||
div.w2p_fw,div.w2p_fc{
|
|
||||||
display:inline-block;
|
|
||||||
vertical-align:middle;
|
|
||||||
margin-bottom:0;
|
|
||||||
}
|
|
||||||
div.w2p_fc{
|
|
||||||
padding-left:5px;
|
|
||||||
margin-top:-8px;
|
|
||||||
}
|
|
||||||
/*when formstyle=ul*/
|
|
||||||
form>ul{
|
|
||||||
list-style:none;
|
|
||||||
margin:0;
|
|
||||||
}
|
|
||||||
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*/
|
|
||||||
#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*/
|
|
||||||
#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;}
|
|
||||||
div.control-group.error{
|
|
||||||
width:auto;
|
|
||||||
background:transparent;
|
|
||||||
border:0;
|
|
||||||
color:inherit;
|
|
||||||
padding:0;
|
|
||||||
background-repeat:repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
OTHER RULES
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
/* 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 */
|
|
||||||
.hidden{visibility:visible;}
|
|
||||||
.web2py_console input{
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 0;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.web2py_console input[type="submit"],
|
|
||||||
.web2py_console input[type="button"],
|
|
||||||
.web2py_console button{
|
|
||||||
padding-top:4px;
|
|
||||||
padding-bottom:4px;
|
|
||||||
margin:3px 0 0 2px;
|
|
||||||
}
|
|
||||||
.web2py_console a,
|
|
||||||
.web2py_console select,
|
|
||||||
.web2py_console input
|
|
||||||
{
|
|
||||||
margin:3px 0 0 2px;
|
|
||||||
}
|
|
||||||
.web2py_grid form table{width:auto;}
|
|
||||||
/* auth_user_remember checkbox extrapadding in IE fix */
|
|
||||||
.ie-lte9 input#auth_user_remember.checkbox {padding-left:0;}
|
|
||||||
|
|
||||||
div.controls .error {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
MEDIA QUERIES
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
@media only screen and (max-width:979px){
|
|
||||||
body{padding-top:0px;}
|
|
||||||
#navbar{/*top:5px;*/}
|
|
||||||
div.flash{right:5px;}
|
|
||||||
.dropdown-menu ul{visibility:visible;}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width:479px){
|
|
||||||
body{
|
|
||||||
padding-left:10px;
|
|
||||||
padding-right:10px;
|
|
||||||
}
|
|
||||||
.navbar-fixed-top,.navbar-fixed-bottom {
|
|
||||||
margin-left:-10px;
|
|
||||||
margin-right:-10px;
|
|
||||||
}
|
|
||||||
input[type="text"],input[type="password"],select{
|
|
||||||
width:95%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.navbar {
|
|
||||||
margin-right: -20px;
|
|
||||||
margin-left: -20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
/*=============================================================
|
|
||||||
BOOTSTRAP DROPDOWN MENU
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
.dropdown-menu ul{
|
|
||||||
left:100%;
|
|
||||||
position:absolute;
|
|
||||||
top:0;
|
|
||||||
visibility:hidden;
|
|
||||||
margin-top:-1px;
|
|
||||||
}
|
|
||||||
.dropdown-menu li:hover ul{visibility:visible;}
|
|
||||||
.navbar .dropdown-menu ul:before{
|
|
||||||
border-bottom:7px solid transparent;
|
|
||||||
border-left:none;
|
|
||||||
border-right:7px solid rgba(0, 0, 0, 0.2);
|
|
||||||
border-top:7px solid transparent;
|
|
||||||
left:-7px;
|
|
||||||
top:5px;
|
|
||||||
}
|
|
||||||
.nav > li.dropdown > a:after {
|
|
||||||
border-left: 4px solid transparent;
|
|
||||||
border-right: 4px solid transparent;
|
|
||||||
border-top: 4px solid #000000;
|
|
||||||
content: "";
|
|
||||||
display: inline-block;
|
|
||||||
height: 0;
|
|
||||||
opacity: 0.7;
|
|
||||||
vertical-align: top;
|
|
||||||
width: 0;
|
|
||||||
|
|
||||||
margin-left: 2px;
|
|
||||||
margin-top: 8px;
|
|
||||||
|
|
||||||
border-bottom-color: #FFFFFF;
|
|
||||||
border-top-color: #FFFFFF;
|
|
||||||
}
|
|
||||||
.dropdown-menu span{display:inline-block;}
|
|
||||||
ul.dropdown-menu li.dropdown > a:after {
|
|
||||||
border-left: 4px solid #000;
|
|
||||||
border-right: 4px solid transparent;
|
|
||||||
border-bottom: 4px solid transparent;
|
|
||||||
border-top: 4px solid transparent;
|
|
||||||
content: "";
|
|
||||||
display: inline-block;
|
|
||||||
height: 0;
|
|
||||||
opacity: 0.7;
|
|
||||||
vertical-align: top;
|
|
||||||
width: 0;
|
|
||||||
|
|
||||||
margin-left: 8px;
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.nav li.dropdown:hover ul.dropdown-menu {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.open >.dropdown-menu ul{display:block;} /* fix menu issue when BS2.0.4 is applied */
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
BOOTSTRAP SUBMIT BUTTON
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
input[type='submit']:not(.btn) {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 4px 14px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 20px;
|
|
||||||
color: #333;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
|
|
||||||
vertical-align: middle;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: whiteSmoke;
|
|
||||||
background-image: -webkit-gradient(linear,0 0,0 100%,from(white),to(#E6E6E6));
|
|
||||||
background-image: -webkit-linear-gradient(top,white,#E6E6E6);
|
|
||||||
background-image: -o-linear-gradient(top,white,#E6E6E6);
|
|
||||||
background-image: linear-gradient(to bottom,white,#E6E6E6);
|
|
||||||
background-image: -moz-linear-gradient(top,white,#E6E6E6);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border: 1px solid #BBB;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
border-bottom-color: #A2A2A2;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);
|
|
||||||
filter: progid:dximagetransform.microsoft.gradient(enabled=false);
|
|
||||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);
|
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type='submit']:not(.btn):hover {
|
|
||||||
color: #333;
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: #E6E6E6;
|
|
||||||
background-position: 0 -15px;
|
|
||||||
-webkit-transition: background-position .1s linear;
|
|
||||||
-moz-transition: background-position .1s linear;
|
|
||||||
-o-transition: background-position .1s linear;
|
|
||||||
transition: background-position .1s linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type='submit']:not(.btn).active, input[type='submit']:not(.btn):active {
|
|
||||||
background-color: #E6E6E6;
|
|
||||||
background-color: #D9D9D9 9;
|
|
||||||
background-image: none;
|
|
||||||
outline: 0;
|
|
||||||
-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
-moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);
|
|
||||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
OTHER
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
.ie-lte8 .navbar-fixed-top {position:static;}
|
|
||||||
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -77,10 +77,10 @@ function doClickSave() {
|
|||||||
t.attr('disabled', '');
|
t.attr('disabled', '');
|
||||||
var flash = xhr.getResponseHeader('web2py-component-flash');
|
var flash = xhr.getResponseHeader('web2py-component-flash');
|
||||||
if(flash) {
|
if(flash) {
|
||||||
$('.flash').html(decodeURIComponent(flash))
|
$('.w2p_flash').html(decodeURIComponent(flash))
|
||||||
.append('<a href="#" class="close">×</a>')
|
.append('<a href="#" class="close">×</a>')
|
||||||
.slideDown();
|
.slideDown();
|
||||||
} else $('.flash').hide();
|
} else $('.w2p_flash').hide();
|
||||||
try {
|
try {
|
||||||
if(json.error) {
|
if(json.error) {
|
||||||
window.location.href = json.redirect;
|
window.location.href = json.redirect;
|
||||||
@@ -158,10 +158,10 @@ function doToggleBreakpoint(filename, url, sel) {
|
|||||||
// show flash message (if any)
|
// show flash message (if any)
|
||||||
var flash = xhr.getResponseHeader('web2py-component-flash');
|
var flash = xhr.getResponseHeader('web2py-component-flash');
|
||||||
if(flash) {
|
if(flash) {
|
||||||
$('.flash').html(decodeURIComponent(flash))
|
$('.w2p_flash').html(decodeURIComponent(flash))
|
||||||
.append('<a href="#" class="close">×</a>')
|
.append('<a href="#" class="close">×</a>')
|
||||||
.slideDown();
|
.slideDown();
|
||||||
} else $('.flash').hide();
|
} else $('.w2p_flash').hide();
|
||||||
try {
|
try {
|
||||||
if(json.error) {
|
if(json.error) {
|
||||||
window.location.href = json.redirect;
|
window.location.href = json.redirect;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
|||||||
// 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 adjust_height_of_collapsed_nav() {
|
|
||||||
var cn = jQuery('div.collapse');
|
|
||||||
if (cn.get(0)) {
|
|
||||||
var cnh = cn.get(0).style.height;
|
|
||||||
if (cnh>'0px'){
|
|
||||||
cn.css('height','auto');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function hoverMenu(){
|
|
||||||
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
|
|
||||||
adjust_height_of_collapsed_nav();
|
|
||||||
var mi = jQuery(this).addClass('open');
|
|
||||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
|
|
||||||
}, function(){
|
|
||||||
var 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');});
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
{{extend 'layout.html'}}
|
{{extend 'layout.html'}}
|
||||||
{{block sectionclass}}about{{end}}
|
|
||||||
<!-- begin "about" block -->
|
<!-- begin "about" block -->
|
||||||
<h2>{{=T("About application")}} "{{=app}}"</h2>
|
<h2>{{=T("About application")}} "{{=app}}"</h2>
|
||||||
<h3>{{=T("About")}} {{=app}}</h3>
|
<h3>{{=T("About")}} {{=app}}</h3>
|
||||||
|
|||||||
@@ -9,19 +9,16 @@ def peekfile(path,file,vars={},title=None):
|
|||||||
return A(file.replace('\\\\','/'),_title=title,_href=URL('peek', args=args, vars=vars))
|
return A(file.replace('\\\\','/'),_title=title,_href=URL('peek', args=args, vars=vars))
|
||||||
def editfile(path,file,vars={}):
|
def editfile(path,file,vars={}):
|
||||||
args=(path,file) if 'app' in vars else (app,path,file)
|
args=(path,file) if 'app' in vars else (app,path,file)
|
||||||
return A(SPAN(T('Edit')),_class='button editbutton btn btn-mini',_href=URL('edit', args=args, vars=vars))
|
return A(T('Edit'),_class='btn small rounded black',_href=URL('edit', args=args, vars=vars))
|
||||||
def testfile(path,file):
|
def testfile(path,file):
|
||||||
return A(TAG[''](IMG(_src=URL('static', 'images/test_icon.png'), _alt=T('test')),
|
return A(I(_class="fa fa-cog"),**{
|
||||||
SPAN(T("Run tests in this file (to run all files, you may also use the button labelled 'test')"))),
|
'_class':'btn small rounded black',
|
||||||
_class='icon test',
|
'_data-tooltip':T("Run tests in this file (to run all files, you may also use the but\
|
||||||
_href=URL('test', args=(app, file)),
|
ton labelled 'test')")})
|
||||||
_rel="tooltip",
|
|
||||||
**{'_data-placement':'right',
|
|
||||||
'_data-original-title':T("Run tests in this file (to run all files, you may also use the button labelled 'test')")})
|
|
||||||
def editlanguagefile(path,file,vars={}):
|
def editlanguagefile(path,file,vars={}):
|
||||||
return A(SPAN(T('Edit')),_class='button editbutton btn btn-mini',_href=URL('edit_language', args=(app, path, file), vars=vars))
|
return A(T('Edit'),_class='button editbutton btn rounded small',_href=URL('edit_language', args=(app, path, file), vars=vars))
|
||||||
def editpluralsfile(path,file,vars={}):
|
def editpluralsfile(path,file,vars={}):
|
||||||
return A(SPAN(T('Edit')),_class='button editbutton btn btn-mini',_href=URL('edit_plurals', args=(app, path, file), vars=vars))
|
return A(T('Edit'),_class='button editbutton btn rounded small',_href=URL('edit_plurals', args=(app, path, file), vars=vars))
|
||||||
def file_upload_form(location, anchor=None):
|
def file_upload_form(location, anchor=None):
|
||||||
form=FORM(
|
form=FORM(
|
||||||
LABEL(T("upload file:")),
|
LABEL(T("upload file:")),
|
||||||
@@ -59,13 +56,8 @@ def upload_plugin_form(app, anchor=None):
|
|||||||
return form
|
return form
|
||||||
def deletefile(arglist, vars={}):
|
def deletefile(arglist, vars={}):
|
||||||
vars.update({'sender':request.function+'/'+app})
|
vars.update({'sender':request.function+'/'+app})
|
||||||
return A(TAG[''](IMG(_src=URL('static', 'images/delete_icon.png')),
|
return A(I(_class='fa fa-trash'),_class="red rounded small btn",
|
||||||
SPAN(T('Delete this file (you will be asked to confirm deletion)'))),
|
_href=URL('delete',args=arglist,vars=vars))
|
||||||
_href=URL('delete',args=arglist,vars=vars),
|
|
||||||
_class='icon delete',
|
|
||||||
_rel="tooltip",
|
|
||||||
**{'_data-placement':'right',
|
|
||||||
'_data-original-title':T('Delete this file (you will be asked to confirm deletion)')})
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
{{block sectionclass}}design{{end}}
|
{{block sectionclass}}design{{end}}
|
||||||
@@ -74,64 +66,66 @@ def deletefile(arglist, vars={}):
|
|||||||
<h2>{{=T("Edit application")}} "{{=A(app,_href=URL(app,'default','index'),_target="_blank")}}"</h2>
|
<h2>{{=T("Edit application")}} "{{=A(app,_href=URL(app,'default','index'),_target="_blank")}}"</h2>
|
||||||
|
|
||||||
<!-- COLLAPSE/JUMP-TO BUTTONS -->
|
<!-- COLLAPSE/JUMP-TO BUTTONS -->
|
||||||
<div class="right-full controls">
|
<div class="container">
|
||||||
<p class="buttons-row">
|
<div class="fill">
|
||||||
{{=searchbox('search')}}
|
<input placeholder="filter" id="search" style="left:100px"/>
|
||||||
<a class="button special btn btn-inverse" href="#" onclick="jQuery('h3>span').click();return false"><span>{{=T("collapse/expand all")}}</span></a>
|
</div>
|
||||||
<span class="buttongroup">
|
<div class="fill">
|
||||||
{{=button('#models', T("models"))}}
|
<div class="padded">
|
||||||
{{=button('#controllers', T("controllers"))}}
|
<a class="btn rounded small black" href="#" onclick="jQuery('.accordion>[type=checkbox]').click();return false">{{=T("collapse/expand all")}}</a>
|
||||||
{{=button('#views', T("views"))}}
|
<a href="#models" class="btn small rounded orange">{{=T("models")}}</a>
|
||||||
{{=button('#languages', T("languages"))}}
|
<a href="#controllers" class="btn small rounded orange">{{=T("controllers")}}</a>
|
||||||
{{=button('#static', T("static"))}}
|
<a href="#views" class="btn small rounded orange">{{=T("views")}}</a>
|
||||||
{{=button('#modules', T("modules"))}}
|
<a href="#models" class="btn small rounded orange">{{=T("languages")}}</a>
|
||||||
{{=button('#private', T("private files"))}}
|
<a href="#static" class="btn small rounded orange">{{=T("static")}}</a>
|
||||||
{{=button('#plugins', T("plugins"))}}
|
<a href="#models" class="btn small rounded orange">{{=T("modules")}}</a>
|
||||||
</span>
|
<a href="#private" class="btn small rounded orange">{{=T("private files")}}</a>
|
||||||
</p>
|
<a href="#plugins" class="btn small rounded orange">{{=T("plugins")}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MODELS -->
|
<!-- MODELS -->
|
||||||
<h3 id="_models" rel="pagebookmark">
|
<h5 id="_models">
|
||||||
<span class="component" onclick="collapse('models_inner');">{{=T("Models")}}</span>
|
<label class="component" for="models_inner" data-tooltip="{{=T('The data representation, define database tables and sets')}}">{{=T("Models")}}</label>
|
||||||
<a href="#models" rel="tooltip" data-placement="right" data-original-title="{{=T('The data representation, define database tables and sets')}}">
|
</h5>
|
||||||
{{=helpicon()}}
|
<div class="accordion">
|
||||||
<span>{{=T("The data representation, define database tables and sets")}}</span>
|
<input type="checkbox" id="models_inner" checked>
|
||||||
</a><span id="models" class="hashstick"> </span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
|
<div>
|
||||||
</h3>
|
<a href="#" class="right btn rounded small yellow">top</a>
|
||||||
<div id="models_inner" class="component_contents">
|
|
||||||
{{if not models:}}<p><strong>{{=T("There are no models")}}</strong></p>{{else:}}
|
{{if not models:}}<p><strong>{{=T("There are no models")}}</strong></p>{{else:}}
|
||||||
<div class="controls comptools">
|
<div class="controls comptools">
|
||||||
{{=button(URL(a=app,c='appadmin',f='index'), T('database administration'))}}
|
{{=button(URL(a=app,c='appadmin',f='index'), T('database administration'))}}
|
||||||
{{if os.access(os.path.join(request.folder,'..',app,'databases','sql.log'),os.R_OK):}}
|
{{if os.access(os.path.join(request.folder,'..',app,'databases','sql.log'),os.R_OK):}}
|
||||||
{{=button(URL('peek/%s/databases/sql.log'%app), 'sql.log')}}
|
{{=button(URL('peek/%s/databases/sql.log'%app), 'sql.log')}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
{{=button(URL(a=app, c='appadmin',f='graph_model'), T('graph model'))}}
|
{{=button(URL(a=app, c='appadmin',f='graph_model'), T('graph model'))}}
|
||||||
</div>
|
</div>
|
||||||
<ul class="unstyled act_edit">
|
<ul class="unstyled act_edit">
|
||||||
{{for m in models:}}
|
{{for m in models:}}
|
||||||
{{id="models__"+m.replace('.','__')}}
|
{{id="models__"+m.replace('.','__')}}
|
||||||
<li id="{{='_'+id}}" rel="pagebookmark"><span id="{{=id}}" class="hashstick"> </span>
|
<li id="{{='_'+id}}"><span id="{{=id}}" class="hashstick"> </span>
|
||||||
<span class="filetools controls">
|
<span class="filetools controls">
|
||||||
{{=editfile('models',m, dict(id=id))}}
|
{{=editfile('models',m, dict(id=id))}}
|
||||||
{{=deletefile([app, 'models', m], dict(id=id, id2='models'))}}
|
{{=deletefile([app, 'models', m], dict(id=id, id2='models'))}}
|
||||||
</span>
|
</span>
|
||||||
<span class="file">
|
<span class="file">
|
||||||
{{=peekfile('models',m, dict(id=id))}}
|
{{=peekfile('models',m, dict(id=id))}}
|
||||||
</span>
|
</span>
|
||||||
<span class="extras">
|
<span class="extras">
|
||||||
{{if len(defines[m]):}}{{=T("defines tables")}} {{pass}}{{=XML(', '.join([B(table).xml() for table in defines[m]]))}}
|
{{if len(defines[m]):}}{{=T("defines tables")}} {{pass}}{{=XML(', '.join([B(table).xml() for table in defines[m]]))}}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</ul>
|
</ul>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
<div class="controls formfield">
|
<div class="silver rounded padded">
|
||||||
<button onclick="jQuery('#form1').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button>
|
<button onclick="jQuery('#form1').slideToggle()" class="btn rounded small">{{=T('Create')}}</button>
|
||||||
<div id="form1" class="row-fluid" style="display:none">
|
<div id="form1" class="row-fluid" style="display:none">
|
||||||
<div class="span3">{{=file_create_form('%s/models/' % app, 'models')}}</div>
|
<div class="span3">{{=file_create_form('%s/models/' % app, 'models')}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- FIND CONTROLLER FUNCTIONS -->
|
<!-- FIND CONTROLLER FUNCTIONS -->
|
||||||
@@ -141,163 +135,162 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
<!-- CONTROLLERS -->
|
<!-- CONTROLLERS -->
|
||||||
<h3 id="_controllers" rel="pagebookmark">
|
<h5 id="_controllers">
|
||||||
<span class="component" onclick="collapse('controllers_inner');">{{=T("Controllers")}}</span>
|
<label class="component" for="controllers_inner" data-tooltip="{{=T('The application logic, each URL path is mapped in one exposed function in the controller')}}">{{=T("Controllers")}}</label>
|
||||||
<a href="#controllers" rel="tooltip" data-placement="right" data-original-title="{{=T('The application logic, each URL path is mapped in one exposed function in the controller')}}">
|
</h5>
|
||||||
{{=helpicon()}}
|
<div class="accordion">
|
||||||
<span>{{=T("The application logic, each URL path is mapped in one exposed function in the controller")}}</span>
|
<input type="checkbox" id="controllers_inner" checked>
|
||||||
</a><span id="controllers" class="hashstick"> </span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
|
<div>
|
||||||
</h3>
|
<a href="#" class="right btn rounded small yellow">top</a>
|
||||||
<div id="controllers_inner" class="component_contents">
|
|
||||||
{{if not controllers:}}<p><strong>{{=T("There are no controllers")}}</strong></p>{{else:}}
|
{{if not controllers:}}<p><strong>{{=T("There are no controllers")}}</strong></p>{{else:}}
|
||||||
<div class="controls comptools">
|
<div class="controls comptools">
|
||||||
{{=button(URL(r=request,c='shell',f='index',args=app), T("shell"))}}
|
{{=button(URL(r=request,c='shell',f='index',args=app), T("shell"))}}
|
||||||
{{=button(URL('test',args=app), T("test"))}}
|
{{=button(URL('test',args=app), T("test"))}}
|
||||||
{{=button(URL('edit',args=[app,'cron','crontab']), T("crontab"))}}
|
{{=button(URL('edit',args=[app,'cron','crontab']), T("crontab"))}}
|
||||||
</div>
|
</div>
|
||||||
<ul class="unstyled act_edit">
|
<ul class="unstyled act_edit">
|
||||||
{{for c in controllers:}}
|
{{for c in controllers:}}
|
||||||
{{id="controllers__"+c.replace('.','__')}}
|
{{id="controllers__"+c.replace('.','__')}}
|
||||||
<li id="{{='_'+id}}" rel="pagebookmark"><span id="{{=id}}" class="hashstick"> </span>
|
<li id="{{='_'+id}}"><span id="{{=id}}" class="hashstick"> </span>
|
||||||
<span class="filetools controls">
|
<span class="filetools controls">
|
||||||
{{=editfile('controllers',c, dict(id=id))}}
|
{{=editfile('controllers',c, dict(id=id))}}
|
||||||
{{=deletefile([app, 'controllers', c], dict(id=id, id2='controllers'))}}
|
{{=deletefile([app, 'controllers', c], dict(id=id, id2='controllers'))}}
|
||||||
{{=testfile('controllers',c)}}
|
{{=testfile('controllers',c)}}
|
||||||
</span>
|
</span>
|
||||||
<span class="file">
|
<span class="file">
|
||||||
{{=peekfile('controllers',c, dict(id=id))}}
|
{{=peekfile('controllers',c, dict(id=id))}}
|
||||||
</span>
|
</span>
|
||||||
<span class="extras celled">
|
<span class="extras celled">
|
||||||
{{if functions[c]:}}{{=T("exposes")}}{{pass}} {{=XML(', '.join([A(f,_href=URL(a=app,c=c[:-3],f=f)).xml() for f in functions[c]]))}}
|
{{if functions[c]:}}{{=T("exposes")}}{{pass}} {{=XML(', '.join([A(f,_href=URL(a=app,c=c[:-3],f=f)).xml() for f in functions[c]]))}}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</ul>
|
</ul>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
<div class="controls formfield">
|
<div class="silver rounded padded">
|
||||||
<button onclick="jQuery('#form2').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button>
|
<button onclick="jQuery('#form2').slideToggle()" class="btn rounded small">{{=T('Create')}}</button>
|
||||||
<div id="form2" class="row-fluid" style="display:none">
|
<div id="form2" class="row-fluid" style="display:none">
|
||||||
<div class="span3">{{=file_create_form('%s/controllers/' % app, 'controllers')}}</div>
|
<div class="span3">{{=file_create_form('%s/controllers/' % app, 'controllers')}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- VIEWS -->
|
<!-- VIEWS -->
|
||||||
<h3 id="_views" rel="pagebookmark">
|
<h5 id="_views">
|
||||||
<span class="component" onclick="collapse('views_inner');">{{=T("Views")}}</span>
|
<label class="component" for="views_inner" data-tooltip="{{=T('The presentations layer, views are also known as templates')}}">{{=T("Views")}}</label>
|
||||||
<a href="#views" rel="tooltip" data-placement="right" data-original-title="{{=T('The presentations layer, views are also known as templates')}}">
|
</h5>
|
||||||
{{=helpicon()}}
|
<div class="accordion">
|
||||||
<span>{{=T("The presentations layer, views are also known as templates")}}</span>
|
<input type="checkbox" id="views_inner" checked>
|
||||||
</a><span id="views" class="hashstick"> </span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
|
<div>
|
||||||
</h3>
|
<a href="#" class="right btn rounded small yellow">top</a>
|
||||||
<div id="views_inner" class="component_contents">
|
{{if not views:}}<p><strong>{{=T("There are no views")}}</strong></p>{{else:}}
|
||||||
{{if not views:}}<p><strong>{{=T("There are no views")}}</strong></p>{{else:}}
|
|
||||||
<div class="controls comptools">
|
<div class="controls comptools">
|
||||||
{{=button(LAYOUTS_APP, T("Download layouts from repository"))}}
|
{{=button(LAYOUTS_APP, T("Download layouts from repository"))}}
|
||||||
</div>
|
</div>
|
||||||
<ul class="unstyled act_edit">
|
<ul class="unstyled act_edit">
|
||||||
{{for c in views:}}
|
{{for c in views:}}
|
||||||
{{id="views__"+c.replace('/','__').replace('.','__')}}
|
{{id="views__"+c.replace('/','__').replace('.','__')}}
|
||||||
<li id="{{='_'+id}}" rel="pagebookmark"><span id="{{=id}}" class="hashstick"> </span>
|
<li id="{{='_'+id}}"><span id="{{=id}}" class="hashstick"> </span>
|
||||||
<span class="filetools controls">
|
<span class="filetools controls">
|
||||||
{{=editfile('views',c, dict(id=id))}}
|
{{=editfile('views',c, dict(id=id))}}
|
||||||
{{=deletefile([app, 'views', c], dict(id=id, id2='views'))}}
|
{{=deletefile([app, 'views', c], dict(id=id, id2='views'))}}
|
||||||
</span>
|
</span>
|
||||||
<span class="file">
|
<span class="file">
|
||||||
{{=peekfile('views',c, dict(id=id))}}
|
{{=peekfile('views',c, dict(id=id))}}
|
||||||
</span>
|
</span>
|
||||||
<span class="extras celled celled-one">
|
<span class="extras celled celled-one">
|
||||||
{{if c in extend:}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
|
{{if c in extend:}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
|
||||||
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
|
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</ul>
|
</ul>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
<div class="controls formfield">
|
<div class="silver rounded padded">
|
||||||
<button onclick="jQuery('#form3').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button>
|
<button onclick="jQuery('#form3').slideToggle()" class="btn rounded small">{{=T('Create')}}</button>
|
||||||
<div id="form3" class="row-fluid" style="display:none">
|
<div id="form3" class="row-fluid" style="display:none">
|
||||||
<div class="span3">{{=file_create_form('%s/views/' % app, 'views')}}</div>
|
<div class="span3">{{=file_create_form('%s/views/' % app, 'views')}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- LANGUAGES -->
|
<!-- LANGUAGES -->
|
||||||
<h3 id="_languages" rel="pagebookmark">
|
<h5 id="_languages">
|
||||||
<span class="component" onclick="collapse('languages_inner');">{{=T("Languages")}}</span>
|
<label class="component" for="languages_inner" data-tooltip="{{=T('Translation strings for the application')}}">{{=T("Languages")}}</label>
|
||||||
<a href="#languages" rel="tooltip" data-placement="right" data-original-title="{{=T('Translation strings for the application')}}">
|
</h5>
|
||||||
{{=helpicon()}}
|
<div class="accordion">
|
||||||
<span>{{=T("Translation strings for the application")}}</span>
|
<input type="checkbox" id="languages_inner" checked>
|
||||||
</a><span id="languages" class="hashstick"> </span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
|
<div>
|
||||||
</h3>
|
<a href="#" class="right btn rounded small yellow">top</a>
|
||||||
<div id="languages_inner" class="component_contents">
|
|
||||||
{{if not languages:}}<p><strong>{{=T("There are no translators, only default language is supported")}}</strong></p>{{else:}}
|
{{if not languages:}}<p><strong>{{=T("There are no translators, only default language is supported")}}</strong></p>{{else:}}
|
||||||
<div class="controls comptools">
|
<div class="controls comptools">
|
||||||
{{=button(URL('update_languages/'+app), T('update all languages'))}}
|
{{=button(URL('update_languages/'+app), T('update all languages'))}}
|
||||||
</div>
|
</div>
|
||||||
<ul class="unstyled act_edit">
|
<ul class="unstyled act_edit">
|
||||||
{{for lang in sorted(languages):
|
{{for lang in sorted(languages):
|
||||||
file = lang+'.py'
|
file = lang+'.py'
|
||||||
id = "languages__"+file.replace('.','__')}}
|
id = "languages__"+file.replace('.','__')}}
|
||||||
<li id="{{='_'+id}}" rel="pagebookmark" class="li-row"><span id="{{=id}}" class="hashstick"> </span>
|
<li id="{{='_'+id}}" class="li-row"><span id="{{=id}}" class="hashstick"> </span>
|
||||||
<span class="li-controls">
|
<span class="li-controls">
|
||||||
<span class="filetools controls">
|
<span class="filetools controls">
|
||||||
{{=editlanguagefile('languages',file)}}
|
{{=editlanguagefile('languages',file)}}
|
||||||
{{=deletefile([app, 'languages', file], dict(id=id, id2='languages'))}}
|
{{=deletefile([app, 'languages', file], dict(id=id, id2='languages'))}}
|
||||||
</span>
|
</span>
|
||||||
<span class="">
|
<span class="">
|
||||||
{{=peekfile('languages',file, dict(id=id))}}
|
{{=peekfile('languages',file, dict(id=id))}}
|
||||||
</span>
|
</span>
|
||||||
</span> <!-- /li-row -->
|
</span> <!-- /li-row -->
|
||||||
<span class="extras celled">
|
<span class="extras celled">
|
||||||
(
|
(
|
||||||
{{=T("Plural-Forms:")}}
|
{{=T("Plural-Forms:")}}
|
||||||
{{p=languages[lang][3:7]}}
|
{{p=languages[lang][3:7]}}
|
||||||
{{if p[2] == 'default':}}
|
{{if p[2] == 'default':}}
|
||||||
<span class='error text-error'>{{=T("rules are not defined")}}</span> {{=T.M("(file **gluon/contrib/plural_rules/%s.py** is not found)",lang[:2])}}
|
<span class='error text-error'>{{=T("rules are not defined")}}</span> {{=T.M("(file **gluon/contrib/plural_rules/%s.py** is not found)",lang[:2])}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
{{if p[3] == 1:}}
|
{{if p[3] == 1:}}
|
||||||
{{=B(T("are not used"))}}
|
{{=B(T("are not used"))}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
{{pfile=p[0]}}
|
{{pfile=p[0]}}
|
||||||
{{if p[1]!=0:}}<span style="display:inline-block;margin-top:-10px;">
|
{{if p[1]!=0:}}<span style="display:inline-block;margin-top:-10px;">
|
||||||
<span class="filetools controls">
|
<span class="filetools controls">
|
||||||
{{=editpluralsfile('languages',pfile,dict(nplurals=p[3]))}}
|
{{=editpluralsfile('languages',pfile,dict(nplurals=p[3]))}}
|
||||||
</span>
|
</span>
|
||||||
<span class="file">
|
<span class="file">
|
||||||
{{=peekfile('languages',pfile,dict(id=id))}}
|
{{=peekfile('languages',pfile,dict(id=id))}}
|
||||||
</span></span>
|
</span></span>
|
||||||
{{else:}}
|
{{else:}}
|
||||||
<b>{{=T("are not used yet")}}</b>
|
<b>{{=T("are not used yet")}}</b>
|
||||||
{{pass}}
|
|
||||||
{{pass}}
|
|
||||||
{{pass}}
|
|
||||||
)
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
{{pass}}
|
||||||
|
{{pass}}
|
||||||
|
)
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
{{pass}}
|
||||||
</ul>
|
</ul>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
<div class="controls formfield">
|
<div class="silver rounded padded">
|
||||||
<button onclick="jQuery('#form4').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button>
|
<button onclick="jQuery('#form4').slideToggle()" class="btn rounded small">{{=T('Create')}}</button>
|
||||||
<div id="form4" class="row-fluid" style="display:none">
|
<div id="form4" class="row-fluid" style="display:none">
|
||||||
<div class="span3">{{=file_create_form('%s/languages/' % app, 'languages', T('(something like "it-it")'))}}</div>
|
<div class="span3">{{=file_create_form('%s/languages/' % app, 'languages', T('(something like "it-it")'))}}</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- STATIC -->
|
<!-- STATIC -->
|
||||||
<h3 id="_static" rel="pagebookmark">
|
<h5 id="_static">
|
||||||
<span class="component" onclick="collapse('static_inner');">{{=T("Static")}}</span>
|
<label class="component" for="static_inner" data-tooltip="{{=T('These files are served without processing, your images go here')}}">{{=T("Static")}}</label>
|
||||||
<a href="#static" rel="tooltip" data-placement="right" data-original-title="{{=T('These files are served without processing, your images go here')}}">
|
</h5>
|
||||||
{{=helpicon()}}
|
<div class="accordion">
|
||||||
<span>{{=T("These files are served without processing, your images go here")}}</span>
|
<input type="checkbox" id="static_inner" checked>
|
||||||
</a><span id="static" class="hashstick"> </span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
|
<div>
|
||||||
</h3>
|
<a href="#" class="right btn rounded small yellow">top</a>
|
||||||
<div id="static_inner" class="component_contents">
|
|
||||||
{{if not statics:}}<p><strong>{{=T("There are no static files")}}</strong></p>{{else:}}
|
{{if not statics:}}<p><strong>{{=T("There are no static files")}}</strong></p>{{else:}}
|
||||||
<ul class="unstyled act_edit">
|
<ul class="unstyled act_edit">
|
||||||
{{
|
{{
|
||||||
path=[]
|
path=[]
|
||||||
for file in statics+['']:
|
for file in statics+['']:
|
||||||
items=file.split('/')
|
items=file.split('/')
|
||||||
@@ -308,88 +301,89 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
|
|||||||
path.append(file_path[len(path)])
|
path.append(file_path[len(path)])
|
||||||
thispath = regex_space.sub('-', 'static__'+'__'.join(path))
|
thispath = regex_space.sub('-', 'static__'+'__'.join(path))
|
||||||
}}
|
}}
|
||||||
<li class="folder"><i> </i>
|
<li class="folder"><i> </i>
|
||||||
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
|
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
|
||||||
<ul id="{{=thispath}}" style="display: none;" class="sublist">{{
|
<ul id="{{=thispath}}" style="display: none;" class="sublist">{{
|
||||||
else:
|
else:
|
||||||
path = path[:-1]
|
path = path[:-1]
|
||||||
}}
|
}}
|
||||||
</ul></li>
|
</ul>
|
||||||
|
</li>
|
||||||
{{
|
{{
|
||||||
pass
|
pass
|
||||||
pass
|
pass
|
||||||
if filename:
|
if filename:
|
||||||
}}
|
}}
|
||||||
<li>
|
<li>
|
||||||
<span class="filetools controls">
|
<span class="filetools controls">
|
||||||
{{=editfile('static',file, dict(id="static"))}} {{=deletefile([app,'static',file], dict(id="static",id2="static"))}}
|
{{=editfile('static',file, dict(id="static"))}} {{=deletefile([app,'static',file], dict(id="static",id2="static"))}}
|
||||||
</span>
|
</span>
|
||||||
<span class="file">
|
<span class="file">
|
||||||
<a href="{{=URL(a=app,c='static',f=file)}}">{{=filename}}</a>
|
<a href="{{=URL(a=app,c='static',f=file)}}">{{=filename}}</a>
|
||||||
</span>
|
</span>
|
||||||
</li>{{
|
</li>{{
|
||||||
pass
|
pass
|
||||||
pass
|
pass
|
||||||
}}
|
}}
|
||||||
</ul>
|
</ul>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
<div class="controls formfield">
|
<div class="silver rounded padded">
|
||||||
<button onclick="jQuery('#form5').slideToggle()" class="btn btn-mini">{{=T('Create/Upload')}}</button>
|
<button onclick="jQuery('#form5').slideToggle()" class="btn rounded small">{{=T('Create/Upload')}}</button>
|
||||||
<div id="form5" class="row-fluid" style="display:none">
|
<div id="form5" class="row-fluid" style="display:none">
|
||||||
<div class="span3">{{=file_create_form('%s/static/' % app, 'static')}}<em>{{=T('or alternatively')}}</em></div>
|
<div class="span3">{{=file_create_form('%s/static/' % app, 'static')}}<em>{{=T('or alternatively')}}</em></div>
|
||||||
<div class="span3">{{=file_upload_form('%s/static/' % app, 'static')}}</div>
|
<div class="span3">{{=file_upload_form('%s/static/' % app, 'static')}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MODULES -->
|
<!-- MODULES -->
|
||||||
<h3 id="_modules" rel="pagebookmark">
|
<h5 id="_modules">
|
||||||
<span class="component" onclick="collapse('modules_inner');">{{=T("Modules")}}</span>
|
<label class="component" for="modules_inner" data-tooltip="{{=T('Additional code for your application')}}">{{=T("Modules")}}</label>
|
||||||
<a href="#modules" rel="tooltip" data-placement="right" data-original-title="{{=T('Additional code for your application')}}">
|
</h5>
|
||||||
{{=helpicon()}}
|
<div class="accordion">
|
||||||
<span>{{=T("Additional code for your application")}}</span>
|
<input type="checkbox" id="modules_inner" checked>
|
||||||
</a><span id="modules" class="hashstick"> </span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
|
<div>
|
||||||
</h3>
|
<a href="#" class="right btn rounded small yellow">top</a>
|
||||||
<div id="modules_inner" class="component_contents">
|
|
||||||
{{if not modules:}}<p><strong>{{=T("There are no modules")}}</strong></p>{{else:}}
|
{{if not modules:}}<p><strong>{{=T("There are no modules")}}</strong></p>{{else:}}
|
||||||
<ul class="unstyled act_edit">
|
<ul class="unstyled act_edit">
|
||||||
{{for m in modules:}}
|
{{for m in modules:}}
|
||||||
{{id="modules__"+m.replace('/','__').replace('.','__')}}
|
{{id="modules__"+m.replace('/','__').replace('.','__')}}
|
||||||
<li id="{{='_'+id}}" rel="pagebookmark"><span id="{{=id}}" class="hashstick"> </span>
|
<li id="{{='_'+id}}"><span id="{{=id}}" class="hashstick"> </span>
|
||||||
<span class="filetols controls">
|
<span>
|
||||||
{{=editfile('modules',m,dict(id=id))}}
|
{{=editfile('modules',m,dict(id=id))}}
|
||||||
{{if m!='__init__.py':}}
|
{{if m!='__init__.py':}}
|
||||||
{{=deletefile([app, 'modules', m], dict(id=id, id2='modules'))}}
|
{{=deletefile([app, 'modules', m], dict(id=id, id2='modules'))}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</span>
|
</span>
|
||||||
<span class="file">
|
<span class="file">
|
||||||
{{=peekfile('modules',m, dict(id=id))}}
|
{{=peekfile('modules',m, dict(id=id))}}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</ul>
|
</ul>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
<div class="controls formfield">
|
<div class="silver rounded padded">
|
||||||
<button onclick="jQuery('#form6').slideToggle()" class="btn btn-mini">{{=T('Create/Upload')}}</button>
|
<button onclick="jQuery('#form6').slideToggle()" class="btn rounded small">{{=T('Create/Upload')}}</button>
|
||||||
<div id="form6" class="row-fluid" style="display:none">
|
<div id="form6" class="row-fluid" style="display:none">
|
||||||
<div class="span3">{{=file_create_form('%s/modules/' % app, 'modules')}}<em>{{=T('or alternatively')}}</em></div>
|
<div class="span3">{{=file_create_form('%s/modules/' % app, 'modules')}}<em>{{=T('or alternatively')}}</em></div>
|
||||||
<div class="span3">{{=file_upload_form('%s/modules/' % app, 'modules')}}</div>
|
<div class="span3">{{=file_upload_form('%s/modules/' % app, 'modules')}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- PRIVATE -->
|
<!-- PRIVATE -->
|
||||||
<h3 id="_private" rel="pagebookmark">
|
<h5 id="_private">
|
||||||
<span class="component" onclick="collapse('private_inner');">{{=T("Private files")}}</span>
|
<label class="component" for="private_inner" data-tooltip="{{=T('These files are not served, they are only available from within your app')}}">{{=T("Private files")}}</label>
|
||||||
<a href="#private" rel="tooltip" data-placement="right" data-original-title="{{=T('These files are not served, they are only available from within your app')}}">
|
</h5>
|
||||||
{{=helpicon()}}
|
<div class="accordion">
|
||||||
<span>{{=T("These files are not served, they are only available from within your app")}}</span>
|
<input type="checkbox" id="private_inner" checked>
|
||||||
</a><span id="private" class="hashstick"> </span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
|
<div>
|
||||||
</h3>
|
<a href="#" class="right btn rounded small yellow">top</a>
|
||||||
<div id="private_inner" class="component_contents">
|
|
||||||
{{if not privates:}}<p><strong>{{=T("There are no private files")}}</strong></p>{{else:}}
|
{{if not privates:}}<p><strong>{{=T("There are no private files")}}</strong></p>{{else:}}
|
||||||
<ul class="unstyled act_edit">
|
<ul class="unstyled act_edit">
|
||||||
{{
|
{{
|
||||||
path=[]
|
path=[]
|
||||||
for file in privates+['']:
|
for file in privates+['']:
|
||||||
items=file.split('/')
|
items=file.split('/')
|
||||||
@@ -400,72 +394,74 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
|
|||||||
path.append(file_path[len(path)])
|
path.append(file_path[len(path)])
|
||||||
thispath='private__'+'__'.join(path)
|
thispath='private__'+'__'.join(path)
|
||||||
}}
|
}}
|
||||||
<li class="folder">
|
<li class="folder">
|
||||||
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
|
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
|
||||||
<ul id="{{=thispath}}" style="display: none;" class="sublist">{{
|
<ul id="{{=thispath}}" style="display: none;" class="sublist">{{
|
||||||
else:
|
else:
|
||||||
path = path[:-1]
|
path = path[:-1]
|
||||||
}}
|
}}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{{
|
{{
|
||||||
pass
|
pass
|
||||||
pass
|
pass
|
||||||
if filename:
|
if filename:
|
||||||
}}
|
}}
|
||||||
<li>
|
<li>
|
||||||
<span class="filetools controls">
|
<span class="filetools controls">
|
||||||
{{=editfile('private',file, dict(id="private"))}} {{=deletefile([app,'private',file], dict(id="private",id2="private"))}}
|
{{=editfile('private',file, dict(id="private"))}} {{=deletefile([app,'private',file], dict(id="private",id2="private"))}}
|
||||||
</span>
|
</span>
|
||||||
<span class="file">
|
<span class="file">
|
||||||
{{=peekfile('private',file, dict(id="private"))}}
|
{{=peekfile('private',file, dict(id="private"))}}
|
||||||
</span>
|
</span>
|
||||||
</li>{{
|
</li>{{
|
||||||
pass
|
pass
|
||||||
pass
|
pass
|
||||||
}}
|
}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="controls formfield">
|
<div class="silver rounded padded">
|
||||||
<button onclick="jQuery('#form7').slideToggle()" class="btn btn-mini">{{=T('Create/Upload')}}</button>
|
<button onclick="jQuery('#form7').slideToggle()" class="btn rounded small">{{=T('Create/Upload')}}</button>
|
||||||
<div id="form7" class="row-fluid" style="display:none">
|
<div id="form7" class="row-fluid" style="display:none">
|
||||||
<div class="span3">{{=file_create_form('%s/private/' % app, 'private')}}<em>{{=T('or alternatively')}}</em></div>
|
<div class="span3">{{=file_create_form('%s/private/' % app, 'private')}}<em>{{=T('or alternatively')}}</em></div>
|
||||||
<div class="span3">{{=file_upload_form('%s/private/' % app, 'private')}}</div>
|
<div class="span3">{{=file_upload_form('%s/private/' % app, 'private')}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- PLUGINS -->
|
<!-- PLUGINS -->
|
||||||
<h3 id="_plugins" rel="pagebookmark">
|
<h5 id="_plugins">
|
||||||
<span class="component" onclick="collapse('plugins_inner');">{{=T("Plugins")}}</span>
|
<label class="component" for="plugins_inner" data-tooltip="{{=T('To create a plugin, name a file/folder plugin_[name]')}}">{{=T("Plugins files")}}</label>
|
||||||
<a href="#plugins" rel="tooltip" data-placement="right" data-original-title="{{=T('To create a plugin, name a file/folder plugin_[name]')}}">
|
</h5>
|
||||||
{{=helpicon()}}
|
<div class="accordion">
|
||||||
<span>{{=T("To create a plugin, name a file/folder plugin_[name]")}}</span>
|
<input type="checkbox" id="plugins_inner" checked>
|
||||||
</a><span id="plugins" class="hashstick"> </span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
|
<div>
|
||||||
</h3>
|
<a href="#" class="right btn rounded small yellow">top</a>
|
||||||
<div id="plugins_inner" class="component_contents">
|
|
||||||
{{if plugins:}}
|
{{if plugins:}}
|
||||||
<ul class="unstyled act_edit">
|
<ul class="unstyled act_edit">
|
||||||
{{for plugin in plugins:}}
|
{{for plugin in plugins:}}
|
||||||
{{id="plugins__"+plugin.replace('/','__').replace('.','__')}}
|
{{id="plugins__"+plugin.replace('/','__').replace('.','__')}}
|
||||||
<li id="{{=id}}">
|
<li id="{{=id}}">
|
||||||
{{=A('plugin_%s' % plugin, _class='file', _href=URL('plugin', args=[app, plugin], vars=dict(id=id, id2='plugins')))}}
|
{{=A('plugin_%s' % plugin, _class='file', _href=URL('plugin', args=[app, plugin], vars=dict(id=id, id2='plugins')))}}
|
||||||
</li>
|
</li>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</ul>
|
</ul>
|
||||||
{{else:}}
|
{{else:}}
|
||||||
<p><strong>{{=T('There are no plugins')}}</strong></p>
|
<p><strong>{{=T('There are no plugins')}}</strong></p>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
<div class="controls comptools">
|
<div class="controls comptools">
|
||||||
{{=button(URL(c="default", f="plugins", args=[app,]), T('Download plugins from repository'))}}
|
{{=button(URL(c="default", f="plugins", args=[app,]), T('Download plugins from repository'))}}
|
||||||
</div>
|
</div>
|
||||||
<div class="controls formfield">
|
<div class="silver rounded padded">
|
||||||
<button onclick="jQuery('#form8').slideToggle()" class="btn btn-mini">{{=T('Upload')}}</button>
|
<button onclick="jQuery('#form8').slideToggle()" class="btn rounded small">{{=T('Upload')}}</button>
|
||||||
<div id="form8" class="row-fluid" style="display:none">
|
<div id="form8" class="row-fluid" style="display:none">
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="span3">{{=upload_plugin_form(app, 'plugins')}}</div>
|
<div class="span3">{{=upload_plugin_form(app, 'plugins')}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -477,11 +473,11 @@ function filter_files() {
|
|||||||
message=data['message'];
|
message=data['message'];
|
||||||
for(var i=0; i<files.length; i++)
|
for(var i=0; i<files.length; i++)
|
||||||
jQuery('li#_'+files[i].replace(/\//g,'__').replace('.','__')).slideDown();
|
jQuery('li#_'+files[i].replace(/\//g,'__').replace('.','__')).slideDown();
|
||||||
jQuery('.flash').html(message).slideDown();
|
jQuery('.w2p_flash').html(message).slideDown();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
jQuery('.component_contents li, .formfield, .comptools').slideDown();
|
jQuery('.component_contents li, .formfield, .comptools').slideDown();
|
||||||
jQuery('.flash').html('').hide();
|
jQuery('.w2p_flash').html('').hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jQuery(document).ready(function(){
|
jQuery(document).ready(function(){
|
||||||
@@ -490,6 +486,7 @@ jQuery(document).ready(function(){
|
|||||||
if(code==13) filter_files();
|
if(code==13) filter_files();
|
||||||
});
|
});
|
||||||
jQuery('#search_start').click(function(e){ filter_files(); });
|
jQuery('#search_start').click(function(e){ filter_files(); });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<!-- end "design" block -->
|
<!-- end "design" block -->
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ def file_create_form(location, anchor=None, helptext=""):
|
|||||||
<!-- begin "edit" block -->
|
<!-- begin "edit" block -->
|
||||||
{{
|
{{
|
||||||
def shortcut(combo, description):
|
def shortcut(combo, description):
|
||||||
return XML('<li class="span5"><span class="teletype-text">%s</span><span>%s</span></li>' % (combo, description))
|
return XML('<li><span class="teletype-text">%s</span><span>%s</span></li>' % (combo, description))
|
||||||
def listfiles(app, dir, regexp='.*\.py$'):
|
def listfiles(app, dir, regexp='.*\.py$'):
|
||||||
files = sorted(
|
files = sorted(
|
||||||
listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
|
listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
{{extend 'layout.html'}}
|
{{extend 'layout.html'}}
|
||||||
{{block sectionclass}}login{{end}}
|
|
||||||
<!-- begin "index" block -->
|
<!-- begin "index" block -->
|
||||||
<h2>web2py™ {{=T('Web Framework')}}</h2>
|
<h2>web2py™ {{=T('Web Framework')}}</h2>
|
||||||
<h3>{{=T('Login to the Administrative Interface')}}</h3>
|
<div class="twothirds padded lifted">
|
||||||
<div class="form row-fluid">
|
|
||||||
{{if request.is_https or request.is_local:}}
|
{{if request.is_https or request.is_local:}}
|
||||||
<form action="{{=URL(r=request)}}" method="post" class="span4 well">
|
<form action="{{=URL(r=request)}}" method="post" class="span4 well">
|
||||||
<label for="password">{{=T('Administrator Password:')}}</label>
|
<h5>{{=T('Login to the Administrative Interface')}}</h5>
|
||||||
<input type="password" name="password" id="password"/>
|
<label class="spaced" for="password">{{=T('Administrator Password:')}}</label>
|
||||||
<input type="hidden" name="send" value="{{=send}}"/>
|
<input class="spaced" type="password" name="password" id="password"/>
|
||||||
<div class="controls"><button type="submit" name="login" class="btn">{{=T('Login')}}</button></div>
|
<input class="spaced" type="hidden" name="send" value="{{=send}}"/>
|
||||||
</form>
|
<button class="spaced" type="submit" name="login">{{=T('Login')}}</button>
|
||||||
|
</form>
|
||||||
{{else:}}
|
{{else:}}
|
||||||
<p class="help span7 alert alert-block alert-warning">{{=T('ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.')}}</p>
|
<p class="help span7 alert alert-block alert-warning">{{=T('ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.')}}</p>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
jQuery(document).ready(function(){
|
jQuery(document).ready(function(){
|
||||||
jQuery("#password").focus();
|
jQuery("#password").focus();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<!-- end "index" block -->
|
<!-- end "index" block -->
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
{{extend 'layout.html'}}
|
{{extend 'layout.html'}}
|
||||||
{{import os, glob}}
|
{{import os, glob}}
|
||||||
{{block sectionclass}}site{{end}}
|
|
||||||
<!-- begin "site" block -->
|
<!-- begin "site" block -->
|
||||||
<div class="row-fluid">
|
<div class="container">
|
||||||
<div class="applist f60 span7">
|
<div class="twothirds">
|
||||||
<div class="applist_inner">
|
<div class="padded">
|
||||||
<h2>{{=T("Installed applications")}}</h2>
|
<h2>{{=T("Installed applications")}}</h2>
|
||||||
<table width="100%" class="table">
|
<table>
|
||||||
{{for a in apps:}}
|
<tbody>
|
||||||
<tr>{{buttons = []}}
|
{{for a in apps:}}
|
||||||
<td>
|
<tr>{{buttons = []}}
|
||||||
|
<td>
|
||||||
{{if a==request.application:}}
|
{{if a==request.application:}}
|
||||||
<h4 class="currentapp">{{=a}} ({{=T('currently running')}})</h4>
|
<a class="btn rounded gray">{{=a}} ({{=T('currently running')}})</a>
|
||||||
{{else:}}
|
{{else:}}
|
||||||
<h4 class="editableapp">{{=A(a,_href=URL(a,'default','index'))}}</h4>
|
<a class="btn rounded orange" href="{{=URL(a,'default','index')}}">{{=a}}</a>
|
||||||
{{if MULTI_USER_MODE and db.app(name=a):}}(created by {{="%(first_name)s %(last_name)s" % db.auth_user[db.app(name=a).owner]}}){{pass}}
|
{{if MULTI_USER_MODE and db.app(name=a):}}(created by {{="%(first_name)s %(last_name)s" % db.auth_user[db.app(name=a).owner]}}){{pass}}
|
||||||
{{if not os.path.exists('applications/%s/compiled' % a):}}
|
{{if not os.path.exists('applications/%s/compiled' % a):}}
|
||||||
{{buttons.append((URL('design',args=a), T("Edit")))}}
|
{{buttons.append((URL('design',args=a), T("Edit")))}}
|
||||||
@@ -43,28 +43,30 @@
|
|||||||
{{if a!=request.application:}}
|
{{if a!=request.application:}}
|
||||||
{{buttons.append((URL('uninstall',args=a), T("Uninstall")))}}
|
{{buttons.append((URL('uninstall',args=a), T("Uninstall")))}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group">
|
<ul class="menu">
|
||||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
<li>
|
||||||
{{=T('Manage')}}
|
<a class="btn white rounded">{{=T('Manage')}}</a>
|
||||||
<span class="caret"></span>
|
<ul>
|
||||||
</a>
|
{{for link,name in buttons:}}
|
||||||
<ul class="dropdown-menu">
|
{{=LI(A(name,_href=link))}}
|
||||||
{{for link,name in buttons:}}
|
{{pass}}
|
||||||
{{=LI(A(name,_href=link))}}
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{=button_enable(URL('enable',args=a), a) if a!='admin' else ''}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</ul>
|
</tbody>
|
||||||
</div>
|
|
||||||
{{=button_enable(URL('enable',args=a), a) if a!='admin' else ''}}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{pass}}
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div> <!-- /applist -->
|
</div> <!-- /applist -->
|
||||||
<div class="sidebar fl60 span5">
|
<div class="third black">
|
||||||
<div class="sidebar_inner controls well well-small">
|
<div class="padded">
|
||||||
<!-- CHANGE ADMIN PWD -->
|
<!-- CHANGE ADMIN PWD -->
|
||||||
<div class="pwdchange pull-right">
|
<div class="pwdchange pull-right">
|
||||||
{{if MULTI_USER_MODE:}}
|
{{if MULTI_USER_MODE:}}
|
||||||
@@ -77,77 +79,77 @@
|
|||||||
{{if is_manager():}}
|
{{if is_manager():}}
|
||||||
<!-- VERSION -->
|
<!-- VERSION -->
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h4>{{=T("Version")}}</h4>
|
<h6>{{=T("Version")}}</h6>
|
||||||
<p>
|
<p>
|
||||||
<tt>{{=myversion}}</tt><br/>
|
<tt>{{=myversion}}</tt><br/>
|
||||||
{{running_on = T("Running on %s", request.env.server_software or 'Unknown')}}
|
{{running_on = T("Running on %s", request.env.server_software or 'Unknown')}}
|
||||||
({{="%s, Python %s" % (running_on, myplatform)}})
|
({{="%s, Python %s" % (running_on, myplatform)}})
|
||||||
</p>
|
</p>
|
||||||
<p id="check_version" class="row-buttons">
|
<p id="check_version" class="row-buttons">
|
||||||
{{if session.check_version:}}
|
{{if session.check_version:}}
|
||||||
{{=T('Checking for upgrades...')}}
|
{{=T('Checking for upgrades...')}}
|
||||||
<script>ajax('{{=URL('check_version')}}',[],'check_version');</script>
|
<script>ajax('{{=URL('check_version')}}',[],'check_version');</script>
|
||||||
{{session.check_version=False}}
|
{{session.check_version=False}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
{{=button("javascript:ajax('"+URL('check_version')+"',[],'check_version')", T('Check for upgrades'))}}
|
{{=button("javascript:ajax('"+URL('check_version')+"',[],'check_version')", T('Check for upgrades'))}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</p>
|
</p>
|
||||||
{{if session.is_mobile=='auto':}}
|
{{if session.is_mobile=='auto':}}
|
||||||
<p>{{=A(T('Try the mobile interface'),_href=URL('plugin_jqmobile','about'))}}</p>
|
<p>{{=A(T('Try the mobile interface'),_href=URL('plugin_jqmobile','about'))}}</p>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</div> <!-- /VERSION -->
|
</div> <!-- /VERSION -->
|
||||||
{{pass}}
|
{{pass}}
|
||||||
{{if MULTI_USER_MODE and is_manager():}}
|
{{if MULTI_USER_MODE and is_manager():}}
|
||||||
<!-- MULTI_USER_INTERFACE -->
|
<!-- MULTI_USER_INTERFACE -->
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h4>{{=T("Multi User Mode")}}</h4>
|
<h6>{{=T("Multi User Mode")}}</h6>
|
||||||
<p class="row-buttons">
|
<p class="row-buttons">
|
||||||
{{=button(URL('bulk_register'),T('Bulk Register'))}}
|
{{=button(URL('bulk_register'),T('Bulk Register'))}}
|
||||||
{{=button(URL('manage_students',vars={'order':'auth_user.id'}),T('Manage Students'))}}
|
{{=button(URL('manage_students',vars={'order':'auth_user.id'}),T('Manage Students'))}}
|
||||||
</p>
|
</p>
|
||||||
</div> <!-- /MULTI_USER_INTERFACE -->
|
</div> <!-- /MULTI_USER_INTERFACE -->
|
||||||
{{pass}}
|
{{pass}}
|
||||||
<!-- SCAFFOLD APP -->
|
<!-- SCAFFOLD APP -->
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h4>{{=T("New simple application")}}</h4>
|
<h6>{{=T("New simple application")}}</h6>
|
||||||
{{=form_create.custom.begin}}
|
{{=form_create.custom.begin}}
|
||||||
{{=LABEL(T("Application name:"))}}
|
{{=LABEL(T("Application name:"))}}
|
||||||
{{=form_create.custom.widget.name}}
|
{{=form_create.custom.widget.name}}
|
||||||
<div class="controls"><button type="submit" class="btn">{{=T('Create')}}</button></div>
|
<div class="controls"><button type="submit" class="btn">{{=T('Create')}}</button></div>
|
||||||
{{=form_create.custom.end}}
|
{{=form_create.custom.end}}
|
||||||
</div> <!-- /SCAFFOLD APP -->
|
</div> <!-- /SCAFFOLD APP -->
|
||||||
<!-- UPLOAD PACKAGE -->
|
<!-- UPLOAD PACKAGE -->
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h4>{{=T("Upload and install packed application")}}</h4>
|
<h6>{{=T("Upload and install packed application")}}</h6>
|
||||||
{{=form_update.custom.begin}}
|
{{=form_update.custom.begin}}
|
||||||
<label for="appupdate_name">{{=T("Application name:")}}</label>
|
<label for="appupdate_name">{{=T("Application name:")}}</label>
|
||||||
{{=form_update.custom.widget.name}}
|
{{=form_update.custom.widget.name}}
|
||||||
<label for="appupdate_file">{{=T("Upload a package:")}}</label>
|
<label for="appupdate_file">{{=T("Upload a package:")}}</label>
|
||||||
{{=form_update.custom.widget.file}}
|
{{=form_update.custom.widget.file}}
|
||||||
<label for="appupdate_url">{{=T("Or Get from URL:")}}</label>
|
<label for="appupdate_url">{{=T("Or Get from URL:")}}</label>
|
||||||
{{=form_update.custom.widget.url}}<small class="help-block">({{=T('can be a git repo')}})</small>
|
{{=form_update.custom.widget.url}}<small class="help-block">({{=T('can be a git repo')}})</small>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<label class="checkbox">
|
<label class="checkbox">
|
||||||
{{=form_update.custom.widget.overwrite}} {{=T("Overwrite installed app")}}
|
{{=form_update.custom.widget.overwrite}} {{=T("Overwrite installed app")}}
|
||||||
</label>
|
</label>
|
||||||
<button type="submit" class='btn'>{{=T('Install')}}</button>
|
<button type="submit" class='btn'>{{=T('Install')}}</button>
|
||||||
</div>
|
</div>
|
||||||
{{=form_update.custom.end}}
|
{{=form_update.custom.end}}
|
||||||
</div> <!-- /UPLOAD PACKAGE -->
|
</div> <!-- /UPLOAD PACKAGE -->
|
||||||
<!-- DEPLOY ON GAE -->
|
<!-- DEPLOY ON GAE -->
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h4>{{=T("Deploy")}}</h4>
|
<h6>{{=T("Deploy")}}</h6>
|
||||||
<p class="row-buttons">
|
<p class="row-buttons">
|
||||||
{{=button(URL('gae','deploy'), T('Deploy on Google App Engine'))}}
|
{{=button(URL('gae','deploy'), T('Deploy on Google App Engine'))}}
|
||||||
{{=button(URL('openshift','deploy'),T('Deploy to OpenShift'))}}
|
{{=button(URL('openshift','deploy'),T('Deploy to OpenShift'))}}
|
||||||
{{=button(URL('pythonanywhere','deploy'), T('Deploy to PythonAnywhere'))}}
|
{{=button(URL('pythonanywhere','deploy'), T('Deploy to PythonAnywhere'))}}
|
||||||
</p>
|
</p>
|
||||||
</div> <!-- /DEPLOY ON GAE -->
|
</div> <!-- /DEPLOY ON GAE -->
|
||||||
<!-- APP WIZARD -->
|
<!-- APP WIZARD -->
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h4>{{=T("New application wizard")}}</h4>
|
<h6>{{=T("New application wizard")}}</h6>
|
||||||
<p>{{=button(URL('wizard','index'), T('Start wizard'))}}<br/>
|
<p>{{=button(URL('wizard','index'), T('Start wizard'))}}<br/>
|
||||||
{{=T("(requires internet access, experimental)")}}</p>
|
{{=T("(requires internet access, experimental)")}}</p>
|
||||||
</div> <!-- /APP WIZARD -->
|
</div> <!-- /APP WIZARD -->
|
||||||
<!-- TWITTER TIMELINE -->
|
<!-- TWITTER TIMELINE -->
|
||||||
<div class="box twitter-timeline">
|
<div class="box twitter-timeline">
|
||||||
|
|||||||
@@ -1,114 +1,69 @@
|
|||||||
<!DOCTYPE html>
|
<html>
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
<head>
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta http-equiv="P3P" content="CP=\"IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA\"" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/>
|
||||||
<title>{{=response.title or URL()}}</title>
|
<link href="{{=URL('static','css/calendar.css')}}" rel="stylesheet" type="text/css"/>
|
||||||
{{
|
<link href="{{=URL('static','css/web2py.css')}}" rel="stylesheet" type="text/css"/>
|
||||||
response.files.append(URL('static','css/bootstrap.min.css'))
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
|
||||||
response.files.append(URL('static','css/bootstrap_essentials.css'))
|
<style>
|
||||||
response.files.append(URL('static','css/bootstrap-responsive.min.css'))
|
th, td {color: black}
|
||||||
}}
|
tbody tr:hover {background-color:transparent}
|
||||||
{{include 'web2py_ajax.html'}}
|
tbody tr {border-bottom: none}
|
||||||
</head>
|
p {text-align: left}
|
||||||
|
pre {background-color: black!important;border-radius:5px; color:white; padding:10px}
|
||||||
<body class="{{=T('direction: ltr') == 'direction: rtl' and 'RTLbody' or ''}} {{block sectionclass}}home{{end}}">
|
a.btn.btn180 {padding:20px; font-size:1.2em; width:200px!important}
|
||||||
|
[type=submit], [type=button] {border-radius:5px!important;padding:5px 10px;margin-top:10px}
|
||||||
<!-- NAVBAR
|
</style>
|
||||||
============== -->
|
{{
|
||||||
<div id="header" class="navbar navbar-inverse navbar-fixed-top">
|
left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
|
||||||
<div class="navbar-inner">
|
right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
|
||||||
<div class="container-fluid">
|
middle_column = {0: 'fill', 1: 'threequarters', 2: 'half'}[
|
||||||
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
|
||||||
<span class="icon-bar"></span>
|
}}
|
||||||
<span class="icon-bar"></span>
|
{{include "web2py_ajax.html"}}
|
||||||
<span class="icon-bar"></span>
|
</head>
|
||||||
</button>
|
<body class="black">
|
||||||
<div id="start" class="brand_wrapper">
|
<header class="black padded">
|
||||||
<a href="{{=URL('default', 'index')}}" class="button brand" ><span>web2py™ {{=T('administrative interface')}}</span></a>
|
<div class="container middle max900">
|
||||||
</div>
|
<div class="fill middle">
|
||||||
<div class="nav-collapse">
|
<label class="ham padded fa fa-bars" for="menu"></label>
|
||||||
{{if response.menu is not None:}}
|
<div class="burger accordion">
|
||||||
<ul id="menu" class="nav pull-right">
|
<input type="checkbox" id="menu"/>
|
||||||
{{for _name,_active,_link in response.menu:}}
|
{{=MENU(response.menu,_class='menu')}}
|
||||||
<li>{{=A(SPAN(_name), _href=_link, _class=_active and 'button select' or 'button')}}</li>
|
</div>
|
||||||
{{pass}}
|
</div>
|
||||||
</ul>
|
</div>
|
||||||
{{pass}}
|
</header>
|
||||||
</div><!--/.nav-collapse -->
|
{{if response.flash:}}
|
||||||
</div><!-- /container-fluid -->
|
<div class="w2p_flash">
|
||||||
</div><!-- /navbar-inner -->
|
{{=response.flash}}
|
||||||
</div><!-- /#header -->
|
</div>
|
||||||
|
{{pass}}
|
||||||
<!-- MAIN
|
<main class="white">
|
||||||
=========== -->
|
<div class="hidden">{{block sectionclass}}design{{end}}</div>
|
||||||
<div id="{{=globals().get('main_id', 'main')}}" class="container-fluid">
|
<div class="container max900">
|
||||||
<div id="main_inner" class="row-fluid">
|
{{if left_sidebar_enabled:}}
|
||||||
<div class="span12">
|
<div class="quarter padded">{{block left_sidebar}}{{end}}</div>
|
||||||
<div class="flash alert">{{=response.flash or ''}}</div>
|
|
||||||
{{include}}
|
|
||||||
</div><!-- /main span12 -->
|
|
||||||
</div><!-- /main row-fluid -->
|
|
||||||
</div><!-- /#main -->
|
|
||||||
|
|
||||||
<!-- FOOTER
|
|
||||||
============== -->
|
|
||||||
{{block footer}}
|
|
||||||
<footer id="footer" class="fixed">
|
|
||||||
<p><span>{{=T('Powered by')}} {{=A('web2py', _href='http://www.web2py.com')}}™ {{=T('created by')}} Massimo Di Pierro ©2007-{{=request.now.year}}
|
|
||||||
{{if hasattr(T,'get_possible_languages_info'):}}
|
|
||||||
- {{=T('Admin language')}}</span>
|
|
||||||
<select name="adminlanguage" onchange="var date = new Date();cookieDate=date.setTime(date.getTime()+(100*24*60*60*1000));document.cookie='adminLanguage='+this.options[this.selectedIndex].id+'; expires='+cookieDate+'; path=/';window.location.reload()">
|
|
||||||
{{for langinfo in sorted([(code,info[1]) for code,info in T.get_possible_languages_info().iteritems() if code != 'default']):}}
|
|
||||||
<option {{=T.accepted_language==langinfo[0] and 'selected' or ''}} {{='id='+langinfo[0]}} >{{=langinfo[1]}}</option>
|
|
||||||
{{pass}}
|
|
||||||
</select>
|
|
||||||
{{else:}}
|
|
||||||
</span>{{pass}}
|
|
||||||
</p>
|
|
||||||
</footer><!-- /#footer -->
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<!-- BS JAVASCRIPT
|
|
||||||
====================== -->
|
|
||||||
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
jQuery(document).ready(function(){
|
|
||||||
jQuery("[rel=tooltip]").tooltip();
|
|
||||||
jQuery(":input").attr("autocomplete","off");
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
// ====================
|
|
||||||
// upload input mask
|
|
||||||
// ====================
|
|
||||||
|
|
||||||
function FileSelectHandler(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
var filename = e.target.value.split(/\\|\//).pop();
|
|
||||||
jQuery('#fileselect>span').removeClass('txtPlaceholder').text(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
jQuery(document).ready(function(){
|
|
||||||
var iupload = jQuery('#appupdate_file');
|
|
||||||
var ow = 300, oh = 20;
|
|
||||||
var iplaceholder = jQuery('<span class="txtPlaceholder">{{=T("no package selected")}}</span>'),
|
|
||||||
iuploadbtn = jQuery('<button class="btn btn-inverse btn-mini uploadbtn"><i class="icon-white icon-circle-arrow-up"></i></button>');
|
|
||||||
iupload
|
|
||||||
.addClass('masked')
|
|
||||||
.wrap('<div id="fileselect" style="width:'+ow+'px;height:'+oh+'px"></div>')
|
|
||||||
.on('change', function(event){FileSelectHandler(event)});
|
|
||||||
jQuery('#fileselect').append(iplaceholder, iuploadbtn);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{{if request.function in ('index','site'):}}
|
|
||||||
<a style="position:fixed;bottom:0;left:0;z-index:1000" href="https://groups.google.com/forum/?fromgroups#!forum/web2py" target="_blank">
|
|
||||||
<!-- http://webchat.freenode.net/?channels=web2py" //-->
|
|
||||||
<img src="{{=URL('static','images/questions.png')}}" />
|
|
||||||
</a>
|
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</body>
|
<div class="{{=middle_column}} padded">{{include}}</div>
|
||||||
|
{{if right_sidebar_enabled:}}
|
||||||
|
<div class="quarter padded">{{block right_sidebar}}{{end}}</div>
|
||||||
|
{{pass}}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer class="black">
|
||||||
|
<div class="container padded max900">
|
||||||
|
<div class="fill">
|
||||||
|
Copyright @ 2016 - Powered by Web2py
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
// prevent android horizontal scrolling
|
||||||
|
window.addEventListener("scroll", function(){window.scroll(0, window.pageYOffset);}, false);
|
||||||
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ def what():
|
|||||||
return response.render(images=images)
|
return response.render(images=images)
|
||||||
|
|
||||||
|
|
||||||
@cache.action(time_expire=300, cache_model=cache.ram, quick='P')
|
#@cache.action(time_expire=300, cache_model=cache.ram, quick='P')
|
||||||
def download():
|
def download():
|
||||||
return response.render()
|
return response.render()
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ French speakers group
|
|||||||
|
|
||||||
``web2py-fr``:groupdates
|
``web2py-fr``:groupdates
|
||||||
|
|
||||||
|
## Italian Group
|
||||||
|
|
||||||
|
- [[https://groups.google.com/forum/?fromgroups#!forum/web2py-it https://groups.google.com/forum/?fromgroups#!forum/web2py-it popup]]
|
||||||
|
|
||||||
## Japanese Group
|
## Japanese Group
|
||||||
|
|
||||||
Japanese speakers group
|
Japanese speakers group
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,7 +1,11 @@
|
|||||||
.calendar{z-index:99;position:relative;display:none;background:#fff;border:2px solid #000;font-size:11px;color:#000;cursor:default;font-family:Arial,Helvetica,sans-serif;
|
.calendar {z-index:2000;position:relative;margin-top:140px;display:none;background-color:white;border:1px solid #000;color:#000;cursor:default;box-shadow:0 0 10px #666}.calendar * {text-align: center;font-size:10px!important}
|
||||||
border-radius: 10px;
|
.calendar table {border-collapse:collapse}
|
||||||
-moz-border-radius: 10px;
|
.calendar tbody tr:hover {background-color:#fbf6d9}
|
||||||
-webkit-border-radius: 10px;
|
.calendar td, th {padding:5px; vertical-align:top; text-align:left; border:0}
|
||||||
}.calendar table{margin:0px;font-size:11px;color:#000;cursor:default;font-family:tahoma,verdana,sans-serif;}.calendar .button{text-align:center;padding:1px;color:#fff;background:#000;}.calendar .nav{background:#000;color:#fff}.calendar thead .title{font-weight:bold;padding:1px;background:#000;color:#fff;text-align:center;}.calendar thead .name{padding:2px;text-align:center;background:#bbb;}.calendar thead .weekend{color:#f00;}.calendar thead .hilite {background-color:#666;}.calendar thead .active{padding:2px 0 0 2px;background-color:#c4c0b8;}.calendar tbody .day{width:2em;text-align:right;padding:2px 4px 2px 2px;}.calendar tbody .day.othermonth{color:#aaa;}.calendar tbody .day.othermonth.oweekend{color:#faa;}.calendar table .wn{padding:2px 3px 2px 2px;background:#bbb;}.calendar tbody .rowhilite td{background:#ddd;}.calendar tbody td.hilite{background:#bbb;}.calendar tbody td.active{background:#bbb;}.calendar tbody td.selected{font-weight:bold;background:#ddd;}.calendar tbody td.weekend{color:#f00;}.calendar tbody td.today{font-weight:bold;color:#00f;}.calendar tbody .disabled{color:#999;}.calendar tbody .emptycell{visibility:hidden;}.calendar tbody .emptyrow{display:none;}.calendar tfoot .ttip{background:#bbb;padding:1px;background:#000;color:#fff;text-align:center;}.calendar tfoot .hilite{background:#ddd;}.calendar tfoot .active{}.calendar .combo{position:absolute;display:none;width:4em;top:0;left:0;cursor:default;background:#e4e0d8;padding:1px;z-index:100;}.calendar .combo .label,.calendar .combo .label-IEfix{text-align:center;padding:1px;}.calendar .combo .label-IEfix{width:4em;}.calendar .combo .active{background:#c4c0b8;}.calendar .combo .hilite{background:#048;color:#fea;}.calendar td.time{padding:1px 0;text-align:center;background-color:#bbb;}.calendar td.time .hour,.calendar td.time .minute,.calendar td.time .ampm{padding:0 3px 0 4px;font-weight:bold;}.calendar td.time .ampm{text-align:center;}.calendar td.time .colon{padding:0 2px 0 3px;font-weight:bold;}.calendar td.time span.hilite{}.calendar td.time span.active{border-color:#f00;background-color:#000;color:#0f0;}.hour,.minute{font-size:2em;}
|
.calendar thead tr {background-color:#f1f1f1}
|
||||||
|
.calendar tbody tr {border-bottom:2px solid #f1f1f1}
|
||||||
|
.calendar th {font-weight:string; padding:5px; vertical-align:bottom; text-align:left}
|
||||||
|
.calendar thead th {vertical-align:bottom}
|
||||||
|
.calendar tbody th {vertical-align:top}
|
||||||
|
|
||||||
#CP_hourcont{z-index:99;padding:0;position:absolute;border:1px dashed #666;background-color:#eee;display:none;}#CP_minutecont{z-index:99;background-color:#ddd;padding:1px;position:absolute;width:45px;display:none;}.floatleft{float:left;}.CP_hour{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;}.CP_minute{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;}.CP_over{background-color:#fff;z-index:99}
|
#CP_hourcont{z-index:2000;padding:0;position:absolute;border:1px dashed #666;background-color:#eee;display:none;}#CP_minutecont{z-index:2000;background-color:#ddd;padding:1px;position:absolute;width:45px;display:none;}.floatleft{float:left;}.CP_hour{z-index:2000;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;}.CP_minute{z-index:2000;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;}.CP_over{background-color:#fff;z-index:2000}
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
/************
|
||||||
|
Created by Massimo Di Pierro
|
||||||
|
Stupid.css is what the names says, take it with a grain of salt
|
||||||
|
License: BSD
|
||||||
|
************/
|
||||||
|
|
||||||
|
/*** basic styles ***/
|
||||||
|
* {border:0; margin:0; padding:0; font-familiy:Helvetica}
|
||||||
|
html, body {max-width: 100vw !important;overflow-x: hidden !important}
|
||||||
|
body {font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif}
|
||||||
|
p, li {margin-bottom:0.5em}
|
||||||
|
p {text-align:justify}
|
||||||
|
label, strong {font-weight:bold}
|
||||||
|
ul {list-style-type:none; padding-left:20px}
|
||||||
|
a {text-decoration:none; color:#26a69a; white-space:nowrap}
|
||||||
|
a:hover {cursor:pointer}
|
||||||
|
h1,h2,h3,h4,h5,h6{font-weight:strong; text-transform:uppercase}
|
||||||
|
h1{font-size:4em; margin:1.0em 0 0.25em 0}
|
||||||
|
h2{font-size:2.4em; margin:0.9em 0 0.25em 0}
|
||||||
|
h3{font-size:1.8em; margin:0.8em 0 0.25em 0}
|
||||||
|
h4{font-size:1.6em; margin:0.7em 0 0.25em 0}
|
||||||
|
h5{font-size:1.4em; margin:0.6em 0 0.25em 0}
|
||||||
|
h6{font-size:1.2em; margin:0.5em 0 0.25em 0}
|
||||||
|
table {border-collapse:collapse}
|
||||||
|
tbody tr:hover {background-color:#fbf6d9}
|
||||||
|
td, th {padding:5px; vertical-align:top; text-align:left; border:0}
|
||||||
|
thead tr {background-color:#f1f1f1}
|
||||||
|
tbody tr {border-bottom:2px solid #f1f1f1}
|
||||||
|
th {font-weight:string; padding:5px; vertical-align:bottom; text-align:left}
|
||||||
|
thead th {vertical-align:bottom}
|
||||||
|
tbody th {vertical-align:top}
|
||||||
|
header, footer {with:100%}
|
||||||
|
|
||||||
|
@media (max-width:599px) {
|
||||||
|
h1{font-size:2em}
|
||||||
|
h2{font-size:1.8em}
|
||||||
|
h3{font-size:1.6em}
|
||||||
|
h4{font-size:1.4em}
|
||||||
|
h5{font-size:1.2em}
|
||||||
|
h6{font-size:1.0em}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** buttons ***/
|
||||||
|
.btn, button, [type=button], [type=submit] {padding:0.5em 1em !important; margin:0 0.5em 0.5em 0; line-height:2.4em; background-color:#26a69a; color:white}
|
||||||
|
.btn:hover, button:hover, [type=button]:hover, [type=submit]:hover {box-shadow:0 0 10px #666; text-decoration:none; cursor:pointer}
|
||||||
|
.btn.small, table .btn {padding:0.25em 0.5em !important; font-size:0.8em; line-height:1.5em}
|
||||||
|
.btn.large {padding:1em 2em !important; font-size:1.2em; line-height:4em}
|
||||||
|
.btn.oval {border-radius:50%}
|
||||||
|
|
||||||
|
/*** helpers ***/
|
||||||
|
.rounded {-moz-border-radius:5px; border-radius:5px}
|
||||||
|
.padded {padding:10px 20px !important; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box}
|
||||||
|
.center {text-align:center !important; margin-left:auto; margin-right:auto}
|
||||||
|
.center>div {text-align:left}
|
||||||
|
.right {right:0; text-align:right}
|
||||||
|
.middle div {vertical-align:middle !important}
|
||||||
|
.bottom div {vertical-align:bottom !important}
|
||||||
|
.xscroll {overflow-x:scroll; width:100%}
|
||||||
|
.yscroll {overflow-y:scroll; width:100%}
|
||||||
|
.nowrap {white-space:nowrap; overflow-x:hidden}
|
||||||
|
.fill {width:100%}
|
||||||
|
.lifted {box-shadow:5px 5px 10px #666}
|
||||||
|
.relative {position:relative}
|
||||||
|
.relative>div {position:absolute}
|
||||||
|
.spaced {margin-bottom:20px; margin-top:20px}
|
||||||
|
.hidden {display:none}
|
||||||
|
|
||||||
|
/*** forms ***/
|
||||||
|
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit]), [type=file]:before {outline:none; padding:0.5em 1em; margin:0.5px; border-bottom:1px solid #ddd; width:100%}
|
||||||
|
textarea {width:100%; border:1px solid #ddd; padding:4px 8px; outline:none; outline:none}
|
||||||
|
select {-webkit-appearance:none; outline:none; padding:0.5em 1em; border-radius:0; margin:0.5px; border-bottom:1px solid #ddd}
|
||||||
|
input, textarea, select, button {font-size:12px}
|
||||||
|
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit]):hover, select:hover, textarea:hover {background-color:#fbf6d9; transition:background-color 1s ease}
|
||||||
|
|
||||||
|
/*** grid ***/
|
||||||
|
.container {margin-right:-20px}
|
||||||
|
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters, .container>.fill{display:inline-block; padding:5px 20px 5px 0; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; vertical-align:top}
|
||||||
|
.container>.fill {width:100%; margin-right:-20px}
|
||||||
|
.container img, .container video {max-width:100%}
|
||||||
|
@media (min-width:800px) {
|
||||||
|
.max900 {max-width:900px; margin-left:auto; margin-right:auto}
|
||||||
|
.quarter {width:25%; margin-right:-5px}
|
||||||
|
.half {width:50%; margin-right:-10px}
|
||||||
|
.third {width:33.33%; margin-right:-6.66px}
|
||||||
|
.twothirds {width:66.66%; margin-right:-13.33px}
|
||||||
|
.threequarters {width:75%; margin-right:-15px}
|
||||||
|
}
|
||||||
|
@media (min-width:600px) and (max-width:799px) {
|
||||||
|
.quarter.compressible {width:25%; margin-right:-5px}
|
||||||
|
.half.compressible {width:50%; margin-right:-10px}
|
||||||
|
.threequarters.compressible {width:75%; margin-right:-15px}
|
||||||
|
.quarter:not(.compressible), .half:not(.compressible), .threequarters:not(.compressible) {width:100%; margin-right:-20px}
|
||||||
|
.third {width:33.33%; margin-right:-6.66px}
|
||||||
|
.twothirds {width:66.66%; margin-right:-13.33px}
|
||||||
|
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right {float:left; text-align:left}
|
||||||
|
}
|
||||||
|
@media (max-width:599px) {
|
||||||
|
.quarter:not(.compressible), .half:not(.compressible), .third:not(.compressible), .twothirds:not(.compressible), .threequarters:not(.compressible) {width:100%;}
|
||||||
|
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right,
|
||||||
|
label.third:not(.compressible).right, label.twothirds:not(.compressible).right {float:left; text-align:left}
|
||||||
|
.quarter.compressible {width:25%; margin-right:-5px}
|
||||||
|
.half.compressible {width:50%; margin-right:-10px}
|
||||||
|
.third.compressible {width:33.33%; margin-right:-6.66px}
|
||||||
|
.twothirds.compressible {width:66.66%; margin-right:-13.33px}
|
||||||
|
.threequarters.compressible {width:75%; margin-right:-15px}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** progress bar from http://codepen.io/holdencreative/details/pvxGxy ***/
|
||||||
|
.progress {
|
||||||
|
margin-left:-15px;
|
||||||
|
margin-right:-15px;
|
||||||
|
position:relative;
|
||||||
|
height:8px;
|
||||||
|
display:block;
|
||||||
|
width:120%;
|
||||||
|
background-color:#acece6;
|
||||||
|
border-radius:0 !important;
|
||||||
|
background-clip:padding-box;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
.progress .determinate {
|
||||||
|
position:absolute;
|
||||||
|
background-color:inherit;
|
||||||
|
top:0;
|
||||||
|
bottom:0;
|
||||||
|
background-color:#26a69a;
|
||||||
|
transition:width .3s linear;
|
||||||
|
}
|
||||||
|
.progress .indeterminate {
|
||||||
|
background-color:#26a69a;
|
||||||
|
}
|
||||||
|
.progress .indeterminate:before {
|
||||||
|
content:'';
|
||||||
|
position:absolute;
|
||||||
|
background-color:inherit;
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
|
bottom:0;
|
||||||
|
will-change:left, right;
|
||||||
|
animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
|
||||||
|
}
|
||||||
|
.progress .indeterminate:after {
|
||||||
|
content:'';
|
||||||
|
position:absolute;
|
||||||
|
background-color:inherit;
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
|
bottom:0;
|
||||||
|
will-change:left, right;
|
||||||
|
animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
|
||||||
|
animation-delay:1.15s;
|
||||||
|
}
|
||||||
|
@-webkit-keyframes indeterminate {
|
||||||
|
0% {left:-35%; right:100%}
|
||||||
|
60% {left:100%; right:-90%}
|
||||||
|
100% {left:100%; right:-90%}
|
||||||
|
}
|
||||||
|
@-moz-keyframes indeterminate {
|
||||||
|
0% {left:-35%; right:100%}
|
||||||
|
60% {left:100%; right:-90%}
|
||||||
|
100% {left:100%; right:-90%}
|
||||||
|
}
|
||||||
|
@keyframes indeterminate {
|
||||||
|
0% {left:-35%; right:100%}
|
||||||
|
60% {left:100%; right:-90%}
|
||||||
|
100% {left:100%; right:-90%}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes indeterminate-short {
|
||||||
|
0% {left:-200%; right:100%}
|
||||||
|
60% {left:107%; right:-8%}
|
||||||
|
100% {left:107%; right:-8%}
|
||||||
|
}
|
||||||
|
@-moz-keyframes indeterminate-short {
|
||||||
|
0% {left:-200%; right:100%}
|
||||||
|
60% {left:107%; right:-8%}
|
||||||
|
100% {left:107%; right:-8%}
|
||||||
|
}
|
||||||
|
@keyframes indeterminate-short {
|
||||||
|
0% {left:-200%; right:100%}
|
||||||
|
60% {left:107%; right:-8%}
|
||||||
|
100% {left:107%; right:-8%}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** dropdown menu from http://codepen.io/philhoyt/pen/ujHzd ***/
|
||||||
|
.menu {list-style:none; position:relative; margin:0; padding:0}
|
||||||
|
.menu.right {float:right}
|
||||||
|
.menu a {padding:0 15px; text-decoration:none;text-align:left;font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; text-align:left}
|
||||||
|
.menu li {position:relative; float:left; margin:0; padding:0}
|
||||||
|
.menu ul {background:white; border:1px solid #e1e1e1; visibility:hidden; opacity:0; position:absolute; top:110%; padding:0; z-index:1000; transition:all 0.2s ease-out; list-style-type:none; box-shadow:5px 5px 10px #666}
|
||||||
|
.menu ul a {padding:10px 15px; color:#333; font-weight:700; font-size:12px; line-height:16px; display: block}
|
||||||
|
.menu ul li {float:none; width:200px}
|
||||||
|
.menu ul ul {top:0; left:80%; z-index:2000}
|
||||||
|
.menu li:hover > ul {visibility:visible; opacity:1}
|
||||||
|
.menu>li>ul>li:first-child:before{content:''; position:absolute; width:1px; height:1px; border:10px solid transparent; left:50px; top:-20px; margin-left:-10px; border-bottom-color:white}
|
||||||
|
.menu.dark ul {background:black; border:1px solid black}
|
||||||
|
.menu.dark ul a {color:white}
|
||||||
|
.menu.dark>li>ul>li:first-child:before{border-bottom-color:black}
|
||||||
|
|
||||||
|
@media (max-width:599px) {
|
||||||
|
header .menu li, header .menu ul {width: 100%}
|
||||||
|
header .menu.right {float:left; text-align:left}
|
||||||
|
header .menu ul ul {top:2.5em; left:-1px; z-index:2000}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width:600px) {
|
||||||
|
.ham {display:none!important}
|
||||||
|
.burger.accordion * {max-height:1000px; overflow:visible}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** pulsating ring from https://jsfiddle.net/mandynicole/7xrKP/ *******/
|
||||||
|
.pulse:after {
|
||||||
|
content:"";
|
||||||
|
border:3px solid #00e6ac;
|
||||||
|
-webkit-border-radius:30px;
|
||||||
|
height:40px;
|
||||||
|
width:40px;
|
||||||
|
position:absolute;
|
||||||
|
margin-left:-20px;
|
||||||
|
margin-top:-20px;
|
||||||
|
-webkit-animation:pulsate 1s ease-out;
|
||||||
|
-webkit-animation-iteration-count:infinite;
|
||||||
|
opacity:0.0
|
||||||
|
}
|
||||||
|
@-webkit-keyframes pulsate {
|
||||||
|
0% {-webkit-transform:scale(0.1, 0.1); opacity:0.0}
|
||||||
|
50% {opacity:1.0}
|
||||||
|
100% {-webkit-transform:scale(1.2, 1.2); opacity:0.0}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** underline effect ***/
|
||||||
|
a:not(.btn):not(.noeffect) {position:relative}
|
||||||
|
a:not(.btn):not(.noeffect):hover {color:#26a69a}
|
||||||
|
a:not(.btn):not(.noeffect):hover:after {width:100%}
|
||||||
|
a:not(.btn):not(.noeffect):after {
|
||||||
|
display:block;
|
||||||
|
position:absolute;
|
||||||
|
left:0;
|
||||||
|
bottom:-1px;
|
||||||
|
width:0;
|
||||||
|
height:2px;
|
||||||
|
background-color:#26a69a;
|
||||||
|
content:"";
|
||||||
|
transition:width 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** modal ***/
|
||||||
|
.modal {
|
||||||
|
position:fixed;
|
||||||
|
z-index:9999;
|
||||||
|
top:0;
|
||||||
|
bottom:0;
|
||||||
|
left:0;
|
||||||
|
right:0;
|
||||||
|
background-color:rgba(0,0,0,0.8);
|
||||||
|
padding-top:20vh;
|
||||||
|
transition:opacity 500ms;
|
||||||
|
visibility:hidden;
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
.modal:target {visibility:visible; opacity:1}
|
||||||
|
.modal div {margin-left:auto; margin-right:auto}
|
||||||
|
.modal .close:not(.btn) {position:absolute; top:10px; right:10px; font-size:20px}
|
||||||
|
.modal .close {transition:all 200ms}
|
||||||
|
|
||||||
|
/*** tooltips from http://codepen.io/trezy/pen/Khnzy ***/
|
||||||
|
[data-tooltip] {position:relative}
|
||||||
|
[data-tooltip]:hover:after,[data-tooltip]:hover:before {display:block}
|
||||||
|
[data-tooltip]:before, [data-tooltip]:after {display:none; position:absolute; top:0}
|
||||||
|
[data-tooltip]:before {
|
||||||
|
border-bottom:.6em solid black;
|
||||||
|
border-bottom:.6em solid black;
|
||||||
|
border-left:7px solid transparent;
|
||||||
|
border-right:7px solid transparent;
|
||||||
|
content:"";
|
||||||
|
left:20px;
|
||||||
|
margin-top:1em;
|
||||||
|
}
|
||||||
|
[data-tooltip]:after {
|
||||||
|
background-color:rgba(0,0,0,0.8);
|
||||||
|
border:4px solid rgba(0,0,0,0.8);
|
||||||
|
border-radius:7px;
|
||||||
|
color:white;
|
||||||
|
content:attr(data-tooltip);
|
||||||
|
left:0;
|
||||||
|
margin-top:1.5em;
|
||||||
|
padding:5px 15px;
|
||||||
|
white-space:pre-wrap;
|
||||||
|
width:100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** accordion ***/
|
||||||
|
.accordion>input ~ label:before {content:"▲ "; color:#ddd}
|
||||||
|
.accordion>input:checked ~ label:before {content:"▼ "; color:#ddd}
|
||||||
|
.accordion>input {display:none}
|
||||||
|
.accordion>input:checked ~ *:not(label) {
|
||||||
|
max-height: 1000px !important;
|
||||||
|
overflow:visible !important;
|
||||||
|
-webkit-transition: max-height .3s ease-in;
|
||||||
|
transition: max-height .3s ease-in;
|
||||||
|
}
|
||||||
|
.accordion>*:not(label) {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-transition: max-height .3s ease-out;
|
||||||
|
transition: max-height .3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*** cards from http://codepen.io/edeesims/pen/iGDzk ***/
|
||||||
|
.card {perspective: 500px; max-width:100%}
|
||||||
|
.card>div {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-shadow: 0 0 15px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 1s;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
.card:hover>div {
|
||||||
|
transform: rotateY( 180deg ) ;
|
||||||
|
transition: transform 0.5s;
|
||||||
|
}
|
||||||
|
.card>div>div {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
}
|
||||||
|
.card>div>div:nth-child(2) {
|
||||||
|
transform: rotateY( 180deg );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** colors from http://clrs.cc/ ***/
|
||||||
|
.navy{background-color:#001f3f!important;color:white}.blue{background-color:#0074d9!important;color:white}.aqua{background-color:#7fdbff!important;color:black}.teal{background-color:#39cccc!important;color:white}.olive{background-color:#3d9970!important;color:white}.green{background-color:#2ecc40!important;color:white}.aquamarine{background-color:#26a69a!important;color:white}.lime{background-color:#01ff70!important;color:black}.yellow{background-color:#ffdc00!important;color:black}.orange{background-color:#ff851b!important;color:white}.red{background-color:#cc1f00!important;color:white}.fuchsia{background-color:#f012be!important;color:white}.pink{background-color:#ee6e73!important;color:white}.purple{background-color:#b10dc9!important;color:white}.maroon{background-color:#85144b!important;color:white}.white{background-color:#fff!important;color:black;-webkit-box-shadow:inset 0px 0px 0px 1px #ddd;-moz-box-shadow:inset 0px 0px 0px 1px #ddd;box-shadow:inset 0px 0px 0px 1px #ddd}.gray{background-color:#aaa!important;color:white}.silver{background-color:#f1f1f1!important;color:black}.black{background-color:#000!important;color:white}.glass{background:rgba(255,255,255,0.5)!important;color:black}
|
||||||
|
|
||||||
|
/**** tags ****/
|
||||||
|
.tags > span, .tags > span.off:hover {
|
||||||
|
height: 30px;
|
||||||
|
padding: 4px 9px;
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 0 5px 30px 0 !important;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: white;
|
||||||
|
background-color: #26a69a;
|
||||||
|
border-radius: 5px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
.tags.dismissible > span:not(.off):hover {
|
||||||
|
background-color: #ccc !important;
|
||||||
|
}
|
||||||
|
.tags.dismissible > span:not(.off):after {
|
||||||
|
content: " \f00d";
|
||||||
|
font-family: FontAwesome;
|
||||||
|
}
|
||||||
|
.tags > span.off {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
@@ -1,84 +1,17 @@
|
|||||||
/** these MUST stay **/
|
header a {color: white; font-size:1.1em}
|
||||||
a {text-decoration:none; white-space:nowrap}
|
main {min-height: 70vh}
|
||||||
a:hover {text-decoration:underline}
|
.form-group {padding-bottom: 10px !important;}
|
||||||
a.button {text-decoration:none}
|
.w2p_hidden {display:none;visibility:visible}
|
||||||
h1,h2,h3,h4,h5,h6 {margin:0.5em 0 0.25em 0; display:block;
|
|
||||||
font-family:Helvetica}
|
|
||||||
h1 {font-size:4.00em}
|
|
||||||
h2 {font-size:3.00em}
|
|
||||||
h3 {font-size:2.00em}
|
|
||||||
h4 {font-size:1.50em}
|
|
||||||
h5 {font-size:1.25em}
|
|
||||||
h6 {font-size:1.12em}
|
|
||||||
th,label {font-weight:bold; white-space:nowrap;}
|
|
||||||
td,th {text-align:left; padding:2px 5px 2px 5px}
|
|
||||||
th {vertical-align:middle; border-right:1px solid white}
|
|
||||||
td {vertical-align:top}
|
|
||||||
form table tr td label {text-align:left}
|
|
||||||
p,table,ol,ul {padding:0; margin: 0.75em 0}
|
|
||||||
p {text-align:justify}
|
|
||||||
ol, ul {list-style-position:outside; margin-left:2em}
|
|
||||||
li {margin-bottom:0.5em}
|
|
||||||
span,input,select,textarea,button,label,a {display:inline}
|
|
||||||
img {border:0}
|
|
||||||
blockquote,blockquote p,p blockquote {
|
|
||||||
font-style:italic; margin:0.5em 30px 0.5em 30px; font-size:0.9em}
|
|
||||||
i,em {font-style:italic}
|
|
||||||
strong {font-weight:bold}
|
|
||||||
small {font-size:0.8em}
|
|
||||||
code {font-family:Courier}
|
|
||||||
textarea {width:100%}
|
|
||||||
video {width:400px}
|
|
||||||
audio {width:200px}
|
|
||||||
[type="text"], [type="password"], select {
|
|
||||||
margin-right: 5px; width: 300px;
|
|
||||||
}
|
|
||||||
.hidden {display:none;visibility:visible}
|
|
||||||
.right {float:right; text-align:right}
|
.right {float:right; text-align:right}
|
||||||
.left {float:left; text-align:left}
|
.left {float:left; text-align:left}
|
||||||
.center {width:100%; text-align:center; vertical-align:middle}
|
.center {width:100%; text-align:center; vertical-align:middle}
|
||||||
/** end **/
|
|
||||||
|
|
||||||
/* Sticky footer begin */
|
|
||||||
|
|
||||||
.main {
|
|
||||||
padding:20px 0 50px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer,.push {
|
|
||||||
height:6em;
|
|
||||||
padding:1em 0;
|
|
||||||
clear:both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-content {position:relative; bottom:-4em; width:100%}
|
|
||||||
|
|
||||||
.auth_navbar {
|
|
||||||
white-space:nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sticky footer end */
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
border-top:1px #DEDEDE solid;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
/* background:<fill here for header image>; */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fieldset {padding:16px; border-top:1px #DEDEDE solid}
|
|
||||||
fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4px 16px; background:#f1f1f1}
|
|
||||||
|
|
||||||
/* fix ie problem with menu */
|
|
||||||
|
|
||||||
td.w2p_fw {padding-bottom:1px}
|
td.w2p_fw {padding-bottom:1px}
|
||||||
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
|
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
|
||||||
td.w2p_fl {text-align:left}
|
td.w2p_fl {text-align:left}
|
||||||
td.w2p_fl, td.w2p_fw {padding-right:7px}
|
td.w2p_fl, td.w2p_fw {padding-right:7px}
|
||||||
td.w2p_fl,td.w2p_fc {padding-top:4px}
|
td.w2p_fl,td.w2p_fc {padding-top:4px}
|
||||||
div.w2p_export_menu {margin:5px 0}
|
div.w2p_export_menu {white-space: wrap; margin:5px 0}
|
||||||
div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px;}
|
div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px; font-size:0.7em; color: black}
|
||||||
|
|
||||||
/* tr#submit_record__row {border-top:1px solid #E5E5E5} */
|
/* tr#submit_record__row {border-top:1px solid #E5E5E5} */
|
||||||
#submit_record__row td {padding-top:.5em}
|
#submit_record__row td {padding-top:.5em}
|
||||||
@@ -88,54 +21,30 @@ div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; pa
|
|||||||
#web2py_user_form td {vertical-align:top}
|
#web2py_user_form td {vertical-align:top}
|
||||||
|
|
||||||
/*********** web2py specific ***********/
|
/*********** web2py specific ***********/
|
||||||
div.flash {
|
div.w2p_flash {
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
display:none;
|
display:none;
|
||||||
position:fixed;
|
padding:20px 20px 20px 50px;
|
||||||
padding:10px;
|
width:100%;
|
||||||
top:48px;
|
|
||||||
right:250px;
|
|
||||||
min-width:280px;
|
|
||||||
opacity:0.95;
|
opacity:0.95;
|
||||||
margin:0px 0px 10px 10px;
|
|
||||||
vertical-align:middle;
|
vertical-align:middle;
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
color:#fff;
|
color:#000;
|
||||||
background-color:#000;
|
background-color:#ffdc00;
|
||||||
border:2px solid #fff;
|
|
||||||
border-radius:8px;
|
|
||||||
-o-border-radius: 8px;
|
|
||||||
-moz-border-radius:8px;
|
|
||||||
-webkit-border-radius:8px;
|
|
||||||
background-image: -webkit-linear-gradient(top,#222,#000);
|
|
||||||
background-image: -o-linear-gradient(top,#222,#000);
|
|
||||||
background-image: -moz-linear-gradient(90deg, #222, #000);
|
|
||||||
background-image: linear-gradient(top,#222,#000);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
z-index:2000;
|
z-index:2000;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.flash #closeflash{color:inherit; float:right; margin-left:15px;}
|
div.w2p_flash:before{content:"×";float:right; margin-right:100px; color:black;}
|
||||||
.ie-lte7 div.flash #closeflash
|
.ie-lte7 div.flash #closeflash
|
||||||
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
|
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
|
||||||
|
|
||||||
div.flash:hover { opacity:0.25; }
|
div.w2p_flash:hover { opacity:0.80; }
|
||||||
|
|
||||||
div.error_wrapper {display:block}
|
div.error_wrapper {display:block}
|
||||||
div.error {
|
div.error {
|
||||||
width: 298px;
|
color:red;
|
||||||
background:red;
|
|
||||||
border: 2px solid #d00;
|
|
||||||
color:white;
|
|
||||||
padding:5px;
|
padding:5px;
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
background-image: -webkit-linear-gradient(left,#f00,#fdd);
|
|
||||||
background-image: -o-linear-gradient(left,#f00,#fdd);
|
|
||||||
background-image: -moz-linear-gradient(0deg, #f00, #fdd);
|
|
||||||
background-image: linear-gradient(left,#f00,#fdd);
|
|
||||||
background-repeat: repeat-y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.topbar {
|
.topbar {
|
||||||
@@ -190,34 +99,8 @@ div.error {
|
|||||||
*/
|
*/
|
||||||
/* .web2py_table {border:1px solid #ccc} */
|
/* .web2py_table {border:1px solid #ccc} */
|
||||||
.web2py_paginator {}
|
.web2py_paginator {}
|
||||||
.web2py_grid {width:100%}
|
|
||||||
.web2py_grid table {width:100%}
|
.web2py_grid table {width:100%}
|
||||||
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
|
.web2py_grid td {color: black;}
|
||||||
.web2py_grid .web2py_form td {vertical-align: top;}
|
|
||||||
|
|
||||||
.web2py_grid thead th,.web2py_grid tfoot td {
|
|
||||||
background-color:#EAEAEA;
|
|
||||||
padding:10px 5px 10px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.web2py_grid tr.odd {background-color:#F9F9F9}
|
|
||||||
.web2py_grid tr:hover {background-color:#F5F5F5}
|
|
||||||
|
|
||||||
/*
|
|
||||||
.web2py_breadcrumbs a {
|
|
||||||
line-height:20px; margin-right:5px; display:inline-block;
|
|
||||||
padding:3px 5px 3px 5px;
|
|
||||||
font-family:'lucida grande',tahoma,verdana,arial,sans-serif;
|
|
||||||
color:#3C3C3D;
|
|
||||||
text-shadow:1px 1px 0 #FFFFFF;
|
|
||||||
white-space:nowrap; overflow:visible; cursor:pointer;
|
|
||||||
background:#ECECEC;
|
|
||||||
border:1px solid #CACACA;
|
|
||||||
-webkit-border-radius:2px; -moz-border-radius:2px;
|
|
||||||
-webkit-background-clip:padding-box; border-radius:2px;
|
|
||||||
outline:none; position:relative; zoom:1; *display:inline;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
.web2py_console form {
|
.web2py_console form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -302,11 +185,6 @@ li.w2p_grid_breadcrumb_elem {
|
|||||||
.web2py_console input, .web2py_console select,
|
.web2py_console input, .web2py_console select,
|
||||||
.web2py_console a { margin: 2px; }
|
.web2py_console a { margin: 2px; }
|
||||||
|
|
||||||
.web2py_htmltable {
|
|
||||||
width: 100%;
|
|
||||||
overflow-x: auto;
|
|
||||||
-ms-overflow-x:scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
#wiki_page_body {
|
#wiki_page_body {
|
||||||
width: 600px;
|
width: 600px;
|
||||||
@@ -317,13 +195,10 @@ li.w2p_grid_breadcrumb_elem {
|
|||||||
/* fix some IE problems */
|
/* fix some IE problems */
|
||||||
|
|
||||||
.ie-lte7 .topbar .container {z-index:2}
|
.ie-lte7 .topbar .container {z-index:2}
|
||||||
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
|
.ie-lte8 div.w2p_flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
|
||||||
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
|
.ie-lte8 div.w2p_flash:hover {filter:alpha(opacity=25);}
|
||||||
.ie9 #w2p_query_panel {padding-bottom:2px}
|
.ie9 #w2p_query_panel {padding-bottom:2px}
|
||||||
.control-label.readonly{
|
|
||||||
padding-top:0px !important;
|
|
||||||
padding-right:0px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.web2py_console .form-control {width: 20%; display: inline;}
|
||||||
|
.web2py_console #w2p_keywords {width: 50%;}
|
||||||
|
.web2py_search_actions a, .web2py_console input[type=submit], .web2py_console input[type=button], .web2py_console button { padding: 6px 12px; }
|
||||||
|
|||||||
@@ -1,264 +0,0 @@
|
|||||||
/*=============================================================
|
|
||||||
CUSTOM RULES
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
body{height:auto;} /* to avoid vertical scroll bar */
|
|
||||||
|
|
||||||
a{}
|
|
||||||
a:visited{}
|
|
||||||
a:hover{}
|
|
||||||
a:focus{}
|
|
||||||
a:active{}
|
|
||||||
|
|
||||||
h1{}
|
|
||||||
h2{}
|
|
||||||
h3{}
|
|
||||||
h4{}
|
|
||||||
h5{}
|
|
||||||
h6{}
|
|
||||||
|
|
||||||
div.flash.flash-center{left:25%;right:25%;}
|
|
||||||
div.flash.flash-top,div.flash.flash-top:hover{
|
|
||||||
position:relative;
|
|
||||||
display:block;
|
|
||||||
margin:0;
|
|
||||||
padding:1em;
|
|
||||||
top:0;
|
|
||||||
left:0;
|
|
||||||
width:100%;
|
|
||||||
text-align:center;
|
|
||||||
text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
|
|
||||||
color:#865100;
|
|
||||||
background:#feea9a;
|
|
||||||
border:1px solid;
|
|
||||||
border-top:0px;
|
|
||||||
border-left:0px;
|
|
||||||
border-right:0px;
|
|
||||||
border-radius:0;
|
|
||||||
opacity:1;
|
|
||||||
}
|
|
||||||
#header{margin-top:60px;}
|
|
||||||
.mastheader h1 {
|
|
||||||
margin-bottom:9px;
|
|
||||||
font-size:81px;
|
|
||||||
font-weight:bold;
|
|
||||||
letter-spacing:-1px;
|
|
||||||
line-height:1;
|
|
||||||
font-size:54px;
|
|
||||||
}
|
|
||||||
.mastheader small {
|
|
||||||
font-size:20px;
|
|
||||||
font-weight:300;
|
|
||||||
}
|
|
||||||
/* auth navbar - primitive style */
|
|
||||||
.auth_navbar,.auth_navbar a{color:inherit;}
|
|
||||||
.navbar-inner {-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
|
|
||||||
.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 */
|
|
||||||
div.error_wrapper{margin-bottom:9px;}
|
|
||||||
div.error_wrapper .error{
|
|
||||||
border-radius: 4px;
|
|
||||||
-o-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
}
|
|
||||||
/* 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;
|
|
||||||
vertical-align:middle;
|
|
||||||
}
|
|
||||||
div.controls .error{
|
|
||||||
min-width:5px;
|
|
||||||
background:inherit;
|
|
||||||
color:#B94A48;
|
|
||||||
border:none;
|
|
||||||
padding:0;
|
|
||||||
margin:0;
|
|
||||||
/*display:inline;*/ /* uncommenting this, the animation effect is lost */
|
|
||||||
}
|
|
||||||
div.controls .help-inline{color:#3A87AD;}
|
|
||||||
div.controls .error_wrapper +.help-inline {margin-left:-99999px;}
|
|
||||||
div.controls select +.error_wrapper {margin-left:5px;}
|
|
||||||
.ie-lte7 div.error{color:#fff;}
|
|
||||||
|
|
||||||
/* beautify brand */
|
|
||||||
.navbar {margin-bottom:0}
|
|
||||||
.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 */
|
|
||||||
span.highlighted{color:#d8d800;}
|
|
||||||
.open span.highlighted{color:#ffff00;}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
OVERRIDING WEB2PY.CSS RULES
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
/* reset to default */
|
|
||||||
a{white-space:normal;}
|
|
||||||
li{margin-bottom:0;}
|
|
||||||
textarea,button{display:block;}
|
|
||||||
/*reset ul padding */
|
|
||||||
ul#navbar{padding:0;}
|
|
||||||
/* label aligned to related input */
|
|
||||||
td.w2p_fl,td.w2p_fc {padding:0;}
|
|
||||||
#web2py_user_form td{vertical-align:middle;}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
OVERRIDING BOOTSTRAP.CSS RULES
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
/* because web2py handles this via js */
|
|
||||||
textarea { width:90%}
|
|
||||||
.hidden{visibility:visible;}
|
|
||||||
/* 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 */
|
|
||||||
.icon-white,
|
|
||||||
.nav-tabs > .active > a > [class^="icon-"],
|
|
||||||
.nav-tabs > .active > a > [class*=" icon-"],
|
|
||||||
.nav-pills > .active > a > [class^="icon-"],
|
|
||||||
.nav-pills > .active > a > [class*=" icon-"],
|
|
||||||
.nav-list > .active > a > [class^="icon-"],
|
|
||||||
.nav-list > .active > a > [class*=" icon-"],
|
|
||||||
.navbar-inverse .nav > .active > a > [class^="icon-"],
|
|
||||||
.navbar-inverse .nav > .active > a > [class*=" icon-"],
|
|
||||||
.dropdown-menu > li > a:hover > [class^="icon-"],
|
|
||||||
.dropdown-menu > li > a:hover > [class*=" icon-"],
|
|
||||||
.dropdown-menu > .active > a > [class^="icon-"],
|
|
||||||
.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 */
|
|
||||||
div>input[type="radio"],div>input[type="checkbox"]{margin:0;}
|
|
||||||
/* bootstrap has button instead of input */
|
|
||||||
input[type="button"], input[type="submit"]{margin-right:8px;}
|
|
||||||
|
|
||||||
/* web2py radio widget adjustment */
|
|
||||||
.generic-widget input[type='radio'] {margin:-1px 0 0 0; vertical-align: middle;}
|
|
||||||
.generic-widget input[type='radio'] + label {display:inline-block; margin:0 0 0 6px; vertical-align: middle;}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
RULES FOR SOLVING CONFLICTS BETWEEN WEB2PY.CSS AND BOOTSTRAP.CSS
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
/*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*/
|
|
||||||
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;}
|
|
||||||
div.w2p_fw,div.w2p_fc{
|
|
||||||
display:inline-block;
|
|
||||||
vertical-align:middle;
|
|
||||||
margin-bottom:0;
|
|
||||||
}
|
|
||||||
div.w2p_fc{
|
|
||||||
padding-left:5px;
|
|
||||||
margin-top:-8px;
|
|
||||||
}
|
|
||||||
/*when formstyle=ul*/
|
|
||||||
form>ul{
|
|
||||||
list-style:none;
|
|
||||||
margin:0;
|
|
||||||
}
|
|
||||||
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*/
|
|
||||||
#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*/
|
|
||||||
#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;}
|
|
||||||
div.control-group.error{
|
|
||||||
width:auto;
|
|
||||||
background:transparent;
|
|
||||||
border:0;
|
|
||||||
color:inherit;
|
|
||||||
padding:0;
|
|
||||||
background-repeat:repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
OTHER RULES
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
/* 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 */
|
|
||||||
.hidden{visibility:visible;}
|
|
||||||
.web2py_console input{
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 0;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.web2py_console input[type="submit"],
|
|
||||||
.web2py_console input[type="button"],
|
|
||||||
.web2py_console button{
|
|
||||||
padding-top:4px;
|
|
||||||
padding-bottom:4px;
|
|
||||||
margin:3px 0 0 2px;
|
|
||||||
}
|
|
||||||
.web2py_console a,
|
|
||||||
.web2py_console select,
|
|
||||||
.web2py_console input
|
|
||||||
{
|
|
||||||
margin:3px 0 0 2px;
|
|
||||||
}
|
|
||||||
.web2py_grid form table{width:auto;}
|
|
||||||
/* auth_user_remember checkbox extrapadding in IE fix */
|
|
||||||
.ie-lte9 input#auth_user_remember.checkbox {padding-left:0;}
|
|
||||||
|
|
||||||
div.controls .error {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
MEDIA QUERIES
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
@media only screen and (max-width:979px){
|
|
||||||
body{padding-top:0px;}
|
|
||||||
#navbar{/*top:5px;*/}
|
|
||||||
div.flash{right:5px;}
|
|
||||||
.dropdown-menu ul{visibility:visible;}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width:479px){
|
|
||||||
body{
|
|
||||||
padding-left:10px;
|
|
||||||
padding-right:10px;
|
|
||||||
}
|
|
||||||
.navbar-fixed-top,.navbar-fixed-bottom {
|
|
||||||
margin-left:-10px;
|
|
||||||
margin-right:-10px;
|
|
||||||
}
|
|
||||||
input[type="text"],input[type="password"],select{
|
|
||||||
width:95%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.navbar {
|
|
||||||
margin-right: -20px;
|
|
||||||
margin-left: -20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
/*=============================================================
|
|
||||||
BOOTSTRAP DROPDOWN MENU
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
.dropdown-menu ul{
|
|
||||||
left:100%;
|
|
||||||
position:absolute;
|
|
||||||
top:0;
|
|
||||||
visibility:hidden;
|
|
||||||
margin-top:-1px;
|
|
||||||
}
|
|
||||||
.dropdown-menu li:hover ul{visibility:visible;}
|
|
||||||
.navbar .dropdown-menu ul:before{
|
|
||||||
border-bottom:7px solid transparent;
|
|
||||||
border-left:none;
|
|
||||||
border-right:7px solid rgba(0, 0, 0, 0.2);
|
|
||||||
border-top:7px solid transparent;
|
|
||||||
left:-7px;
|
|
||||||
top:5px;
|
|
||||||
}
|
|
||||||
.nav > li.dropdown > a:after {
|
|
||||||
border-left: 4px solid transparent;
|
|
||||||
border-right: 4px solid transparent;
|
|
||||||
border-top: 4px solid #000000;
|
|
||||||
content: "";
|
|
||||||
display: inline-block;
|
|
||||||
height: 0;
|
|
||||||
opacity: 0.7;
|
|
||||||
vertical-align: top;
|
|
||||||
width: 0;
|
|
||||||
|
|
||||||
margin-left: 2px;
|
|
||||||
margin-top: 8px;
|
|
||||||
|
|
||||||
border-bottom-color: #FFFFFF;
|
|
||||||
border-top-color: #FFFFFF;
|
|
||||||
}
|
|
||||||
.dropdown-menu span{display:inline-block;}
|
|
||||||
ul.dropdown-menu li.dropdown > a:after {
|
|
||||||
border-left: 4px solid #000;
|
|
||||||
border-right: 4px solid transparent;
|
|
||||||
border-bottom: 4px solid transparent;
|
|
||||||
border-top: 4px solid transparent;
|
|
||||||
content: "";
|
|
||||||
display: inline-block;
|
|
||||||
height: 0;
|
|
||||||
opacity: 0.7;
|
|
||||||
vertical-align: top;
|
|
||||||
width: 0;
|
|
||||||
|
|
||||||
margin-left: 8px;
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.nav li.dropdown:hover ul.dropdown-menu {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.open >.dropdown-menu ul{display:block;} /* fix menu issue when BS2.0.4 is applied */
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
BOOTSTRAP SUBMIT BUTTON
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
input[type='submit']:not(.btn) {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 4px 14px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 20px;
|
|
||||||
color: #333;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
|
|
||||||
vertical-align: middle;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: whiteSmoke;
|
|
||||||
background-image: -webkit-gradient(linear,0 0,0 100%,from(white),to(#E6E6E6));
|
|
||||||
background-image: -webkit-linear-gradient(top,white,#E6E6E6);
|
|
||||||
background-image: -o-linear-gradient(top,white,#E6E6E6);
|
|
||||||
background-image: linear-gradient(to bottom,white,#E6E6E6);
|
|
||||||
background-image: -moz-linear-gradient(top,white,#E6E6E6);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border: 1px solid #BBB;
|
|
||||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
|
||||||
border-bottom-color: #A2A2A2;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);
|
|
||||||
filter: progid:dximagetransform.microsoft.gradient(enabled=false);
|
|
||||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);
|
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type='submit']:not(.btn):hover {
|
|
||||||
color: #333;
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: #E6E6E6;
|
|
||||||
background-position: 0 -15px;
|
|
||||||
-webkit-transition: background-position .1s linear;
|
|
||||||
-moz-transition: background-position .1s linear;
|
|
||||||
-o-transition: background-position .1s linear;
|
|
||||||
transition: background-position .1s linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type='submit']:not(.btn).active, input[type='submit']:not(.btn):active {
|
|
||||||
background-color: #E6E6E6;
|
|
||||||
background-color: #D9D9D9 9;
|
|
||||||
background-image: none;
|
|
||||||
outline: 0;
|
|
||||||
-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
-moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);
|
|
||||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*=============================================================
|
|
||||||
OTHER
|
|
||||||
==============================================================*/
|
|
||||||
|
|
||||||
.ie-lte8 .navbar-fixed-top {position:static;}
|
|
||||||
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
|||||||
// 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 adjust_height_of_collapsed_nav() {
|
|
||||||
var cn = jQuery('div.collapse');
|
|
||||||
if (cn.get(0)) {
|
|
||||||
var cnh = cn.get(0).style.height;
|
|
||||||
if (cnh>'0px'){
|
|
||||||
cn.css('height','auto');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function hoverMenu(){
|
|
||||||
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
|
|
||||||
adjust_height_of_collapsed_nav();
|
|
||||||
var mi = jQuery(this).addClass('open');
|
|
||||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
|
|
||||||
}, function(){
|
|
||||||
var 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');});
|
|
||||||
});
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
{{extend 'layout.html'}}
|
{{extend 'layout.html'}}
|
||||||
|
|
||||||
<iframe src="//player.vimeo.com/hubnut/album/3016728?color=ff6600&background=ffffff&slideshow=1&video_title=1&video_byline=1" width="400" height="300" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
|
<center>
|
||||||
|
<iframe src="//player.vimeo.com/hubnut/album/3016728?color=ff6600&background=ffffff&slideshow=1&video_title=1&video_byline=1" width="400" height="300" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
|
||||||
|
</center>
|
||||||
|
|
||||||
<div class="contentleft">
|
<div>
|
||||||
<div >
|
{{=get_content('main')}}
|
||||||
{{=get_content('main')}}
|
{{=get_content('official')}}
|
||||||
</div>
|
{{=get_content('community')}}
|
||||||
{{=get_content('official')}}
|
{{=get_content('more')}}
|
||||||
{{=get_content('community')}}
|
|
||||||
{{=get_content('more')}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,33 +5,59 @@
|
|||||||
|
|
||||||
<h2>web2py<sup style="font-size:0.5em;">TM</sup> Download</h2>
|
<h2>web2py<sup style="font-size:0.5em;">TM</sup> Download</h2>
|
||||||
|
|
||||||
<center style="padding:20px">
|
<center class="spaced">
|
||||||
<table class="downloads">
|
<table class="twothirds">
|
||||||
<tr>
|
<thead>
|
||||||
<th>For Normal Users</th>
|
<tr>
|
||||||
<th>For Testers</th>
|
<th>For Normal Users</th>
|
||||||
<th>For Developers</th>
|
<th>For Testers</th>
|
||||||
</tr>
|
<th>For Developers</th>
|
||||||
<tr>
|
</tr>
|
||||||
<td><a class="btn btn-180 btn-success" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a></td>
|
</thead>
|
||||||
<td><a class="btn btn-180 btn-warning" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a></td>
|
<tbody>
|
||||||
<td><a class="btn btn-180 btn-danger" href="http://github.com/web2py/web2py/">Git Repository</a></td>
|
<tr>
|
||||||
</tr>
|
<td>
|
||||||
<tr>
|
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a>
|
||||||
<td><a class="btn btn-180 btn-success" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a></td>
|
</td>
|
||||||
<td><a class="btn btn-180 btn-warning" href="http://www.web2py.com/examples/static/nightly/web2py_osx.zip">For Mac</a></td>
|
<td>
|
||||||
<td></td>
|
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a>
|
||||||
</tr>
|
</td>
|
||||||
<tr>
|
<td>
|
||||||
<td><a class="btn btn-180 btn-success" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a></td>
|
<a class="btn btn180 rounded red" href="http://github.com/web2py/web2py/">Git Repository</a>
|
||||||
<td><a class="btn btn-180 btn-warning" href="http://www.web2py.com/examples/static/nightly/web2py_src.zip">Source Code</a></td>
|
</td>
|
||||||
<td><a class="btn btn-180 btn-danger" href="http://web2py.readthedocs.org/en/latest/">Source code docs</a></td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>
|
||||||
<td><a class="btn btn-180 btn-success" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a></td>
|
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a>
|
||||||
<td><a class="btn btn-180" href="https://github.com/web2py/web2py/releases">Change Log</a></td>
|
</td>
|
||||||
<td><a class="btn btn-180" href="https://github.com/web2py/web2py/issues">Report a Bug</a></td>
|
<td>
|
||||||
</tr>
|
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_osx.zip">For Mac</a>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_src.zip">Source Code</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a class="btn btn180 rounded red" href="http://web2py.readthedocs.org/en/latest/">Source code docs</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a class="btn btn180 rounded red" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/issues">Report a Bug</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</center>
|
</center>
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ def hello1():
|
|||||||
return "Hello World"
|
return "Hello World"
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
|
|
||||||
<p>If the controller function returns a string, that is the body of the rendered page.<br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello1">hello1</a></p>
|
<p>If the controller function returns a string, that is the body of the rendered page.<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello1">hello1</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -40,7 +40,7 @@ def hello2():
|
|||||||
return T("Hello World")
|
return T("Hello World")
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
|
|
||||||
<p>The function T() marks strings that need to be translated. Translation dictionaries can be created at /admin/default/design<br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello2">hello2</a></p>
|
<p>The function T() marks strings that need to be translated. Translation dictionaries can be created at /admin/default/design<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello2">hello2</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -51,7 +51,7 @@ def hello3():
|
|||||||
<b>and view: simple_examples/hello3.html</b>
|
<b>and view: simple_examples/hello3.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/simple_examples/hello3.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/simple_examples/hello3.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>If you return a dictionary, the variables defined in the dictionery are visible to the view (template).
|
<p>If you return a dictionary, the variables defined in the dictionery are visible to the view (template).
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello3.html">hello3</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello3.html">hello3</a></p>
|
||||||
|
|
||||||
<p>Actions can also be be rendered in other formsts like JSON, <a href="/{{=request.application}}/simple_examples/hello3.json">hello3.json</a>, and XML, <a href="/{{=request.application}}/simple_examples/hello3.xml">hello3.xml</a></p>
|
<p>Actions can also be be rendered in other formsts like JSON, <a href="/{{=request.application}}/simple_examples/hello3.json">hello3.json</a>, and XML, <a href="/{{=request.application}}/simple_examples/hello3.xml">hello3.xml</a></p>
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ def hello4():
|
|||||||
return dict(message=T("Hello World"))
|
return dict(message=T("Hello World"))
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>You can change the view, but the default is /[controller]/[function].html. If the default is not found web2py tries to render the page using the generic.html view.
|
<p>You can change the view, but the default is /[controller]/[function].html. If the default is not found web2py tries to render the page using the generic.html view.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello4">hello4</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello4">hello4</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -76,7 +76,7 @@ def hello5():
|
|||||||
<li>named arguments and name starts with '_'. These are mapped blindly into tag attributes and the '_' is removed. attributes without value like "READONLY" can be created with the argument "_readonly=ON".</li>
|
<li>named arguments and name starts with '_'. These are mapped blindly into tag attributes and the '_' is removed. attributes without value like "READONLY" can be created with the argument "_readonly=ON".</li>
|
||||||
<li>named arguments and name does not start with '_'. They have a special meaning. See "value=" for INPUT, TEXTAREA, SELECT tags later.
|
<li>named arguments and name does not start with '_'. They have a special meaning. See "value=" for INPUT, TEXTAREA, SELECT tags later.
|
||||||
</ul>
|
</ul>
|
||||||
<p>Try it here: <a href="/{{=request.application}}/simple_examples/hello5">hello5</a></p>
|
<p>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello5">hello5</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -86,7 +86,7 @@ def hello6():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
|
|
||||||
<p>response.flash allows you to flash a message to the user when the page is returned. Use session.flash instead of response.flash to display a message after redirection. With default layout, you can click on the flash to make it disappear.
|
<p>response.flash allows you to flash a message to the user when the page is returned. Use session.flash instead of response.flash to display a message after redirection. With default layout, you can click on the flash to make it disappear.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello6">hello6</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello6">hello6</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -94,7 +94,7 @@ def status():
|
|||||||
return dict(toobar=response.toolbar())
|
return dict(toobar=response.toolbar())
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>Here we are showing the request, session and response objects using the generic.html template.
|
<p>Here we are showing the request, session and response objects using the generic.html template.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/status">status</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/status">status</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -102,7 +102,7 @@ def redirectme():
|
|||||||
redirect(URL('hello3'))
|
redirect(URL('hello3'))
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>You can do redirect.
|
<p>You can do redirect.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/redirectme">redirectme</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/redirectme">redirectme</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -110,7 +110,7 @@ def raisehttp():
|
|||||||
raise HTTP(400,"internal error")
|
raise HTTP(400,"internal error")
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>You can raise HTTP exceptions to return an error page.
|
<p>You can raise HTTP exceptions to return an error page.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/raisehttp">raisehttp</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/raisehttp">raisehttp</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -128,7 +128,7 @@ def servejs():
|
|||||||
return 'alert("This is a Javascript document, it is not supposed to run!");'
|
return 'alert("This is a Javascript document, it is not supposed to run!");'
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>You can serve other than HTML pages by changing the contenttype via the response.headers. The gluon.contenttype module can help you figure the type of the file to be served. NOTICE: this is not necessary for static files unless you want to require authorization.
|
<p>You can serve other than HTML pages by changing the contenttype via the response.headers. The gluon.contenttype module can help you figure the type of the file to be served. NOTICE: this is not necessary for static files unless you want to require authorization.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/servejs">servejs</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/servejs">servejs</a></p>
|
||||||
|
|
||||||
<h3 id="example_json">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
<h3 id="example_json">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -136,7 +136,7 @@ def servejs():
|
|||||||
return response.json(['foo', {'bar': ('baz', None, 1.0, 2)}])
|
return response.json(['foo', {'bar': ('baz', None, 1.0, 2)}])
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>If you are into Ajax, web2py includes gluon.contrib.<a href="http://cheeseshop.python.org/pypi/simplejson">simplejson</a>, developed by Bob Ippolito. This module provides a fast and easy way to serve asynchronous content to your Ajax page. gluon.simplesjson.dumps(...) can serialize most Python types into <a href="http://www.json.org">JSON</a>. gluon.contrib.simplejson.loads(...) performs the reverse operation.
|
<p>If you are into Ajax, web2py includes gluon.contrib.<a href="http://cheeseshop.python.org/pypi/simplejson">simplejson</a>, developed by Bob Ippolito. This module provides a fast and easy way to serve asynchronous content to your Ajax page. gluon.simplesjson.dumps(...) can serialize most Python types into <a href="http://www.json.org">JSON</a>. gluon.contrib.simplejson.loads(...) performs the reverse operation.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/makejson">makejson</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/makejson">makejson</a></p>
|
||||||
|
|
||||||
<p>New in web2py 1.63: Any normal action returning a dict is automatically serialized in JSON if '.json' is appended to the URL.</p>
|
<p>New in web2py 1.63: Any normal action returning a dict is automatically serialized in JSON if '.json' is appended to the URL.</p>
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ def makertf():
|
|||||||
response.headers['Content-Type']='text/rtf'
|
response.headers['Content-Type']='text/rtf'
|
||||||
return q.dumps(doc)
|
return q.dumps(doc)
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>web2py also includes gluon.contrib.<a href="http://pyrtf.sourceforge.net/">pyrtf</a>, developed by Simon Cusack and revised by Grant Edwards. This module allows you to generate Rich Text Format documents including colored formatted text and pictures.<br/>Try it here: <a href="/{{=request.application}}/simple_examples/makertf">makertf</a></p>
|
<p>web2py also includes gluon.contrib.<a href="http://pyrtf.sourceforge.net/">pyrtf</a>, developed by Simon Cusack and revised by Grant Edwards. This module allows you to generate Rich Text Format documents including colored formatted text and pictures.<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/makertf">makertf</a></p>
|
||||||
|
|
||||||
<h3 id="example_rss">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
<h3 id="example_rss">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -179,7 +179,7 @@ def rss_aggregator():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>web2py includes gluon.contrib.<a href="http://www.dalkescientific.com/Python/PyRSS2Gen.html">rss2</a>, developed by Dalke Scientific Software, which generates RSS2 feeds, and
|
<p>web2py includes gluon.contrib.<a href="http://www.dalkescientific.com/Python/PyRSS2Gen.html">rss2</a>, developed by Dalke Scientific Software, which generates RSS2 feeds, and
|
||||||
gluon.contrib.<a href="http://www.feedparser.org/">feedparser</a>, developed by Mark Pilgrim, which collects RSS and ATOM feeds. The above controller collects a slashdot feed and makes new one.
|
gluon.contrib.<a href="http://www.feedparser.org/">feedparser</a>, developed by Mark Pilgrim, which collects RSS and ATOM feeds. The above controller collects a slashdot feed and makes new one.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/rss_aggregator">rss_aggregator</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/rss_aggregator">rss_aggregator</a></p>
|
||||||
|
|
||||||
|
|
||||||
<h3 id="example_wiki">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
<h3 id="example_wiki">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
|
||||||
@@ -194,7 +194,7 @@ def ajaxwiki_onclick():
|
|||||||
return MARKMIN(request.vars.text).xml()
|
return MARKMIN(request.vars.text).xml()
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>The markmin wiki markup is described <a href="{{=URL('static','markmin.html')}}">here</a>.
|
<p>The markmin wiki markup is described <a href="{{=URL('static','markmin.html')}}">here</a>.
|
||||||
web2py also includes gluon.contrib.<a href="http://code.google.com/p/python-markdown2/">markdown</a>.WIKI helper (markdown2) which converts WIKI markup to HTML following <a href="http://en.wikipedia.org/wiki/Markdown">this syntax</a>. In this example we added a fancy ajax effect.<br/>Try it here: <a href="/{{=request.application}}/simple_examples/ajaxwiki">ajaxwiki</a></p>
|
web2py also includes gluon.contrib.<a href="http://code.google.com/p/python-markdown2/">markdown</a>.WIKI helper (markdown2) which converts WIKI markup to HTML following <a href="http://en.wikipedia.org/wiki/Markdown">this syntax</a>. In this example we added a fancy ajax effect.<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/ajaxwiki">ajaxwiki</a></p>
|
||||||
|
|
||||||
<h2 id="session_examples">Session Examples</h2>
|
<h2 id="session_examples">Session Examples</h2>
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ def counter():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: session_examples/counter.html</b>
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: session_examples/counter.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/session_examples/counter.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/session_examples/counter.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>Click to count. The session.counter is persistent for this user and application. Every applicaiton within the system has its own separate session management.
|
<p>Click to count. The session.counter is persistent for this user and application. Every applicaiton within the system has its own separate session management.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/session_examples/counter">counter</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/session_examples/counter">counter</a></p>
|
||||||
|
|
||||||
<h2 id="template_examples">Template Examples</h2>
|
<h2 id="template_examples">Template Examples</h2>
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ def variables():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/variables.html</b>
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/variables.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/template_examples/variables.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/template_examples/variables.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>A view (also known as template) is just an HTML file with {{...}} tags. You can put ANY python code into the tags, no need to indent but you must use pass to close blocks. The view is transformed into a python code and then executed. {{=a}} prints a.xml() or escape(str(a)).
|
<p>A view (also known as template) is just an HTML file with {{...}} tags. You can put ANY python code into the tags, no need to indent but you must use pass to close blocks. The view is transformed into a python code and then executed. {{=a}} prints a.xml() or escape(str(a)).
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/template_examples/variables">variables</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/variables">variables</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -228,7 +228,7 @@ def test_for():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_for.html</b>
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_for.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_for.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_for.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>You can do for and while loops.
|
<p>You can do for and while loops.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/template_examples/test_for">test_for</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/test_for">test_for</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -237,7 +237,7 @@ def test_if():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_if.html</b>
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_if.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_if.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_if.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>You can do if, elif, else.
|
<p>You can do if, elif, else.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/template_examples/test_if">test_if</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/test_if">test_if</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -246,7 +246,7 @@ def test_try():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_try.html</b>
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_try.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_try.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_try.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>You can do try, except, finally.
|
<p>You can do try, except, finally.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/template_examples/test_try">test_try</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/test_try">test_try</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -255,7 +255,7 @@ def test_def():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_def.html</b>
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_def.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_def.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_def.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>You can write functions in HTML too.
|
<p>You can write functions in HTML too.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/template_examples/test_def">test_def</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/test_def">test_def</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -264,7 +264,7 @@ def escape():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/escape.html</b>
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/escape.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/template_examples/escape.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/template_examples/escape.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>The argument of {{=...}} is always escaped unless it is an object with a .xml() method such as link, A(...), a FORM(...), a XML(...) block, etc.
|
<p>The argument of {{=...}} is always escaped unless it is an object with a .xml() method such as link, A(...), a FORM(...), a XML(...) block, etc.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/template_examples/escape">escape</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/escape">escape</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -273,7 +273,7 @@ def xml():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/xml.html</b>
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/xml.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/template_examples/xml.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/template_examples/xml.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>If you do not want to escape the argument of {{=...}} mark it as XML.
|
<p>If you do not want to escape the argument of {{=...}} mark it as XML.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/template_examples/xml">xml</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/xml">xml</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -282,7 +282,7 @@ def beautify():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/beautify.html</b>
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/beautify.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/template_examples/beautify.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/template_examples/beautify.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>You can use BEAUTIFY to turn lists and dictionaries into organized HTML.
|
<p>You can use BEAUTIFY to turn lists and dictionaries into organized HTML.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/template_examples/beautify">beautify</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/beautify">beautify</a></p>
|
||||||
|
|
||||||
<h2 id="layout_examples">Layout Examples</h2>
|
<h2 id="layout_examples">Layout Examples</h2>
|
||||||
|
|
||||||
@@ -298,7 +298,7 @@ def civilized():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/civilized.html</b>
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/civilized.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/layout_examples/civilized.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/layout_examples/civilized.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>You can specify the layout file at the top of your view. civilized Layout file is a view that somewhere in the body contains {{include}}.
|
<p>You can specify the layout file at the top of your view. civilized Layout file is a view that somewhere in the body contains {{include}}.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/layout_examples/civilized">civilized</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/layout_examples/civilized">civilized</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: layout_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: layout_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -310,7 +310,7 @@ def slick():
|
|||||||
return dict(message="you clicked on slick")
|
return dict(message="you clicked on slick")
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/slick.html</b>
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/slick.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/layout_examples/slick.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/layout_examples/slick.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>Same here, but using a different template.<br/>Try it here: <a href="/{{=request.application}}/layout_examples/slick">slick</a></p>
|
<p>Same here, but using a different template.<br/>Try it here: <a class="btn" href="/{{=request.application}}/layout_examples/slick">slick</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: layout_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: layout_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -323,7 +323,7 @@ def basic():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/basic.html</b>
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/basic.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/layout_examples/basic.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/layout_examples/basic.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>'layout.html' is the default template, every application has a copy of it.
|
<p>'layout.html' is the default template, every application has a copy of it.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/layout_examples/basic">basic</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/layout_examples/basic">basic</a></p>
|
||||||
|
|
||||||
<h2 id="form_examples">Form Examples</h2>
|
<h2 id="form_examples">Form Examples</h2>
|
||||||
|
|
||||||
@@ -347,7 +347,7 @@ def form():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>You can use HTML helpers like FORM, INPUT, TEXTAREA, OPTION, SELECT to build forms. The "value=" attribute sets the initial value of the field (works for TEXTAREA and OPTION/SELECT too) and the requires attribute sets the validators.
|
<p>You can use HTML helpers like FORM, INPUT, TEXTAREA, OPTION, SELECT to build forms. The "value=" attribute sets the initial value of the field (works for TEXTAREA and OPTION/SELECT too) and the requires attribute sets the validators.
|
||||||
FORM.accepts(..) tries to validate the form and, on success, stores vars into form.vars. On failure the error messages are stored into form.errors and shown in the form.
|
FORM.accepts(..) tries to validate the form and, on success, stores vars into form.vars. On failure the error messages are stored into form.errors and shown in the form.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/form_examples/form">form</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/form_examples/form">form</a></p>
|
||||||
|
|
||||||
<h2 id="database_examples">Database Examples</h2>
|
<h2 id="database_examples">Database Examples</h2>
|
||||||
|
|
||||||
@@ -497,7 +497,7 @@ def cache_in_ram():
|
|||||||
return dict(time=t,link=A('click to reload',_href=URL(r=request)))
|
return dict(time=t,link=A('click to reload',_href=URL(r=request)))
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>The output of <tt>lambda:time.ctime()</tt> is cached in ram for 5 seconds. The string 'time' is used as cache key.
|
<p>The output of <tt>lambda:time.ctime()</tt> is cached in ram for 5 seconds. The string 'time' is used as cache key.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_in_ram">cache_in_ram</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_in_ram">cache_in_ram</a></p>
|
||||||
|
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
|
||||||
@@ -508,7 +508,7 @@ def cache_on_disk():
|
|||||||
return dict(time=t,link=A('click to reload',_href=URL(r=request)))
|
return dict(time=t,link=A('click to reload',_href=URL(r=request)))
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>The output of <tt>lambda:time.ctime()</tt> is cached on disk (using the shelve module) for 5 seconds.
|
<p>The output of <tt>lambda:time.ctime()</tt> is cached on disk (using the shelve module) for 5 seconds.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_on_disk">cache_on_disk</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_on_disk">cache_on_disk</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -519,7 +519,7 @@ def cache_in_ram_and_disk():
|
|||||||
return dict(time=t,link=A('click to reload',_href=URL(r=request)))
|
return dict(time=t,link=A('click to reload',_href=URL(r=request)))
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>The output of <tt>lambda:time.ctime()</tt> is cached on disk (using the shelve module) and then in ram for 5 seconds. web2py looks in ram first and if not there it looks on disk. If it is not on disk it calls the function. This is useful in a multiprocess type of environment. The two times do not have to be the same.
|
<p>The output of <tt>lambda:time.ctime()</tt> is cached on disk (using the shelve module) and then in ram for 5 seconds. web2py looks in ram first and if not there it looks on disk. If it is not on disk it calls the function. This is useful in a multiprocess type of environment. The two times do not have to be the same.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_in_ram_and_disk">cache_in_ram_and_disk</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_in_ram_and_disk">cache_in_ram_and_disk</a></p>
|
||||||
|
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
|
||||||
@@ -530,7 +530,7 @@ def cache_in_ram_and_disk():
|
|||||||
t=time.ctime()
|
t=time.ctime()
|
||||||
return dict(time=t,link=A('click to reload',_href=URL(r=request)))""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
return dict(time=t,link=A('click to reload',_href=URL(r=request)))""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>Here the entire controller (dictionary) is cached in ram for 5 seconds. The result of a select cannot be cached unless it is first serialized into a table <tt>lambda:SQLTABLE(db().select(db.user.ALL)).xml()</tt>. You can read below for an even better way to do it.
|
<p>Here the entire controller (dictionary) is cached in ram for 5 seconds. The result of a select cannot be cached unless it is first serialized into a table <tt>lambda:SQLTABLE(db().select(db.user.ALL)).xml()</tt>. You can read below for an even better way to do it.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_controller_in_ram">cache_controller_in_ram</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_controller_in_ram">cache_controller_in_ram</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -541,7 +541,7 @@ def cache_controller_on_disk():
|
|||||||
return dict(time=t,link=A('click to reload',_href=URL(r=request)))
|
return dict(time=t,link=A('click to reload',_href=URL(r=request)))
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>Here the entire controller (dictionary) is cached on disk for 5 seconds. This will not work if the dictionary contains unpickleable objects.
|
<p>Here the entire controller (dictionary) is cached on disk for 5 seconds. This will not work if the dictionary contains unpickleable objects.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_controller_on_disk">cache_controller_on_disk</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_controller_on_disk">cache_controller_on_disk</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -553,7 +553,7 @@ def cache_controller_and_view():
|
|||||||
return response.render(d)
|
return response.render(d)
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p><tt>response.render(d)</tt> renders the dictionary inside the controller, so everything is cached now for 5 seconds. This is best and fastest way of caching!
|
<p><tt>response.render(d)</tt> renders the dictionary inside the controller, so everything is cached now for 5 seconds. This is best and fastest way of caching!
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_controller_and_view">cache_controller_and_view</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_controller_and_view">cache_controller_and_view</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -583,7 +583,7 @@ def data():
|
|||||||
<b>In view: ajax_examples/index.html</b>
|
<b>In view: ajax_examples/index.html</b>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/ajax_examples/index.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/ajax_examples/index.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>The javascript function "ajax" is provided in "web2py_ajax.html" and included by "layout.html". It takes three arguments, a url, a list of ids and a target id. When called, it sends to the url (via a get) the values of the ids and display the response in the value (of innerHTML) of the target id.
|
<p>The javascript function "ajax" is provided in "web2py_ajax.html" and included by "layout.html". It takes three arguments, a url, a list of ids and a target id. When called, it sends to the url (via a get) the values of the ids and display the response in the value (of innerHTML) of the target id.
|
||||||
<br/>Try it here: <a href="/{{=request.application}}/ajax_examples/index">index</a></p>
|
<br/>Try it here: <a class="btn" href="/{{=request.application}}/ajax_examples/index">index</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: ajax_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: ajax_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -591,7 +591,7 @@ def flash():
|
|||||||
response.flash='this text should appear!'
|
response.flash='this text should appear!'
|
||||||
return dict()
|
return dict()
|
||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>Try it here: <a href="/{{=request.application}}/ajax_examples/flash">flash</a></p>
|
<p>Try it here: <a class="btn" href="/{{=request.application}}/ajax_examples/flash">flash</a></p>
|
||||||
|
|
||||||
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: ajax_examples.py </b>
|
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: ajax_examples.py </b>
|
||||||
{{=CODE("""
|
{{=CODE("""
|
||||||
@@ -600,7 +600,7 @@ def fade():
|
|||||||
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<b>In view: ajax_examples/fade.html </b><br/>
|
<b>In view: ajax_examples/fade.html </b><br/>
|
||||||
{{=CODE(open(os.path.join(request.folder,'views/ajax_examples/fade.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
{{=CODE(open(os.path.join(request.folder,'views/ajax_examples/fade.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
|
||||||
<p>Try it here: <a href="/{{=request.application}}/ajax_examples/fade">fade</a></p>
|
<p>Try it here: <a class="btn" href="/{{=request.application}}/ajax_examples/fade">fade</a></p>
|
||||||
|
|
||||||
<h3>Excel-like spreadsheet via Ajax</h3>
|
<h3>Excel-like spreadsheet via Ajax</h3>
|
||||||
Web2py includes a widget that acts like an Excel-like spreadsheet and can be used to build forms
|
Web2py includes a widget that acts like an Excel-like spreadsheet and can be used to build forms
|
||||||
|
|||||||
@@ -1,21 +1,8 @@
|
|||||||
{{extend 'layout.html'}}
|
{{extend 'layout.html'}}
|
||||||
{{
|
|
||||||
import random
|
|
||||||
quotes = [
|
|
||||||
("web2py was the life saver today for me, my blog post: Standalone Usage of web2py's", "caglartoklu", "http://twitter.com/#!/caglartoklu/status/84292131707031553"),
|
|
||||||
("Get Things Done - Faster, Better and More Easily with web2py",
|
|
||||||
"Bruno Rocha", "http://twitter.com/#!/rochacbruno/status/73583156044890112"),
|
|
||||||
("Please use www.web2py.com when using MVC , no PHP/SQL stuff please...its 2011 not 1999", "rabblesoft", "http://twitter.com/#!/rabblesoft/status/79189028431343616"),
|
|
||||||
('web2py rules! as a sysadmin I like the no installation and no configuration approach a lot)', "kjogut", "http://twitter.com/#!/jkogut/status/61414554273447936"),
|
|
||||||
("web2py it is. Compatible with everything under the sun and great interfaces to googleappengine", "comamitc","http://twitter.com/#!/comamitc/status/51744719071477760"),
|
|
||||||
("If you are still learning python, web2py is best tool by far", "pbreit", "http://twitter.com/#!/pbreit/status/48260905775017984")
|
|
||||||
]
|
|
||||||
random.shuffle(quotes)
|
|
||||||
}}
|
|
||||||
|
|
||||||
<div class="row-fluid">
|
<div class="container">
|
||||||
<div class="span12">
|
<div class="twothirds">
|
||||||
<div class="span8">
|
<div class="padded">
|
||||||
<h3>web2py<sup>TM</sup> Web Framework</h3>
|
<h3>web2py<sup>TM</sup> Web Framework</h3>
|
||||||
<p>Free open source full-stack framework for rapid development of fast, scalable, <a href="http://www.web2py.com/book/default/chapter/01#Security" target="_blank">secure</a> and portable database-driven web-based applications. Written and programmable in <a href="http://www.python.org" target="_blank">Python</a>.</p>
|
<p>Free open source full-stack framework for rapid development of fast, scalable, <a href="http://www.web2py.com/book/default/chapter/01#Security" target="_blank">secure</a> and portable database-driven web-based applications. Written and programmable in <a href="http://www.python.org" target="_blank">Python</a>.</p>
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
@@ -39,45 +26,46 @@ random.shuffle(quotes)
|
|||||||
</table>
|
</table>
|
||||||
<p>Current version: <a href="{{=URL('download')}}">{{=request.env.web2py_version}} (<a href="http://www.gnu.org/licenses/lgpl.html">LGPLv3 License</a>)</p>
|
<p>Current version: <a href="{{=URL('download')}}">{{=request.env.web2py_version}} (<a href="http://www.gnu.org/licenses/lgpl.html">LGPLv3 License</a>)</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="span4" style="text-align:center">
|
</div>
|
||||||
<a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23"><img src="{{=URL('static','images/infoworld2012.jpeg')}}" width="200px"/></a><br/>
|
<div class="third">
|
||||||
<a class="btn btn-danger" href="{{=URL('download')}}" style="margin-top:10px; width:180px; color:white">Download Now</a><br/>
|
<div class="padded center">
|
||||||
<a class="btn btn-danger" href="https://www.pythonanywhere.com/try-web2py" style="margin-top:10px; width:180px; color:white">Try it now online</a><br/>
|
<a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23">
|
||||||
<a class="btn btn-danger" href="http://web2py.com/poweredby" style="margin-top:10px; width:180px; color:white">Sites Powered by web2py</a><br/><br/>
|
<img src="{{=URL('static','images/infoworld2012.jpeg')}}">
|
||||||
<a class="coinbase-button" data-code="df71ec5c2d5bc3b1c18139ab645f352b" data-button-style="donation_large" href="https://coinbase.com/checkouts/df71ec5c2d5bc3b1c18139ab645f352b">Donate Bitcoins</a><script src="https://coinbase.com/assets/button.js" type="text/javascript"></script>
|
</a>
|
||||||
|
<a class="btn rounded red fill" href="{{=URL('download')}}">
|
||||||
|
Download Now
|
||||||
|
</a>
|
||||||
|
<a class="btn rounded red fill" href="https://www.pythonanywhere.com/try-web2py">
|
||||||
|
Try it now online
|
||||||
|
</a>
|
||||||
|
<a class="btn rounded red fill" href="http://web2py.com/poweredby">
|
||||||
|
Sites Powered by web2py
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-fluid">
|
<div class="container">
|
||||||
<div class="span12">
|
<div class="third">
|
||||||
<div class="span4">
|
<div class="padded">
|
||||||
<h3><a href="{{=URL('what')}}">Batteries Included</a></h3>
|
<h5><a href="{{=URL('what')}}">Batteries Included</a></h5>
|
||||||
<p>Everything you need in one package including fast multi-threaded web server, SQL database and web-based interface. No third party dependencies but works with <a href={{=URL('what')}}>third party tools</a>.</p>
|
<p>Everything you need in one package including fast multi-threaded web server, SQL database and web-based interface. No third party dependencies but works with <a href={{=URL('what')}}>third party tools</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="span4">
|
</div>
|
||||||
<h3><a href="http://web2py.com/demo_admin">Web-Based IDE</a></h3>
|
<div class="third">
|
||||||
|
<div class="padded">
|
||||||
|
<h5><a href="http://web2py.com/demo_admin">Web-Based IDE</a></h5>
|
||||||
<p>Create, modify, deploy and manage application from anywhere using your browser. One web2py instance can run multiple web sites using different databases. Try the <a href="http://www.web2py.com/demo_admin">interactive demo</a>.</p>
|
<p>Create, modify, deploy and manage application from anywhere using your browser. One web2py instance can run multiple web sites using different databases. Try the <a href="http://www.web2py.com/demo_admin">interactive demo</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="span4">
|
</div>
|
||||||
<h3><a href="{{=URL('documentation')}}">Extensive Docs</a></h3>
|
<div class="third">
|
||||||
|
<div class="padded">
|
||||||
|
<h5><a href="{{=URL('documentation')}}">Extensive Docs</a></h5>
|
||||||
<p>Start with some <a href="{{=URL('examples')}}">quick examples</a>, then read the <a href="http://www.web2py.com/book" target="_blank">manual</a> and the <a href="http://web2py.readthedocs.org/en/latest/" target="_blank">Sphinx docs</a>, watch <a href="http://vimeo.com/album/178500" target="_blank">videos</a>, and join a <a href="{{=URL('default', 'usergroups')}}">user group</a> for discussion. Take advantage of the <a href="http://www.web2py.com/layouts" target="_blank">layouts</a>, <a href="http://dev.s-cubism.com/web2py_plugins" target="_blank">plugins</a>, <a href="http://www.web2py.com/appliances" target="_blank">appliances</a>, and <a href="http://web2pyslices.com" target="_blank">recipes</a>.</p>
|
<p>Start with some <a href="{{=URL('examples')}}">quick examples</a>, then read the <a href="http://www.web2py.com/book" target="_blank">manual</a> and the <a href="http://web2py.readthedocs.org/en/latest/" target="_blank">Sphinx docs</a>, watch <a href="http://vimeo.com/album/178500" target="_blank">videos</a>, and join a <a href="{{=URL('default', 'usergroups')}}">user group</a> for discussion. Take advantage of the <a href="http://www.web2py.com/layouts" target="_blank">layouts</a>, <a href="http://dev.s-cubism.com/web2py_plugins" target="_blank">plugins</a>, <a href="http://www.web2py.com/appliances" target="_blank">appliances</a>, and <a href="http://web2pyslices.com" target="_blank">recipes</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-fluid">
|
<div class="container">
|
||||||
<div class="span12">
|
<div class="fill padded">
|
||||||
<img class="scale-with-grid centered" src="/examples/static/images/shadow-bottom.png">
|
<img class="scale-with-grid centered" src="/examples/static/images/shadow-bottom.png">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-fluid">
|
|
||||||
<div class="span12">
|
|
||||||
{{for k,quote in enumerate(quotes[:3]):}}
|
|
||||||
<div class="span4">
|
|
||||||
<p style="text-align: left"><em>{{=quote[0]}}</em></p>
|
|
||||||
<span class="right">
|
|
||||||
<a href="{{=quote[2]}}">{{=quote[1]}}</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{{pass}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -17,14 +17,11 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a target="_blank" href="http://experts4solutions.com">Experts4Soutions</a> (worldwide)</li>
|
<li><a target="_blank" href="http://experts4solutions.com">Experts4Soutions</a> (worldwide)</li>
|
||||||
<li><a target="_blank" href="http://www.planethost.com">PlanetHost</a> (USA)</li>
|
<li><a target="_blank" href="http://www.planethost.com">PlanetHost</a> (USA)</li>
|
||||||
<li><a target="_blank" href="http://www.10biosystems.com">10BioSystems</a> (USA)</li>
|
|
||||||
<li><a target="_blank" href="http://www.formatics.nl">Formatics</a> (Netherlands)</li>
|
|
||||||
<li><a target="_blank" href="http://www.corebyte.nl">Corebyte</a> (Netherlands)</li>
|
<li><a target="_blank" href="http://www.corebyte.nl">Corebyte</a> (Netherlands)</li>
|
||||||
<li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</li>
|
<li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</li>
|
||||||
<li><a target="_blank" href="http://www.onemewebservices.com">OneMeWebServices</a> (Canada)</li>
|
<li><a target="_blank" href="http://www.onemewebservices.com">OneMeWebServices</a> (Canada)</li>
|
||||||
<li><a target="_blank" href="http://www.budgetbytes.nl">BudgetBytes</a> (The Netherlands)</li>
|
<li><a target="_blank" href="http://www.budgetbytes.nl">BudgetBytes</a> (The Netherlands)</li>
|
||||||
<li><a target="_blank" href="http://www.androsoft.pl">ANDROSoft</a> (Poland)</li>
|
<li><a target="_blank" href="http://www.androsoft.pl">ANDROSoft</a> (Poland)</li>
|
||||||
|
|
||||||
<li><a target="_blank" href="http://www.sonnetech.com.br">Sonne Tech</a> (Brazil)</li>
|
<li><a target="_blank" href="http://www.sonnetech.com.br">Sonne Tech</a> (Brazil)</li>
|
||||||
<li><a target="_blank" href="http://www.nrg.com.br">NRG Internet Solutions</a> (Brazil)</li>
|
<li><a target="_blank" href="http://www.nrg.com.br">NRG Internet Solutions</a> (Brazil)</li>
|
||||||
<li><a target="_blank" href="http://itjp.net.br/">ITJP</a> (Brazil)</li>
|
<li><a target="_blank" href="http://itjp.net.br/">ITJP</a> (Brazil)</li>
|
||||||
@@ -32,15 +29,15 @@
|
|||||||
<li><a target="_blank" href="http://www.definescope.com/">DefineScope</a> (Portugal)</li>
|
<li><a target="_blank" href="http://www.definescope.com/">DefineScope</a> (Portugal)</li>
|
||||||
<li><a target="_blank" href="http://lpfx.com.br">LPFX</a> (Brazil)</li>
|
<li><a target="_blank" href="http://lpfx.com.br">LPFX</a> (Brazil)</li>
|
||||||
<li><a target="_blank" href="http://emotionull.com">Emotionull</a> (Greece and Cyprus)</li>
|
<li><a target="_blank" href="http://emotionull.com">Emotionull</a> (Greece and Cyprus)</li>
|
||||||
<li><a target="_blank" href="http://www.vsa-services.com/">VSA Services</a> (Singapore)</li>
|
|
||||||
<li><a target="_blank" href="http://www.albendas.com">Albendas</a> (Spain)</li>
|
<li><a target="_blank" href="http://www.albendas.com">Albendas</a> (Spain)</li>
|
||||||
<li><a target="_blank" href="www.corebyte.nl">Corebyte</a> (Netherland)</li>
|
|
||||||
<li><a target="_blank" href="https://loadinfo-net.appspot.com">LoadInfo</a> (Bulgaria)</li>
|
|
||||||
<li><a target="_blank" href="http://www.appliedobjects.com">Applied Objects</a> (New Zealand)</li>
|
<li><a target="_blank" href="http://www.appliedobjects.com">Applied Objects</a> (New Zealand)</li>
|
||||||
<li><a target="_blank" href="http://www.sistemasagiles.com.ar/">Sistemas Ágiles</a> ("Agile Systems") (Argentina)</li>
|
<li><a target="_blank" href="http://www.sistemasagiles.com.ar/">Sistemas Ágiles</a> ("Agile Systems") (Argentina)</li>
|
||||||
<li><a target="_blank" href="http://www.definescope.com/en/services/consulting/">DefineScope</a> (Portugal)</li>
|
<li><a target="_blank" href="http://www.definescope.com/en/services/consulting/">DefineScope</a> (Portugal)</li>
|
||||||
<li><a target="_blank" href="http://10Biosystems.com">10BioSystems</a></li>
|
<li><a target="_blank" href="http://www.tasko.it/">Tasko</a> (Italy)</li>
|
||||||
<li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</li>
|
<li><a target="_blank" href="http://www.geekondemand.it/"> GeekOnDemand</a> (Italy)</li>
|
||||||
|
<li><a target="_blank" href="http://stifix.com"> Stifix</a> (Indonesia)</li>
|
||||||
|
<li><a target="_blank" href="http://www.garciac.es"> Garciac</a> (Spain)</li>
|
||||||
|
<li><a target="_blank" href="http://memoriapersistente.pt "> Memoria persistente</a> (Portugal)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
{{block right_sidebar}}
|
{{block right_sidebar}}
|
||||||
<center>
|
<center>
|
||||||
|
<!--
|
||||||
<h3 class="feature-title">SITES POWERED BY WEB2PY</h3>
|
<h3 class="feature-title">SITES POWERED BY WEB2PY</h3>
|
||||||
<a href="http://web2py.com/poweredby"><img class="frame" id="img1" width="200px"/></a>
|
<a href="http://web2py.com/poweredby"><img class="frame" id="img1" width="200px"/></a>
|
||||||
<a href="http://web2py.com/poweredby"><img class="frame" id="img2" width="200px"/></a>
|
<a href="http://web2py.com/poweredby"><img class="frame" id="img2" width="200px"/></a>
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
<a href="http://web2py.com/poweredby"><img class="frame" id="img6" width="200px"/></a>
|
<a href="http://web2py.com/poweredby"><img class="frame" id="img6" width="200px"/></a>
|
||||||
<a href="http://web2py.com/poweredby"><img class="frame" id="img7" width="200px"/></a>
|
<a href="http://web2py.com/poweredby"><img class="frame" id="img7" width="200px"/></a>
|
||||||
<a href="http://web2py.com/poweredby"><img class="frame" id="img8" width="200px"/></a>
|
<a href="http://web2py.com/poweredby"><img class="frame" id="img8" width="200px"/></a>
|
||||||
</div>
|
-->
|
||||||
</center>
|
</center>
|
||||||
<script>
|
<script>
|
||||||
function showimages() {
|
function showimages() {
|
||||||
|
|||||||
@@ -1,173 +1,67 @@
|
|||||||
<!--[if HTML5]><![endif]-->
|
<html>
|
||||||
<!DOCTYPE html>
|
<head>
|
||||||
<!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->
|
<meta charset="utf-8">
|
||||||
<!--[if lt IE 7]><html class="ie ie6 ie-lte9 ie-lte8 ie-lte7 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<!--[if IE 7]><html class="ie ie7 ie-lte9 ie-lte8 ie-lte7 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<!--[if IE 8]><html class="ie ie8 ie-lte9 ie-lte8 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<!--[if IE 9]><html class="ie9 ie-lte9 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
|
<link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/>
|
||||||
<!--[if (gt IE 9)|!(IE)]><!--> <html class="no-js" lang="{{=T.accepted_language or 'en'}}"> <!--<![endif]-->
|
<link href="{{=URL('static','css/calendar.css')}}" rel="stylesheet" type="text/css"/>
|
||||||
<head>
|
<link href="{{=URL('static','css/web2py.css')}}" rel="stylesheet" type="text/css"/>
|
||||||
<title>{{=response.title or request.application}}</title>
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
|
||||||
<!--[if !HTML5]>
|
<style>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge{{=not request.is_local and ',chrome=1' or ''}}">
|
th {color: black}
|
||||||
<![endif]-->
|
tbody tr:hover {background-color:transparent}
|
||||||
<!-- www.phpied.com/conditional-comments-block-downloads/ -->
|
tbody tr {border-bottom: none}
|
||||||
<!-- Always force latest IE rendering engine
|
p {text-align: left}
|
||||||
(even in intranet) & Chrome Frame
|
pre {background-color: black!important;border-radius:5px; color:white; padding:10px}
|
||||||
Remove this if you use the .htaccess -->
|
a.btn.btn180 {padding:20px; font-size:1.2em; width:200px!important}
|
||||||
|
</style>
|
||||||
<meta charset="utf-8" />
|
{{
|
||||||
|
left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
|
||||||
<!-- http://dev.w3.org/html5/markup/meta.name.html -->
|
right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
|
||||||
<meta name="application-name" content="{{=request.application}}" />
|
middle_column = {0: 'fill', 1: 'threequarters', 2: 'half'}[
|
||||||
|
|
||||||
<!-- Speaking of Google, don't forget to set your site up:
|
|
||||||
http://google.com/webmasters -->
|
|
||||||
<meta name="google-site-verification" content="" />
|
|
||||||
|
|
||||||
<!-- Mobile Viewport Fix
|
|
||||||
j.mp/mobileviewport & davidbcalhoun.com/2010/viewport-metatag
|
|
||||||
device-width: Occupy full width of the screen in its current orientation
|
|
||||||
initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
|
|
||||||
user-scalable = yes allows the user to zoom in -->
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
|
|
||||||
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
|
|
||||||
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
|
|
||||||
|
|
||||||
<!-- All JavaScript at the bottom, except for Modernizr which enables
|
|
||||||
HTML5 elements & feature detects -->
|
|
||||||
<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'))
|
|
||||||
response.files.append(URL('static','css/web2py_bootstrap.css'))
|
|
||||||
response.files.append(URL('static','css/examples.css'))
|
|
||||||
}}
|
|
||||||
|
|
||||||
{{include 'web2py_ajax.html'}}
|
|
||||||
|
|
||||||
{{
|
|
||||||
# using sidebars need to know what sidebar you want to use
|
|
||||||
left_sidebar_enabled = globals().get('left_sidebar_enabled',False)
|
|
||||||
right_sidebar_enabled = globals().get('right_sidebar_enabled',False)
|
|
||||||
middle_columns = {0:'span12',1:'span9',2:'span6'}[
|
|
||||||
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
|
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
|
||||||
}}
|
}}
|
||||||
|
{{include "web2py_ajax.html"}}
|
||||||
<!-- uncomment here to load jquery-ui
|
</head>
|
||||||
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/base/jquery-ui.css" type="text/css" media="all" />
|
<body class="black">
|
||||||
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js" type="text/javascript"></script>
|
<header class="black padded">
|
||||||
uncomment to load jquery-ui //-->
|
<div class="container middle max900">
|
||||||
<noscript><link href="{{=URL('static', 'css/web2py_bootstrap_nojs.css')}}" rel="stylesheet" type="text/css" /></noscript>
|
<div class="fill middle">
|
||||||
{{block head}}{{end}}
|
<label class="ham padded fa fa-bars" for="menu"></label>
|
||||||
</head>
|
<div class="burger accordion">
|
||||||
|
<input type="checkbox" id="menu"/>
|
||||||
<body>
|
{{=MENU(response.menu,_class='menu')}}
|
||||||
<!-- Navbar ================================================== -->
|
</div>
|
||||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
|
||||||
<div class="flash">{{=response.flash or ''}}</div>
|
|
||||||
<div class="navbar-inner">
|
|
||||||
<div class="container">
|
|
||||||
<!-- the next tag is necessary for bootstrap menus, do not remove -->
|
|
||||||
<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>
|
|
||||||
</button>
|
|
||||||
{{=response.logo or ''}}
|
|
||||||
<ul id="navbar" class="nav pull-right">{{='auth' in globals() and auth.navbar(mode="dropdown") or ''}}</ul>
|
|
||||||
<div class="nav-collapse">
|
|
||||||
{{is_mobile=request.user_agent().is_mobile}}
|
|
||||||
{{if response.menu:}}
|
|
||||||
{{=MENU(response.menu, _class='mobile-menu nav' if is_mobile else 'nav',mobile=is_mobile,li_class='dropdown',ul_class='dropdown-menu')}}
|
|
||||||
{{pass}}
|
|
||||||
</div><!--/.nav-collapse -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div><!--/top navbar -->
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<!-- Masthead ================================================== -->
|
|
||||||
<header class="mastheader" id="header">
|
|
||||||
<div class="span4">
|
|
||||||
<div class="page-header">
|
|
||||||
<img src="{{=URL('static','images/web2py_logo.png')}}" class="logo" alt="web2py logo" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
{{if response.flash:}}
|
||||||
<div class="container">
|
<div class="w2p_flash">
|
||||||
|
{{=response.flash}}
|
||||||
<section id="main" class="main row">
|
|
||||||
{{if left_sidebar_enabled:}}
|
|
||||||
<div class="span3 left-sidebar">
|
|
||||||
{{block left_sidebar}}
|
|
||||||
<h3>Left Sidebar</h3>
|
|
||||||
<p></p>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{pass}}
|
|
||||||
|
|
||||||
<div class="{{=middle_columns}}">
|
|
||||||
{{block center}}
|
|
||||||
{{include}}
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{if right_sidebar_enabled:}}
|
|
||||||
<div class="span3">
|
|
||||||
{{block right_sidebar}}
|
|
||||||
<h3>Right Sidebar</h3>
|
|
||||||
<p></p>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{pass}}
|
|
||||||
</section><!--/main-->
|
|
||||||
|
|
||||||
<!-- Footer ================================================== -->
|
|
||||||
<div class="row">
|
|
||||||
<footer class="footer span12" id="footer">
|
|
||||||
<div class="footer-content">
|
|
||||||
{{block footer}} <!-- this is default footer -->
|
|
||||||
<div id="poweredBy" class="pull-right">
|
|
||||||
{{=T('Copyright')}} © {{=request.now.year}} -
|
|
||||||
{{=T('Powered by')}}
|
|
||||||
<a href="http://www.web2py.com/">web2py</a> -
|
|
||||||
{{=T('Hosted by')}}
|
|
||||||
<a href="http://pythonanywhere.com">PythonAnywhere</a>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
|
{{pass}}
|
||||||
</div> <!-- /container -->
|
<main class="white">
|
||||||
|
<div class="container max900">
|
||||||
<!-- The javascript =============================================
|
{{if left_sidebar_enabled:}}
|
||||||
(Placed at the end of the document so the pages load faster) -->
|
<div class="quarter padded">{{block left_sidebar}}{{end}}</div>
|
||||||
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
|
{{pass}}
|
||||||
<script src="{{=URL('static','js/web2py_bootstrap.js')}}"></script>
|
<div class="{{=middle_column}} padded">{{include}}</div>
|
||||||
<!--[if lt IE 7 ]>
|
{{if right_sidebar_enabled:}}
|
||||||
<script src="{{=URL('static','js/dd_belatedpng.js')}}"></script>
|
<div class="quarter padded">{{block right_sidebar}}{{end}}</div>
|
||||||
<script> DD_belatedPNG.fix('img, .png_bg'); //fix any <img> or .png_bg background-images </script>
|
{{pass}}
|
||||||
<![endif]-->
|
</div>
|
||||||
|
</main>
|
||||||
{{if response.google_analytics_id:}}
|
<footer class="black">
|
||||||
<script src="{{=URL('static','js/analytics.min.js')}}"></script>
|
<div class="container padded max900">
|
||||||
<script type="text/javascript">
|
<div class="fill">
|
||||||
analytics.initialize({
|
Copyright @ 2016 - Powered by Web2py
|
||||||
'Google Analytics':{trackingId:'{{=response.google_analytics_id}}'}
|
</div>
|
||||||
});</script>
|
</div>
|
||||||
{{pass}}
|
</footer>
|
||||||
<script src="{{=URL('static','js/share.js',vars=dict(static=URL('static','images')))}}"></script>
|
</body>
|
||||||
<a style="position:fixed;bottom:0;left:0;z-index:1000" href="https://groups.google.com/forum/?fromgroups#!forum/web2py" target="_blank">
|
<script>
|
||||||
<img src="{{=URL('static','images/questions.png')}}" />
|
// prevent android horizontal scrolling
|
||||||
</a>
|
window.addEventListener("scroll", function(){window.scroll(0, window.pageYOffset);}, false);
|
||||||
|
</script>
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ from gluon.contrib.appconfig import AppConfig
|
|||||||
## once in production, remove reload=True to gain full speed
|
## once in production, remove reload=True to gain full speed
|
||||||
myconf = AppConfig(reload=True)
|
myconf = AppConfig(reload=True)
|
||||||
|
|
||||||
|
|
||||||
if not request.env.web2py_runtime_gae:
|
if not request.env.web2py_runtime_gae:
|
||||||
## if NOT running on Google App Engine use SQLite or other DB
|
## if NOT running on Google App Engine use SQLite or other DB
|
||||||
db = DAL(myconf.take('db.uri'), pool_size=myconf.take('db.pool_size', cast=int), check_reserved=['all'])
|
db = DAL(myconf.get('db.uri'),
|
||||||
|
pool_size = myconf.get('db.pool_size'),
|
||||||
|
migrate_enabled = myconf.get('db.migrate'),
|
||||||
|
check_reserved = ['all'])
|
||||||
else:
|
else:
|
||||||
## connect to Google BigTable (optional 'google:datastore://namespace')
|
## connect to Google BigTable (optional 'google:datastore://namespace')
|
||||||
db = DAL('google:datastore+ndb')
|
db = DAL('google:datastore+ndb')
|
||||||
@@ -32,8 +34,8 @@ else:
|
|||||||
## none otherwise. a pattern can be 'controller/function.extension'
|
## none otherwise. a pattern can be 'controller/function.extension'
|
||||||
response.generic_patterns = ['*'] if request.is_local else []
|
response.generic_patterns = ['*'] if request.is_local else []
|
||||||
## choose a style for forms
|
## choose a style for forms
|
||||||
response.formstyle = myconf.take('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
|
response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
|
||||||
response.form_label_separator = myconf.take('forms.separator')
|
response.form_label_separator = myconf.get('forms.separator') or ''
|
||||||
|
|
||||||
|
|
||||||
## (optional) optimize handling of static files
|
## (optional) optimize handling of static files
|
||||||
@@ -53,7 +55,7 @@ response.form_label_separator = myconf.take('forms.separator')
|
|||||||
|
|
||||||
from gluon.tools import Auth, Service, PluginManager
|
from gluon.tools import Auth, Service, PluginManager
|
||||||
|
|
||||||
auth = Auth(db)
|
auth = Auth(db, host=myconf.get('host.name'))
|
||||||
service = Service()
|
service = Service()
|
||||||
plugins = PluginManager()
|
plugins = PluginManager()
|
||||||
|
|
||||||
@@ -62,9 +64,11 @@ auth.define_tables(username=False, signature=False)
|
|||||||
|
|
||||||
## configure email
|
## configure email
|
||||||
mail = auth.settings.mailer
|
mail = auth.settings.mailer
|
||||||
mail.settings.server = 'logging' if request.is_local else myconf.take('smtp.server')
|
mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server')
|
||||||
mail.settings.sender = myconf.take('smtp.sender')
|
mail.settings.sender = myconf.get('smtp.sender')
|
||||||
mail.settings.login = myconf.take('smtp.login')
|
mail.settings.login = myconf.get('smtp.login')
|
||||||
|
mail.settings.tls = myconf.get('smtp.tls') or False
|
||||||
|
mail.settings.ssl = myconf.get('smtp.ssl') or False
|
||||||
|
|
||||||
## configure auth policy
|
## configure auth policy
|
||||||
auth.settings.registration_requires_verification = False
|
auth.settings.registration_requires_verification = False
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ response.title = request.application.replace('_',' ').title()
|
|||||||
response.subtitle = ''
|
response.subtitle = ''
|
||||||
|
|
||||||
## read more at http://dev.w3.org/html5/markup/meta.name.html
|
## read more at http://dev.w3.org/html5/markup/meta.name.html
|
||||||
response.meta.author = 'Your Name <you@example.com>'
|
response.meta.author = myconf.get('app.author')
|
||||||
response.meta.description = 'a cool new app'
|
response.meta.description = myconf.get('app.description')
|
||||||
response.meta.keywords = 'web2py, python, framework'
|
response.meta.keywords = myconf.get('app.keywords')
|
||||||
response.meta.generator = 'Web2py Web Framework'
|
response.meta.generator = myconf.get('app.generator')
|
||||||
|
|
||||||
## your http://google.com/analytics id
|
## your http://google.com/analytics id
|
||||||
response.google_analytics_id = None
|
response.google_analytics_id = None
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
; App configuration
|
; App configuration
|
||||||
|
[app]
|
||||||
|
name = Welcome
|
||||||
|
author = Your Name <you@example.com>
|
||||||
|
description = a cool new app
|
||||||
|
keywords = web2py, python, framework
|
||||||
|
generator = Web2py Web Framework
|
||||||
|
|
||||||
|
; Host configuration
|
||||||
|
[host]
|
||||||
|
name = localhost
|
||||||
|
|
||||||
; db configuration
|
; db configuration
|
||||||
[db]
|
[db]
|
||||||
uri = sqlite://storage.sqlite
|
uri = sqlite://storage.sqlite
|
||||||
migrate = 1
|
migrate = true
|
||||||
pool_size = 1
|
pool_size = 10 ; ignored for sqlite
|
||||||
|
|
||||||
; smtp address and credentials
|
; smtp address and credentials
|
||||||
[smtp]
|
[smtp]
|
||||||
server = smtp.gmail.com:587
|
server = smtp.gmail.com:587
|
||||||
sender = you@gmail.com
|
sender = you@gmail.com
|
||||||
login = username:password
|
login = username:password
|
||||||
|
tls = true
|
||||||
|
ssl = true
|
||||||
|
|
||||||
; form styling
|
; form styling
|
||||||
[forms]
|
[forms]
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -108,7 +108,7 @@ select.autocomplete {
|
|||||||
background: url(../images/background.jpg) no-repeat center center;
|
background: url(../images/background.jpg) no-repeat center center;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
padding-top: 50px;
|
padding-top: 60px;
|
||||||
margin-bottom: 60px;
|
margin-bottom: 60px;
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
@@ -233,7 +233,7 @@ div.error_wrapper {
|
|||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 3px 5px;
|
padding: 6px 12px;
|
||||||
}
|
}
|
||||||
.web2py_counter {
|
.web2py_counter {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
@@ -270,6 +270,7 @@ li.w2p_grid_breadcrumb_elem {
|
|||||||
.web2py_console select,
|
.web2py_console select,
|
||||||
.web2py_console a {
|
.web2py_console a {
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
|
padding: 6px 12px;
|
||||||
}
|
}
|
||||||
#wiki_page_body {
|
#wiki_page_body {
|
||||||
width: 600px;
|
width: 600px;
|
||||||
@@ -285,7 +286,7 @@ li.w2p_grid_breadcrumb_elem {
|
|||||||
.web2py_console .form-control {
|
.web2py_console .form-control {
|
||||||
width: 20%;
|
width: 20%;
|
||||||
display: inline;
|
display: inline;
|
||||||
height: 100%;
|
height: 32px;
|
||||||
}
|
}
|
||||||
.web2py_console #w2p_keywords {
|
.web2py_console #w2p_keywords {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -120,7 +120,7 @@ args=()
|
|||||||
class=handlers.RotatingFileHandler
|
class=handlers.RotatingFileHandler
|
||||||
level=DEBUG
|
level=DEBUG
|
||||||
formatter=simpleFormatter
|
formatter=simpleFormatter
|
||||||
args=("logs/web2py.log", "a", 1000000, 5)
|
args=("web2py.log", "a", 1000000, 5)
|
||||||
|
|
||||||
[handler_osxSysLogHandler]
|
[handler_osxSysLogHandler]
|
||||||
class=handlers.SysLogHandler
|
class=handlers.SysLogHandler
|
||||||
|
|||||||
Vendored
+14
-7
@@ -5,6 +5,9 @@ import os
|
|||||||
import datetime
|
import datetime
|
||||||
import getpass
|
import getpass
|
||||||
|
|
||||||
|
if os.path.exists('hosts'):
|
||||||
|
env.hosts = [h.strip() for h in open('hosts').readlines() if h.strip()]
|
||||||
|
|
||||||
env.hosts = env.hosts or raw_input('hostname (example.com):').split(',')
|
env.hosts = env.hosts or raw_input('hostname (example.com):').split(',')
|
||||||
env.user = env.user or raw_input('username :')
|
env.user = env.user or raw_input('username :')
|
||||||
|
|
||||||
@@ -85,7 +88,7 @@ def mkdir_or_backup(appname):
|
|||||||
def git_deploy(appname, repo):
|
def git_deploy(appname, repo):
|
||||||
"""fab -H username@host git_deploy:appname,username/remoname"""
|
"""fab -H username@host git_deploy:appname,username/remoname"""
|
||||||
appfolder = applications+'/'+appname
|
appfolder = applications+'/'+appname
|
||||||
backup = mkdir_or_backup(appfolder)
|
backup = mkdir_or_backup(appname)
|
||||||
|
|
||||||
if exists(appfolder):
|
if exists(appfolder):
|
||||||
with cd(appfolder):
|
with cd(appfolder):
|
||||||
@@ -115,18 +118,22 @@ def deploy(appname=None, all=False):
|
|||||||
if os.path.exists('_update.zip'):
|
if os.path.exists('_update.zip'):
|
||||||
os.unlink('_update.zip')
|
os.unlink('_update.zip')
|
||||||
|
|
||||||
backup = mkdir_or_backup(appfolder)
|
backup = mkdir_or_backup(appname)
|
||||||
|
|
||||||
if all=='all' or not backup:
|
if all=='all' or not backup:
|
||||||
local('zip -r _update.zip * -x *~ -x .* -x \#* -x *.bak -x *.bak2')
|
local('zip -r _update.zip * -x *~ -x .* -x \#* -x *.bak -x *.bak2')
|
||||||
else:
|
else:
|
||||||
local('zip -r _update.zip */*.py views/*.html views/*/*.html static/*')
|
local('zip -r _update.zip */*.py views/*.html views/*/*.html static/*')
|
||||||
put('_update.zip','/tmp/_update.zip')
|
|
||||||
|
|
||||||
with cd(appfolder):
|
put('_update.zip','/tmp/_update.zip')
|
||||||
sudo('unzip -o /tmp/_update.zip')
|
try:
|
||||||
sudo('chown -R www-data:www-data *')
|
with cd(appfolder):
|
||||||
sudo('echo "%s" > DATE_DEPLOYMENT' % now)
|
sudo('unzip -o /tmp/_update.zip')
|
||||||
|
sudo('chown -R www-data:www-data *')
|
||||||
|
sudo('echo "%s" > DATE_DEPLOYMENT' % now)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
sudo('rm /tmp/_update.zip')
|
||||||
|
|
||||||
if backup:
|
if backup:
|
||||||
print 'TO RESTORE: fab restore:%s' % backup
|
print 'TO RESTORE: fab restore:%s' % backup
|
||||||
|
|||||||
+2
-2
@@ -676,8 +676,8 @@ def run_view_in(environment):
|
|||||||
else:
|
else:
|
||||||
filename = pjoin(folder, 'views', view)
|
filename = pjoin(folder, 'views', view)
|
||||||
if os.path.exists(path): # compiled views
|
if os.path.exists(path): # compiled views
|
||||||
x = view.replace('/', '_')
|
x = view.replace('/', '.')
|
||||||
files = ['views_%s.pyc' % x]
|
files = ['views.%s.pyc' % x]
|
||||||
is_compiled = os.path.exists(pjoin(path, files[0]))
|
is_compiled = os.path.exists(pjoin(path, files[0]))
|
||||||
# Don't use a generic view if the non-compiled view exists.
|
# Don't use a generic view if the non-compiled view exists.
|
||||||
if is_compiled or (not is_compiled and not os.path.exists(filename)):
|
if is_compiled or (not is_compiled and not os.path.exists(filename)):
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ from gluon.serializers import json_parser
|
|||||||
|
|
||||||
locker = thread.allocate_lock()
|
locker = thread.allocate_lock()
|
||||||
|
|
||||||
|
|
||||||
def AppConfig(*args, **vars):
|
def AppConfig(*args, **vars):
|
||||||
|
|
||||||
locker.acquire()
|
locker.acquire()
|
||||||
@@ -59,6 +58,27 @@ class AppConfigDict(dict):
|
|||||||
dict.__init__(self, *args, **kwargs)
|
dict.__init__(self, *args, **kwargs)
|
||||||
self.int_cache = {}
|
self.int_cache = {}
|
||||||
|
|
||||||
|
def get(self, path, default=None):
|
||||||
|
try:
|
||||||
|
value = self.take(path).strip()
|
||||||
|
if value.lower() in ('none','null',''):
|
||||||
|
return None
|
||||||
|
elif value.lower() == 'true':
|
||||||
|
return True
|
||||||
|
elif value.lower() == 'false':
|
||||||
|
return False
|
||||||
|
elif value.isdigit() or (value[0]=='-' and value[1:].isdigit()):
|
||||||
|
return int(value)
|
||||||
|
elif ', ' in value:
|
||||||
|
return value.split(', ')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return float(value)
|
||||||
|
except:
|
||||||
|
return value
|
||||||
|
except:
|
||||||
|
return default
|
||||||
|
|
||||||
def take(self, path, cast=None):
|
def take(self, path, cast=None):
|
||||||
parts = path.split('.')
|
parts = path.split('.')
|
||||||
if path in self.int_cache:
|
if path in self.int_cache:
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ from gluon import current
|
|||||||
|
|
||||||
class RESIZE(object):
|
class RESIZE(object):
|
||||||
|
|
||||||
def __init__(self, nx=160, ny=80, quality=100,
|
def __init__(self, nx=160, ny=80, quality=100, padding = False
|
||||||
error_message=' image resize'):
|
error_message=' image resize'):
|
||||||
(self.nx, self.ny, self.quality, self.error_message) = (
|
(self.nx, self.ny, self.quality, self.error_message, self.padding) = (
|
||||||
nx, ny, quality, error_message)
|
nx, ny, quality, error_message, padding)
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
if isinstance(value, str) and len(value) == 0:
|
if isinstance(value, str) and len(value) == 0:
|
||||||
@@ -41,7 +41,14 @@ class RESIZE(object):
|
|||||||
img = Image.open(value.file)
|
img = Image.open(value.file)
|
||||||
img.thumbnail((self.nx, self.ny), Image.ANTIALIAS)
|
img.thumbnail((self.nx, self.ny), Image.ANTIALIAS)
|
||||||
s = cStringIO.StringIO()
|
s = cStringIO.StringIO()
|
||||||
img.save(s, 'JPEG', quality=self.quality)
|
if self.padding:
|
||||||
|
background = Image.new('RGBA', (self.nx, self.ny), (255, 255, 255, 0))
|
||||||
|
background.paste(
|
||||||
|
img,
|
||||||
|
((self.nx - img.size[0]) / 2, (self.ny - img.size[1]) / 2))
|
||||||
|
background.save(s, 'JPEG', quality=self.quality)
|
||||||
|
else:
|
||||||
|
img.save(s, 'JPEG', queality=self.quality)
|
||||||
s.seek(0)
|
s.seek(0)
|
||||||
value.file = s
|
value.file = s
|
||||||
except:
|
except:
|
||||||
|
|||||||
@@ -421,7 +421,8 @@ def ldap_auth(server='ldap',
|
|||||||
store_user_mail = None
|
store_user_mail = None
|
||||||
update_or_insert_values = {'first_name': store_user_firstname,
|
update_or_insert_values = {'first_name': store_user_firstname,
|
||||||
'last_name': store_user_lastname,
|
'last_name': store_user_lastname,
|
||||||
'email': store_user_mail}
|
'email': store_user_mail,
|
||||||
|
'username': username}
|
||||||
if '@' not in username:
|
if '@' not in username:
|
||||||
# user as username
|
# user as username
|
||||||
# ################
|
# ################
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class OneallAccount(object):
|
|||||||
reg_id=profile.get('identity_token','')
|
reg_id=profile.get('identity_token','')
|
||||||
username=profile.get('preferredUsername',email)
|
username=profile.get('preferredUsername',email)
|
||||||
first_name=name.get('givenName', dname.split(' ')[0])
|
first_name=name.get('givenName', dname.split(' ')[0])
|
||||||
last_name=profile.get('familyName',dname.split(' ')[1])
|
last_name=profile.get('familyName', dname.split(' ')[1] if(len(dname.split(' ')) > 1) else None)
|
||||||
return dict(registration_id=reg_id,username=username,email=email,
|
return dict(registration_id=reg_id,username=username,email=email,
|
||||||
first_name=first_name,last_name=last_name)
|
first_name=first_name,last_name=last_name)
|
||||||
self.mappings.default = defaultmapping
|
self.mappings.default = defaultmapping
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
Developed by niphlod@gmail.com
|
Developed by niphlod@gmail.com
|
||||||
Released under web2py license because includes gluon/cache.py source code
|
Released under web2py license because includes gluon/cache.py source code
|
||||||
"""
|
"""
|
||||||
import redis
|
|
||||||
from redis.exceptions import ConnectionError
|
|
||||||
from gluon import current
|
|
||||||
from gluon.cache import CacheAbstract
|
|
||||||
try:
|
try:
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
except:
|
except:
|
||||||
import pickle
|
import pickle
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
import thread
|
import thread
|
||||||
import random
|
import random
|
||||||
|
from gluon import current
|
||||||
|
from gluon.cache import CacheAbstract
|
||||||
|
from gluon.contrib.redis_utils import acquire_lock, release_lock
|
||||||
|
from gluon.contrib.redis_utils import register_release_lock, RConnectionError
|
||||||
|
|
||||||
logger = logging.getLogger("web2py.cache.redis")
|
logger = logging.getLogger("web2py.cache.redis")
|
||||||
|
|
||||||
@@ -24,20 +24,36 @@ locker = thread.allocate_lock()
|
|||||||
|
|
||||||
def RedisCache(*args, **vars):
|
def RedisCache(*args, **vars):
|
||||||
"""
|
"""
|
||||||
Usage example: put in models
|
Usage example: put in models::
|
||||||
|
|
||||||
from gluon.contrib.redis_cache import RedisCache
|
First of all install Redis
|
||||||
cache.redis = RedisCache('localhost:6379',db=None, debug=True, with_lock=True, password=None)
|
Ubuntu :
|
||||||
|
sudo apt-get install redis-server
|
||||||
|
sudo pip install redis
|
||||||
|
|
||||||
:param db: redis db to use (0..16)
|
Then
|
||||||
:param debug: if True adds to stats() the total_hits and misses
|
|
||||||
:param with_lock: sets the default locking mode for creating new keys.
|
from gluon.contrib.redis_utils import RConn
|
||||||
|
rconn = RConn()
|
||||||
|
from gluon.contrib.redis_cache import RedisCache
|
||||||
|
cache.redis = RedisCache(redis_conn=rconn, debug=True, with_lock=True)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
redis_conn: a redis-like connection object
|
||||||
|
debug: if True adds to stats() the total_hits and misses
|
||||||
|
with_lock: sets the default locking mode for creating new keys.
|
||||||
By default is False (usualy when you choose Redis you do it
|
By default is False (usualy when you choose Redis you do it
|
||||||
for performances reason)
|
for performances reason)
|
||||||
When True, only one thread/process can set a value concurrently
|
When True, only one thread/process can set a value concurrently
|
||||||
|
fail_gracefully: if redis is unavailable, returns the value computing it
|
||||||
|
instead of raising an exception
|
||||||
|
|
||||||
|
It can be used pretty much the same as cache.ram()
|
||||||
|
When you use cache.redis directly you can use :
|
||||||
|
|
||||||
|
redis_key_and_var_name = cache.redis('redis_key_and_var_name', lambda or function,
|
||||||
|
time_expire=time.time(), with_lock=True)
|
||||||
|
|
||||||
When you use cache.redis directly you can use
|
|
||||||
value = cache.redis('mykey', lambda: time.time(), with_lock=True)
|
|
||||||
to enforce locking. The with_lock parameter overrides the one set in the
|
to enforce locking. The with_lock parameter overrides the one set in the
|
||||||
cache.redis instance creation
|
cache.redis instance creation
|
||||||
|
|
||||||
@@ -81,22 +97,19 @@ class RedisClient(object):
|
|||||||
MAX_RETRIES = 5
|
MAX_RETRIES = 5
|
||||||
RETRIES = 0
|
RETRIES = 0
|
||||||
|
|
||||||
def __init__(self, server='localhost:6379', db=None, debug=False, with_lock=False, password=None):
|
def __init__(self, redis_conn=None, debug=False,
|
||||||
self.server = server
|
with_lock=False, fail_gracefully=False):
|
||||||
self.password = password
|
|
||||||
self.db = db or 0
|
|
||||||
host, port = (self.server.split(':') + ['6379'])[:2]
|
|
||||||
port = int(port)
|
|
||||||
self.request = current.request
|
self.request = current.request
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.with_lock = with_lock
|
self.with_lock = with_lock
|
||||||
self.prefix = "w2p:%s:" % (self.request.application)
|
self.fail_gracefully = fail_gracefully
|
||||||
|
self.prefix = "w2p:cache:%s:" % self.request.application
|
||||||
if self.request:
|
if self.request:
|
||||||
app = self.request.application
|
app = self.request.application
|
||||||
else:
|
else:
|
||||||
app = ''
|
app = ''
|
||||||
|
|
||||||
if not app in self.meta_storage:
|
if app not in self.meta_storage:
|
||||||
self.storage = self.meta_storage[app] = {
|
self.storage = self.meta_storage[app] = {
|
||||||
CacheAbstract.cache_stats_name: {
|
CacheAbstract.cache_stats_name: {
|
||||||
'hit_total': 0,
|
'hit_total': 0,
|
||||||
@@ -105,9 +118,10 @@ class RedisClient(object):
|
|||||||
else:
|
else:
|
||||||
self.storage = self.meta_storage[app]
|
self.storage = self.meta_storage[app]
|
||||||
|
|
||||||
self.cache_set_key = 'w2p:%s:___cache_set' % (self.request.application)
|
self.cache_set_key = 'w2p:%s:___cache_set' % self.request.application
|
||||||
|
|
||||||
self.r_server = redis.Redis(host=host, port=port, db=self.db, password=self.password)
|
self.r_server = redis_conn
|
||||||
|
self._release_script = register_release_lock(self.r_server)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
pass
|
pass
|
||||||
@@ -121,90 +135,86 @@ class RedisClient(object):
|
|||||||
value = None
|
value = None
|
||||||
ttl = 0
|
ttl = 0
|
||||||
try:
|
try:
|
||||||
#is there a value
|
# is there a value
|
||||||
obj = self.r_server.get(newKey)
|
obj = self.r_server.get(newKey)
|
||||||
#what's its ttl
|
# what's its ttl
|
||||||
if obj:
|
if obj:
|
||||||
ttl = self.r_server.ttl(newKey)
|
ttl = self.r_server.ttl(newKey)
|
||||||
if ttl > time_expire:
|
if ttl > time_expire:
|
||||||
obj = None
|
obj = None
|
||||||
if obj:
|
if obj:
|
||||||
#was cached
|
# was cached
|
||||||
if self.debug:
|
if self.debug:
|
||||||
self.r_server.incr('web2py_cache_statistics:hit_total')
|
self.r_server.incr('web2py_cache_statistics:hit_total')
|
||||||
value = pickle.loads(obj)
|
value = pickle.loads(obj)
|
||||||
elif f is None:
|
elif f is None:
|
||||||
#delete and never look back
|
# delete and never look back
|
||||||
self.r_server.delete(newKey)
|
self.r_server.delete(newKey)
|
||||||
else:
|
else:
|
||||||
#naive distributed locking
|
# naive distributed locking
|
||||||
if with_lock:
|
if with_lock:
|
||||||
lock_key = '%s:__lock' % newKey
|
lock_key = '%s:__lock' % newKey
|
||||||
try:
|
randomvalue = time.time()
|
||||||
while True:
|
al = acquire_lock(self.r_server, lock_key, randomvalue)
|
||||||
lock = self.r_server.setnx(lock_key, 1)
|
# someone may have computed it
|
||||||
if lock:
|
obj = self.r_server.get(newKey)
|
||||||
value = self.cache_it(newKey, f, time_expire)
|
if obj is None:
|
||||||
break
|
value = self.cache_it(newKey, f, time_expire)
|
||||||
else:
|
else:
|
||||||
time.sleep(0.2)
|
value = pickle.loads(obj)
|
||||||
#did someone else create it in the meanwhile ?
|
release_lock(self, lock_key, al)
|
||||||
obj = self.r_server.get(newKey)
|
|
||||||
if obj:
|
|
||||||
value = pickle.loads(obj)
|
|
||||||
break
|
|
||||||
finally:
|
|
||||||
self.r_server.delete(lock_key)
|
|
||||||
else:
|
else:
|
||||||
#without distributed locking
|
# without distributed locking
|
||||||
value = self.cache_it(newKey, f, time_expire)
|
value = self.cache_it(newKey, f, time_expire)
|
||||||
return value
|
return value
|
||||||
except ConnectionError:
|
except RConnectionError:
|
||||||
return self.retry_call(key, f, time_expire, with_lock)
|
return self.retry_call(key, f, time_expire, with_lock)
|
||||||
|
|
||||||
def cache_it(self, key, f, time_expire):
|
def cache_it(self, key, f, time_expire):
|
||||||
if self.debug:
|
if self.debug:
|
||||||
self.r_server.incr('web2py_cache_statistics:misses')
|
self.r_server.incr('web2py_cache_statistics:misses')
|
||||||
cache_set_key = self.cache_set_key
|
cache_set_key = self.cache_set_key
|
||||||
expireat = int(time.time() + time_expire) + 120
|
expire_at = int(time.time() + time_expire) + 120
|
||||||
bucket_key = "%s:%s" % (cache_set_key, expireat / 60)
|
bucket_key = "%s:%s" % (cache_set_key, expire_at / 60)
|
||||||
value = f()
|
value = f()
|
||||||
value_ = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
|
value_ = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
|
||||||
if time_expire == 0:
|
if time_expire == 0:
|
||||||
time_expire = 1
|
time_expire = 1
|
||||||
self.r_server.setex(key, value_, time_expire)
|
self.r_server.setex(key, time_expire, value_)
|
||||||
#print '%s will expire on %s: it goes in bucket %s' % (key, time.ctime(expireat))
|
# print '%s will expire on %s: it goes in bucket %s' % (key, time.ctime(expire_at))
|
||||||
#print 'that will expire on %s' % (bucket_key, time.ctime(((expireat/60) + 1)*60))
|
# print 'that will expire on %s' % (bucket_key, time.ctime(((expire_at / 60) + 1) * 60))
|
||||||
p = self.r_server.pipeline()
|
p = self.r_server.pipeline()
|
||||||
#add bucket to the fixed set
|
# add bucket to the fixed set
|
||||||
p.sadd(cache_set_key, bucket_key)
|
p.sadd(cache_set_key, bucket_key)
|
||||||
#sets the key
|
# sets the key
|
||||||
p.setex(key, value_, time_expire)
|
p.setex(key, time_expire, value_)
|
||||||
#add the key to the bucket
|
# add the key to the bucket
|
||||||
p.sadd(bucket_key, key)
|
p.sadd(bucket_key, key)
|
||||||
#expire the bucket properly
|
# expire the bucket properly
|
||||||
p.expireat(bucket_key, ((expireat/60) + 1)*60)
|
p.expireat(bucket_key, ((expire_at / 60) + 1) * 60)
|
||||||
p.execute()
|
p.execute()
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def retry_call(self, key, f, time_expire, with_locking):
|
def retry_call(self, key, f, time_expire, with_lock):
|
||||||
self.RETRIES += 1
|
self.RETRIES += 1
|
||||||
if self.RETRIES <= self.MAX_RETRIES:
|
if self.RETRIES <= self.MAX_RETRIES:
|
||||||
logger.error("sleeping %s seconds before reconnecting" %
|
logger.error("sleeping %s seconds before reconnecting" % (2 * self.RETRIES))
|
||||||
(2 * self.RETRIES))
|
|
||||||
time.sleep(2 * self.RETRIES)
|
time.sleep(2 * self.RETRIES)
|
||||||
self.__init__(self.server, self.db, self.debug, self.with_lock)
|
if self.fail_gracefully:
|
||||||
return self.__call__(key, f, time_expire, with_locking)
|
self.RETRIES = 0
|
||||||
|
return f()
|
||||||
|
return self.__call__(key, f, time_expire, with_lock)
|
||||||
else:
|
else:
|
||||||
self.RETRIES = 0
|
self.RETRIES = 0
|
||||||
raise ConnectionError('Redis instance is unavailable at %s' % (
|
if self.fail_gracefully:
|
||||||
self.server))
|
return f
|
||||||
|
raise RConnectionError('Redis instance is unavailable')
|
||||||
|
|
||||||
def increment(self, key, value=1):
|
def increment(self, key, value=1):
|
||||||
try:
|
try:
|
||||||
newKey = self.__keyFormat__(key)
|
newKey = self.__keyFormat__(key)
|
||||||
return self.r_server.incr(newKey, value)
|
return self.r_server.incr(newKey, value)
|
||||||
except ConnectionError:
|
except RConnectionError:
|
||||||
return self.retry_increment(key, value)
|
return self.retry_increment(key, value)
|
||||||
|
|
||||||
def retry_increment(self, key, value):
|
def retry_increment(self, key, value):
|
||||||
@@ -212,12 +222,10 @@ class RedisClient(object):
|
|||||||
if self.RETRIES <= self.MAX_RETRIES:
|
if self.RETRIES <= self.MAX_RETRIES:
|
||||||
logger.error("sleeping some seconds before reconnecting")
|
logger.error("sleeping some seconds before reconnecting")
|
||||||
time.sleep(2 * self.RETRIES)
|
time.sleep(2 * self.RETRIES)
|
||||||
self.__init__(self.server, self.db, self.debug, self.with_lock)
|
|
||||||
return self.increment(key, value)
|
return self.increment(key, value)
|
||||||
else:
|
else:
|
||||||
self.RETRIES = 0
|
self.RETRIES = 0
|
||||||
raise ConnectionError('Redis instance is unavailable at %s' % (
|
raise RConnectionError('Redis instance is unavailable')
|
||||||
self.server))
|
|
||||||
|
|
||||||
def clear(self, regex):
|
def clear(self, regex):
|
||||||
"""
|
"""
|
||||||
@@ -225,9 +233,9 @@ class RedisClient(object):
|
|||||||
clear cache entries
|
clear cache entries
|
||||||
"""
|
"""
|
||||||
r = re.compile(regex)
|
r = re.compile(regex)
|
||||||
#get all buckets
|
# get all buckets
|
||||||
buckets = self.r_server.smembers(self.cache_set_key)
|
buckets = self.r_server.smembers(self.cache_set_key)
|
||||||
#get all keys in buckets
|
# get all keys in buckets
|
||||||
if buckets:
|
if buckets:
|
||||||
keys = self.r_server.sunion(buckets)
|
keys = self.r_server.sunion(buckets)
|
||||||
else:
|
else:
|
||||||
@@ -237,8 +245,8 @@ class RedisClient(object):
|
|||||||
for a in keys:
|
for a in keys:
|
||||||
if r.match(str(a).replace(prefix, '', 1)):
|
if r.match(str(a).replace(prefix, '', 1)):
|
||||||
pipe.delete(a)
|
pipe.delete(a)
|
||||||
if random.randrange(0,100) < 10:
|
if random.randrange(0, 100) < 10:
|
||||||
#do this just once in a while (10% chance)
|
# do this just once in a while (10% chance)
|
||||||
self.clear_buckets(buckets)
|
self.clear_buckets(buckets)
|
||||||
pipe.execute()
|
pipe.execute()
|
||||||
|
|
||||||
@@ -254,19 +262,19 @@ class RedisClient(object):
|
|||||||
return self.r_server.delete(newKey)
|
return self.r_server.delete(newKey)
|
||||||
|
|
||||||
def stats(self):
|
def stats(self):
|
||||||
statscollector = self.r_server.info()
|
stats_collector = self.r_server.info()
|
||||||
if self.debug:
|
if self.debug:
|
||||||
statscollector['w2p_stats'] = dict(
|
stats_collector['w2p_stats'] = dict(
|
||||||
hit_total=self.r_server.get(
|
hit_total=self.r_server.get(
|
||||||
'web2py_cache_statistics:hit_total'),
|
'web2py_cache_statistics:hit_total'),
|
||||||
misses=self.r_server.get('web2py_cache_statistics:misses')
|
misses=self.r_server.get('web2py_cache_statistics:misses')
|
||||||
)
|
)
|
||||||
statscollector['w2p_keys'] = dict()
|
stats_collector['w2p_keys'] = dict()
|
||||||
|
|
||||||
for a in self.r_server.keys("w2p:%s:*" % (
|
for a in self.r_server.keys("w2p:%s:*" % (
|
||||||
self.request.application)):
|
self.request.application)):
|
||||||
statscollector['w2p_keys']["%s_expire_in_sec" % (a)] = self.r_server.ttl(a)
|
stats_collector['w2p_keys']["%s_expire_in_sec" % a] = self.r_server.ttl(a)
|
||||||
return statscollector
|
return stats_collector
|
||||||
|
|
||||||
def __keyFormat__(self, key):
|
def __keyFormat__(self, key):
|
||||||
return '%s%s' % (self.prefix, key.replace(' ', '_'))
|
return '%s%s' % (self.prefix, key.replace(' ', '_'))
|
||||||
|
|||||||
@@ -0,0 +1,785 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
| This file is part of the web2py Web Framework
|
||||||
|
| Created by niphlod@gmail.com
|
||||||
|
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
||||||
|
|
||||||
|
Scheduler with redis backend
|
||||||
|
---------------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
USAGE = """
|
||||||
|
## Example
|
||||||
|
|
||||||
|
For any existing app
|
||||||
|
|
||||||
|
Create File: app/models/scheduler.py ======
|
||||||
|
from gluon.contrib.redis_utils import RConn
|
||||||
|
from gluon.contrib.redis_scheduler import RScheduler
|
||||||
|
|
||||||
|
def demo1(*args,**vars):
|
||||||
|
print 'you passed args=%s and vars=%s' % (args, vars)
|
||||||
|
return 'done!'
|
||||||
|
|
||||||
|
def demo2():
|
||||||
|
1/0
|
||||||
|
|
||||||
|
rconn = RConn()
|
||||||
|
mysched = RScheduler(db, dict(demo1=demo1,demo2=demo2), ...., redis_conn=rconn)
|
||||||
|
|
||||||
|
## run worker nodes with:
|
||||||
|
|
||||||
|
cd web2py
|
||||||
|
python web2py.py -K app
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
path = os.getcwd()
|
||||||
|
|
||||||
|
if 'WEB2PY_PATH' not in os.environ:
|
||||||
|
os.environ['WEB2PY_PATH'] = path
|
||||||
|
|
||||||
|
try:
|
||||||
|
from gluon.contrib.simplejson import loads, dumps
|
||||||
|
except:
|
||||||
|
from simplejson import loads, dumps
|
||||||
|
|
||||||
|
IDENTIFIER = "%s#%s" % (socket.gethostname(), os.getpid())
|
||||||
|
|
||||||
|
logger = logging.getLogger('web2py.rscheduler.%s' % IDENTIFIER)
|
||||||
|
|
||||||
|
from gluon.utils import web2py_uuid
|
||||||
|
from gluon.storage import Storage
|
||||||
|
from gluon.scheduler import *
|
||||||
|
from gluon.scheduler import _decode_dict
|
||||||
|
from gluon.contrib.redis_utils import RWatchError
|
||||||
|
|
||||||
|
|
||||||
|
POLLING = 'POLLING'
|
||||||
|
|
||||||
|
|
||||||
|
class RScheduler(Scheduler):
|
||||||
|
|
||||||
|
def __init__(self, db, tasks=None, migrate=True,
|
||||||
|
worker_name=None, group_names=None, heartbeat=HEARTBEAT,
|
||||||
|
max_empty_runs=0, discard_results=False, utc_time=False,
|
||||||
|
redis_conn=None, mode=1):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Highly-experimental coordination with redis
|
||||||
|
Takes all args from Scheduler except redis_conn which
|
||||||
|
must be something closer to a StrictRedis instance.
|
||||||
|
|
||||||
|
My only regret - and the reason why I kept this under the hood for a
|
||||||
|
while - is that it's hard to hook up in web2py to something happening
|
||||||
|
right after the commit to a table, which will enable this version of the
|
||||||
|
scheduler to process "immediate" tasks right away instead of waiting a
|
||||||
|
few seconds (see FIXME in queue_task())
|
||||||
|
|
||||||
|
mode is reserved for future usage patterns.
|
||||||
|
Right now it moves the coordination (which is the most intensive
|
||||||
|
routine in the scheduler in matters of IPC) of workers to redis.
|
||||||
|
I'd like to have incrementally redis-backed modes of operations,
|
||||||
|
such as e.g.:
|
||||||
|
- 1: IPC through redis (which is the current implementation)
|
||||||
|
- 2: Store task results in redis (which will relieve further pressure
|
||||||
|
from the db leaving the scheduler_run table empty and possibly
|
||||||
|
keep things smooth as tasks results can be set to expire
|
||||||
|
after a bit of time)
|
||||||
|
- 3: Move all the logic for storing and queueing tasks to redis
|
||||||
|
itself - which means no scheduler_task usage too - and use
|
||||||
|
the database only as an historical record-bookkeeping
|
||||||
|
(e.g. for reporting)
|
||||||
|
|
||||||
|
As usual, I'm eager to see your comments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Scheduler.__init__(self, db, tasks=tasks, migrate=migrate,
|
||||||
|
worker_name=worker_name, group_names=group_names,
|
||||||
|
heartbeat=heartbeat, max_empty_runs=max_empty_runs,
|
||||||
|
discard_results=discard_results, utc_time=utc_time)
|
||||||
|
|
||||||
|
self.r_server = redis_conn
|
||||||
|
from gluon import current
|
||||||
|
self._application = current.request.application or 'appname'
|
||||||
|
|
||||||
|
def _nkey(self, key):
|
||||||
|
"""Helper to restrict all keys to a namespace
|
||||||
|
and track them"""
|
||||||
|
prefix = 'w2p:rsched:%s' % self._application
|
||||||
|
allkeys = '%s:allkeys' % prefix
|
||||||
|
newkey = "%s:%s" % (prefix, key)
|
||||||
|
self.r_server.sadd(allkeys, newkey)
|
||||||
|
return newkey
|
||||||
|
|
||||||
|
def prune_all(self):
|
||||||
|
"""
|
||||||
|
Just to be fair and implement a method
|
||||||
|
that does housekeeping
|
||||||
|
"""
|
||||||
|
all_keys = self._nkey('allkeys')
|
||||||
|
with self.r_server.pipeline() as pipe:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
pipe.watch('PRUNE_ALL')
|
||||||
|
while True:
|
||||||
|
k = pipe.spop(all_keys)
|
||||||
|
if k is None:
|
||||||
|
break
|
||||||
|
pipe.delete(k)
|
||||||
|
pipe.execute()
|
||||||
|
break
|
||||||
|
except RWatchError:
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def dt2str(self, value):
|
||||||
|
return value.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
def str2date(self, value):
|
||||||
|
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
def send_heartbeat(self, counter):
|
||||||
|
"""
|
||||||
|
workers coordination has evolved into something is not that
|
||||||
|
easy. Here we try to do what we need in a single transaction,
|
||||||
|
and retry that transaction if something goes wrong
|
||||||
|
"""
|
||||||
|
with self.r_server.pipeline() as pipe:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
pipe.watch('SEND_HEARTBEAT')
|
||||||
|
self.inner_send_heartbeat(counter, pipe)
|
||||||
|
pipe.execute()
|
||||||
|
self.adj_hibernation()
|
||||||
|
self.sleep()
|
||||||
|
break
|
||||||
|
except RWatchError:
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def inner_send_heartbeat(self, counter, pipe):
|
||||||
|
"""
|
||||||
|
Does a few things:
|
||||||
|
- registers the workers
|
||||||
|
- accepts commands sent to workers (KILL, TERMINATE, PICK, DISABLED, etc)
|
||||||
|
- adjusts sleep
|
||||||
|
- saves stats
|
||||||
|
- elects master
|
||||||
|
- does "housecleaning" for dead workers
|
||||||
|
- triggers tasks assignment
|
||||||
|
"""
|
||||||
|
r_server = pipe
|
||||||
|
status_keyset = self._nkey('worker_statuses')
|
||||||
|
status_key = self._nkey('worker_status:%s' % (self.worker_name))
|
||||||
|
now = self.now()
|
||||||
|
mybackedstatus = r_server.hgetall(status_key)
|
||||||
|
if not mybackedstatus:
|
||||||
|
r_server.hmset(
|
||||||
|
status_key,
|
||||||
|
dict(
|
||||||
|
status=ACTIVE, worker_name=self.worker_name,
|
||||||
|
first_heartbeat=self.dt2str(now),
|
||||||
|
last_heartbeat=self.dt2str(now),
|
||||||
|
group_names=dumps(self.group_names), is_ticker=False,
|
||||||
|
worker_stats=dumps(self.w_stats))
|
||||||
|
)
|
||||||
|
r_server.sadd(status_keyset, status_key)
|
||||||
|
if not self.w_stats.status == POLLING:
|
||||||
|
self.w_stats.status = ACTIVE
|
||||||
|
self.w_stats.sleep = self.heartbeat
|
||||||
|
mybackedstatus = ACTIVE
|
||||||
|
else:
|
||||||
|
mybackedstatus = mybackedstatus['status']
|
||||||
|
if mybackedstatus == DISABLED:
|
||||||
|
# keep sleeping
|
||||||
|
self.w_stats.status = DISABLED
|
||||||
|
r_server.hmset(
|
||||||
|
status_key,
|
||||||
|
dict(last_heartbeat=self.dt2str(now),
|
||||||
|
worker_stats=dumps(self.w_stats))
|
||||||
|
)
|
||||||
|
elif mybackedstatus == TERMINATE:
|
||||||
|
self.w_stats.status = TERMINATE
|
||||||
|
logger.debug("Waiting to terminate the current task")
|
||||||
|
self.give_up()
|
||||||
|
elif mybackedstatus == KILL:
|
||||||
|
self.w_stats.status = KILL
|
||||||
|
self.die()
|
||||||
|
else:
|
||||||
|
if mybackedstatus == STOP_TASK:
|
||||||
|
logger.info('Asked to kill the current task')
|
||||||
|
self.terminate_process()
|
||||||
|
logger.info('........recording heartbeat (%s)',
|
||||||
|
self.w_stats.status)
|
||||||
|
r_server.hmset(
|
||||||
|
status_key,
|
||||||
|
dict(
|
||||||
|
last_heartbeat=self.dt2str(now), status=ACTIVE,
|
||||||
|
worker_stats=dumps(self.w_stats)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# newroutine
|
||||||
|
r_server.expire(status_key, self.heartbeat * 3 * 15)
|
||||||
|
self.w_stats.sleep = self.heartbeat # re-activating the process
|
||||||
|
if self.w_stats.status not in (RUNNING, POLLING):
|
||||||
|
self.w_stats.status = ACTIVE
|
||||||
|
|
||||||
|
self.do_assign_tasks = False
|
||||||
|
if counter % 5 == 0 or mybackedstatus == PICK:
|
||||||
|
try:
|
||||||
|
logger.info(
|
||||||
|
' freeing workers that have not sent heartbeat')
|
||||||
|
registered_workers = r_server.smembers(status_keyset)
|
||||||
|
allkeys = self._nkey('allkeys')
|
||||||
|
for worker in registered_workers:
|
||||||
|
w = r_server.hgetall(worker)
|
||||||
|
w = Storage(w)
|
||||||
|
if not w:
|
||||||
|
r_server.srem(status_keyset, worker)
|
||||||
|
logger.info('removing %s from %s', worker, allkeys)
|
||||||
|
r_server.srem(allkeys, worker)
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
self.is_a_ticker = self.being_a_ticker(pipe)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if self.w_stats.status in (ACTIVE, POLLING):
|
||||||
|
self.do_assign_tasks = True
|
||||||
|
if self.is_a_ticker and self.do_assign_tasks:
|
||||||
|
# I'm a ticker, and 5 loops passed without reassigning tasks,
|
||||||
|
# let's do that and loop again
|
||||||
|
if not self.db_thread:
|
||||||
|
logger.debug('thread building own DAL object')
|
||||||
|
self.db_thread = DAL(
|
||||||
|
self.db._uri, folder=self.db._adapter.folder)
|
||||||
|
self.define_tables(self.db_thread, migrate=False)
|
||||||
|
db = self.db_thread
|
||||||
|
self.wrapped_assign_tasks(db)
|
||||||
|
return None
|
||||||
|
except:
|
||||||
|
logger.error('Error assigning tasks')
|
||||||
|
|
||||||
|
def being_a_ticker(self, pipe):
|
||||||
|
"""
|
||||||
|
This is slightly more convoluted than the original
|
||||||
|
but if far more efficient
|
||||||
|
"""
|
||||||
|
r_server = pipe
|
||||||
|
status_keyset = self._nkey('worker_statuses')
|
||||||
|
registered_workers = r_server.smembers(status_keyset)
|
||||||
|
ticker = None
|
||||||
|
all_active = []
|
||||||
|
all_workers = []
|
||||||
|
for worker in registered_workers:
|
||||||
|
w = r_server.hgetall(worker)
|
||||||
|
if w['worker_name'] != self.worker_name and w['status'] == ACTIVE:
|
||||||
|
all_active.append(w)
|
||||||
|
if w['is_ticker'] == 'True' and ticker is None:
|
||||||
|
ticker = w
|
||||||
|
all_workers.append(w)
|
||||||
|
not_busy = self.w_stats.status in (ACTIVE, POLLING)
|
||||||
|
if not ticker:
|
||||||
|
if not_busy:
|
||||||
|
# only if this worker isn't busy, otherwise wait for a free one
|
||||||
|
for worker in all_workers:
|
||||||
|
key = self._nkey('worker_status:%s' % worker['worker_name'])
|
||||||
|
if worker['worker_name'] == self.worker_name:
|
||||||
|
r_server.hset(key, 'is_ticker', True)
|
||||||
|
else:
|
||||||
|
r_server.hset(key, 'is_ticker', False)
|
||||||
|
logger.info("TICKER: I'm a ticker")
|
||||||
|
else:
|
||||||
|
# giving up, only if I'm not alone
|
||||||
|
if len(all_active) > 1:
|
||||||
|
key = self._nkey('worker_status:%s' % (self.worker_name))
|
||||||
|
r_server.hset(key, 'is_ticker', False)
|
||||||
|
else:
|
||||||
|
not_busy = True
|
||||||
|
return not_busy
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
"%s is a ticker, I'm a poor worker" % ticker['worker_name'])
|
||||||
|
return False
|
||||||
|
|
||||||
|
def assign_tasks(self, db):
|
||||||
|
"""
|
||||||
|
The real beauty. We don't need to ASSIGN tasks, we just put
|
||||||
|
them into the relevant queue
|
||||||
|
"""
|
||||||
|
st, sd = db.scheduler_task, db.scheduler_task_deps
|
||||||
|
r_server = self.r_server
|
||||||
|
now = self.now()
|
||||||
|
status_keyset = self._nkey('worker_statuses')
|
||||||
|
with r_server.pipeline() as pipe:
|
||||||
|
while 1:
|
||||||
|
try:
|
||||||
|
# making sure we're the only one doing the job
|
||||||
|
pipe.watch('ASSIGN_TASKS')
|
||||||
|
registered_workers = pipe.smembers(status_keyset)
|
||||||
|
all_workers = []
|
||||||
|
for worker in registered_workers:
|
||||||
|
w = pipe.hgetall(worker)
|
||||||
|
if w['status'] == ACTIVE:
|
||||||
|
all_workers.append(Storage(w))
|
||||||
|
pipe.execute()
|
||||||
|
break
|
||||||
|
except RWatchError:
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# build workers as dict of groups
|
||||||
|
wkgroups = {}
|
||||||
|
for w in all_workers:
|
||||||
|
group_names = loads(w.group_names)
|
||||||
|
for gname in group_names:
|
||||||
|
if gname not in wkgroups:
|
||||||
|
wkgroups[gname] = dict(
|
||||||
|
workers=[{'name': w.worker_name, 'c': 0}])
|
||||||
|
else:
|
||||||
|
wkgroups[gname]['workers'].append(
|
||||||
|
{'name': w.worker_name, 'c': 0})
|
||||||
|
# set queued tasks that expired between "runs" (i.e., you turned off
|
||||||
|
# the scheduler): then it wasn't expired, but now it is
|
||||||
|
db(
|
||||||
|
(st.status.belongs((QUEUED, ASSIGNED))) &
|
||||||
|
(st.stop_time < now)
|
||||||
|
).update(status=EXPIRED)
|
||||||
|
|
||||||
|
# calculate dependencies
|
||||||
|
deps_with_no_deps = db(
|
||||||
|
(sd.can_visit == False) &
|
||||||
|
(~sd.task_child.belongs(
|
||||||
|
db(sd.can_visit == False)._select(sd.task_parent)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)._select(sd.task_child)
|
||||||
|
no_deps = db(
|
||||||
|
(st.status.belongs((QUEUED, ASSIGNED))) &
|
||||||
|
(
|
||||||
|
(sd.id == None) | (st.id.belongs(deps_with_no_deps))
|
||||||
|
|
||||||
|
)
|
||||||
|
)._select(st.id, distinct=True, left=sd.on(
|
||||||
|
(st.id == sd.task_parent) &
|
||||||
|
(sd.can_visit == False)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
all_available = db(
|
||||||
|
(st.status.belongs((QUEUED, ASSIGNED))) &
|
||||||
|
((st.times_run < st.repeats) | (st.repeats == 0)) &
|
||||||
|
(st.start_time <= now) &
|
||||||
|
((st.stop_time == None) | (st.stop_time > now)) &
|
||||||
|
(st.next_run_time <= now) &
|
||||||
|
(st.enabled == True) &
|
||||||
|
(st.id.belongs(no_deps))
|
||||||
|
)
|
||||||
|
|
||||||
|
limit = len(all_workers) * (50 / (len(wkgroups) or 1))
|
||||||
|
|
||||||
|
# let's freeze it up
|
||||||
|
db.commit()
|
||||||
|
x = 0
|
||||||
|
r_server = self.r_server
|
||||||
|
for group in wkgroups.keys():
|
||||||
|
queued_list = self._nkey('queued:%s' % group)
|
||||||
|
queued_set = self._nkey('queued_set:%s' % group)
|
||||||
|
# if are running, let's don't assign them again
|
||||||
|
running_list = self._nkey('running:%s' % group)
|
||||||
|
while True:
|
||||||
|
# the joys for rpoplpush!
|
||||||
|
t = r_server.rpoplpush(running_list, queued_list)
|
||||||
|
if not t:
|
||||||
|
# no more
|
||||||
|
break
|
||||||
|
r_server.sadd(queued_set, t)
|
||||||
|
|
||||||
|
tasks = all_available(st.group_name == group).select(
|
||||||
|
limitby=(0, limit), orderby = st.next_run_time)
|
||||||
|
|
||||||
|
# put tasks in the processing list
|
||||||
|
|
||||||
|
for task in tasks:
|
||||||
|
x += 1
|
||||||
|
gname = task.group_name
|
||||||
|
|
||||||
|
if r_server.sismember(queued_set, task.id):
|
||||||
|
# already queued, we don't put on the list
|
||||||
|
continue
|
||||||
|
r_server.sadd(queued_set, task.id)
|
||||||
|
r_server.lpush(queued_list, task.id)
|
||||||
|
d = dict(status=QUEUED)
|
||||||
|
if not task.task_name:
|
||||||
|
d['task_name'] = task.function_name
|
||||||
|
db(
|
||||||
|
(st.id == task.id) &
|
||||||
|
(st.status.belongs((QUEUED, ASSIGNED)))
|
||||||
|
).update(**d)
|
||||||
|
db.commit()
|
||||||
|
# I didn't report tasks but I'm working nonetheless!!!!
|
||||||
|
if x > 0:
|
||||||
|
self.w_stats.empty_runs = 0
|
||||||
|
self.w_stats.queue = x
|
||||||
|
self.w_stats.distribution = wkgroups
|
||||||
|
self.w_stats.workers = len(all_workers)
|
||||||
|
# I'll be greedy only if tasks queued are equal to the limit
|
||||||
|
# (meaning there could be others ready to be queued)
|
||||||
|
self.greedy = x >= limit
|
||||||
|
logger.info('TICKER: workers are %s', len(all_workers))
|
||||||
|
logger.info('TICKER: tasks are %s', x)
|
||||||
|
|
||||||
|
def pop_task(self, db):
|
||||||
|
r_server = self.r_server
|
||||||
|
st = self.db.scheduler_task
|
||||||
|
task = None
|
||||||
|
# ready to process something
|
||||||
|
for group in self.group_names:
|
||||||
|
queued_set = self._nkey('queued_set:%s' % group)
|
||||||
|
queued_list = self._nkey('queued:%s' % group)
|
||||||
|
running_list = self._nkey('running:%s' % group)
|
||||||
|
running_dict = self._nkey('running_dict:%s' % group)
|
||||||
|
self.w_stats.status = POLLING
|
||||||
|
# polling for 1 minute in total. If more groups are in,
|
||||||
|
# polling is 1 minute in total
|
||||||
|
logger.debug(' polling on %s' , group)
|
||||||
|
task_id = r_server.brpoplpush(queued_list, running_list, timeout=60/len(self.group_names))
|
||||||
|
logger.debug(' finished polling')
|
||||||
|
self.w_stats.status = ACTIVE
|
||||||
|
if task_id:
|
||||||
|
r_server.hset(running_dict, task_id, self.worker_name)
|
||||||
|
r_server.srem(queued_set, task_id)
|
||||||
|
task = db(
|
||||||
|
(st.id == task_id) &
|
||||||
|
(st.status == QUEUED)
|
||||||
|
).select().first()
|
||||||
|
if not task:
|
||||||
|
r_server.lrem(running_list, 0, task_id)
|
||||||
|
r_server.hdel(running_dict, task_id)
|
||||||
|
r_server.lrem(queued_list, 0, task_id)
|
||||||
|
logger.error("we received a task that isn't there (%s)" % task_id)
|
||||||
|
return None
|
||||||
|
break
|
||||||
|
now = self.now()
|
||||||
|
if task:
|
||||||
|
task.update_record(status=RUNNING, last_run_time=now)
|
||||||
|
# noone will touch my task!
|
||||||
|
db.commit()
|
||||||
|
logger.debug(' work to do %s', task.id)
|
||||||
|
else:
|
||||||
|
logger.info('nothing to do (%s)' % self.w_stats.status)
|
||||||
|
return None
|
||||||
|
times_run = task.times_run + 1
|
||||||
|
if not task.prevent_drift:
|
||||||
|
next_run_time = task.last_run_time + datetime.timedelta(
|
||||||
|
seconds=task.period
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
next_run_time = task.start_time + datetime.timedelta(
|
||||||
|
seconds=task.period * times_run
|
||||||
|
)
|
||||||
|
if times_run < task.repeats or task.repeats == 0:
|
||||||
|
# need to run (repeating task)
|
||||||
|
run_again = True
|
||||||
|
else:
|
||||||
|
# no need to run again
|
||||||
|
run_again = False
|
||||||
|
run_id = 0
|
||||||
|
while True and not self.discard_results:
|
||||||
|
logger.debug(' new scheduler_run record')
|
||||||
|
try:
|
||||||
|
run_id = db.scheduler_run.insert(
|
||||||
|
task_id=task.id,
|
||||||
|
status=RUNNING,
|
||||||
|
start_time=now,
|
||||||
|
worker_name=self.worker_name)
|
||||||
|
db.commit()
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
time.sleep(0.5)
|
||||||
|
db.rollback()
|
||||||
|
logger.info('new task %(id)s "%(task_name)s"'
|
||||||
|
' %(application_name)s.%(function_name)s' % task)
|
||||||
|
return Task(
|
||||||
|
app=task.application_name,
|
||||||
|
function=task.function_name,
|
||||||
|
timeout=task.timeout,
|
||||||
|
args=task.args, # in json
|
||||||
|
vars=task.vars, # in json
|
||||||
|
task_id=task.id,
|
||||||
|
run_id=run_id,
|
||||||
|
run_again=run_again,
|
||||||
|
next_run_time=next_run_time,
|
||||||
|
times_run=times_run,
|
||||||
|
stop_time=task.stop_time,
|
||||||
|
retry_failed=task.retry_failed,
|
||||||
|
times_failed=task.times_failed,
|
||||||
|
sync_output=task.sync_output,
|
||||||
|
uuid=task.uuid,
|
||||||
|
group_name=task.group_name)
|
||||||
|
|
||||||
|
def report_task(self, task, task_report):
|
||||||
|
"""
|
||||||
|
Needs overwriting only because we need to pop from the
|
||||||
|
running tasks
|
||||||
|
"""
|
||||||
|
r_server = self.r_server
|
||||||
|
db = self.db
|
||||||
|
now = self.now()
|
||||||
|
st = db.scheduler_task
|
||||||
|
sr = db.scheduler_run
|
||||||
|
if not self.discard_results:
|
||||||
|
if task_report.result != 'null' or task_report.tb:
|
||||||
|
# result is 'null' as a string if task completed
|
||||||
|
# if it's stopped it's None as NoneType, so we record
|
||||||
|
# the STOPPED "run" anyway
|
||||||
|
logger.debug(' recording task report in db (%s)',
|
||||||
|
task_report.status)
|
||||||
|
db(sr.id == task.run_id).update(
|
||||||
|
status=task_report.status,
|
||||||
|
stop_time=now,
|
||||||
|
run_result=task_report.result,
|
||||||
|
run_output=task_report.output,
|
||||||
|
traceback=task_report.tb)
|
||||||
|
else:
|
||||||
|
logger.debug(' deleting task report in db because of no result')
|
||||||
|
db(sr.id == task.run_id).delete()
|
||||||
|
# if there is a stop_time and the following run would exceed it
|
||||||
|
is_expired = (task.stop_time
|
||||||
|
and task.next_run_time > task.stop_time
|
||||||
|
and True or False)
|
||||||
|
status = (task.run_again and is_expired and EXPIRED
|
||||||
|
or task.run_again and not is_expired
|
||||||
|
and QUEUED or COMPLETED)
|
||||||
|
if task_report.status == COMPLETED:
|
||||||
|
# assigned calculations
|
||||||
|
d = dict(status=status,
|
||||||
|
next_run_time=task.next_run_time,
|
||||||
|
times_run=task.times_run,
|
||||||
|
times_failed=0,
|
||||||
|
assigned_worker_name=self.worker_name
|
||||||
|
)
|
||||||
|
db(st.id == task.task_id).update(**d)
|
||||||
|
if status == COMPLETED:
|
||||||
|
self.update_dependencies(db, task.task_id)
|
||||||
|
else:
|
||||||
|
st_mapping = {'FAILED': 'FAILED',
|
||||||
|
'TIMEOUT': 'TIMEOUT',
|
||||||
|
'STOPPED': 'FAILED'}[task_report.status]
|
||||||
|
status = (task.retry_failed
|
||||||
|
and task.times_failed < task.retry_failed
|
||||||
|
and QUEUED or task.retry_failed == -1
|
||||||
|
and QUEUED or st_mapping)
|
||||||
|
db(st.id == task.task_id).update(
|
||||||
|
times_failed=db.scheduler_task.times_failed + 1,
|
||||||
|
next_run_time=task.next_run_time,
|
||||||
|
status=status,
|
||||||
|
assigned_worker_name=self.worker_name
|
||||||
|
)
|
||||||
|
logger.info('task completed (%s)', task_report.status)
|
||||||
|
running_list = self._nkey('running:%s' % task.group_name)
|
||||||
|
running_dict = self._nkey('running_dict:%s' % task.group_name)
|
||||||
|
r_server.lrem(running_list, 0, task.task_id)
|
||||||
|
r_server.hdel(running_dict, task.task_id)
|
||||||
|
|
||||||
|
def wrapped_pop_task(self):
|
||||||
|
"""Commodity function to call `pop_task` and trap exceptions
|
||||||
|
If an exception is raised, assume it happened because of database
|
||||||
|
contention and retries `pop_task` after 0.5 seconds
|
||||||
|
"""
|
||||||
|
db = self.db
|
||||||
|
db.commit() # another nifty db.commit() only for Mysql
|
||||||
|
x = 0
|
||||||
|
while x < 10:
|
||||||
|
try:
|
||||||
|
rtn = self.pop_task(db)
|
||||||
|
return rtn
|
||||||
|
break
|
||||||
|
# this is here to "interrupt" any blrpoplpush op easily
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self.give_up()
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
self.w_stats.errors += 1
|
||||||
|
db.rollback()
|
||||||
|
logger.error(' error popping tasks')
|
||||||
|
x += 1
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def get_workers(self, only_ticker=False):
|
||||||
|
""" Returns a dict holding worker_name : {**columns}
|
||||||
|
representing all "registered" workers
|
||||||
|
only_ticker returns only the worker running as a TICKER,
|
||||||
|
if there is any
|
||||||
|
"""
|
||||||
|
r_server = self.r_server
|
||||||
|
status_keyset = self._nkey('worker_statuses')
|
||||||
|
registered_workers = r_server.smembers(status_keyset)
|
||||||
|
all_workers = {}
|
||||||
|
for worker in registered_workers:
|
||||||
|
w = r_server.hgetall(worker)
|
||||||
|
w = Storage(w)
|
||||||
|
if not w:
|
||||||
|
continue
|
||||||
|
all_workers[w.worker_name] = Storage(
|
||||||
|
status=w.status,
|
||||||
|
first_heartbeat=self.str2date(w.first_heartbeat),
|
||||||
|
last_heartbeat=self.str2date(w.last_heartbeat),
|
||||||
|
group_names=loads(w.group_names, object_hook=_decode_dict),
|
||||||
|
is_ticker=w.is_ticker == 'True' and True or False,
|
||||||
|
worker_stats=loads(w.worker_stats, object_hook=_decode_dict)
|
||||||
|
)
|
||||||
|
if only_ticker:
|
||||||
|
for k, v in all_workers.iteritems():
|
||||||
|
if v['is_ticker']:
|
||||||
|
return {k: v}
|
||||||
|
return {}
|
||||||
|
return all_workers
|
||||||
|
|
||||||
|
def set_worker_status(self, group_names=None, action=ACTIVE,
|
||||||
|
exclude=None, limit=None, worker_name=None):
|
||||||
|
"""Internal function to set worker's status"""
|
||||||
|
r_server = self.r_server
|
||||||
|
all_workers = self.get_workers()
|
||||||
|
if not group_names:
|
||||||
|
group_names = self.group_names
|
||||||
|
elif isinstance(group_names, str):
|
||||||
|
group_names = [group_names]
|
||||||
|
exclusion = exclude and exclude.append(action) or [action]
|
||||||
|
workers = []
|
||||||
|
if worker_name is not None:
|
||||||
|
if worker_name in all_workers.keys():
|
||||||
|
workers = [worker_name]
|
||||||
|
else:
|
||||||
|
for k, v in all_workers.iteritems():
|
||||||
|
if v.status not in exclusion and set(group_names) & set(v.group_names):
|
||||||
|
workers.append(k)
|
||||||
|
if limit and worker_name is None:
|
||||||
|
workers = workers[:limit]
|
||||||
|
if workers:
|
||||||
|
with r_server.pipeline() as pipe:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
pipe.watch('SET_WORKER_STATUS')
|
||||||
|
for w in workers:
|
||||||
|
worker_key = self._nkey('worker_status:%s' % w)
|
||||||
|
pipe.hset(worker_key, 'status', action)
|
||||||
|
pipe.execute()
|
||||||
|
break
|
||||||
|
except RWatchError:
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def queue_task(self, function, pargs=[], pvars={}, **kwargs):
|
||||||
|
"""
|
||||||
|
FIXME: immediate should put item in queue. The hard part is
|
||||||
|
that currently there are no hooks happening at post-commit time
|
||||||
|
Queue tasks. This takes care of handling the validation of all
|
||||||
|
parameters
|
||||||
|
|
||||||
|
Args:
|
||||||
|
function: the function (anything callable with a __name__)
|
||||||
|
pargs: "raw" args to be passed to the function. Automatically
|
||||||
|
jsonified.
|
||||||
|
pvars: "raw" kwargs to be passed to the function. Automatically
|
||||||
|
jsonified
|
||||||
|
kwargs: all the parameters available (basically, every
|
||||||
|
`scheduler_task` column). If args and vars are here, they should
|
||||||
|
be jsonified already, and they will override pargs and pvars
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
a dict just as a normal validate_and_insert(), plus a uuid key
|
||||||
|
holding the uuid of the queued task. If validation is not passed
|
||||||
|
( i.e. some parameters are invalid) both id and uuid will be None,
|
||||||
|
and you'll get an "error" dict holding the errors found.
|
||||||
|
"""
|
||||||
|
if hasattr(function, '__name__'):
|
||||||
|
function = function.__name__
|
||||||
|
targs = 'args' in kwargs and kwargs.pop('args') or dumps(pargs)
|
||||||
|
tvars = 'vars' in kwargs and kwargs.pop('vars') or dumps(pvars)
|
||||||
|
tuuid = 'uuid' in kwargs and kwargs.pop('uuid') or web2py_uuid()
|
||||||
|
tname = 'task_name' in kwargs and kwargs.pop('task_name') or function
|
||||||
|
immediate = 'immediate' in kwargs and kwargs.pop('immediate') or None
|
||||||
|
rtn = self.db.scheduler_task.validate_and_insert(
|
||||||
|
function_name=function,
|
||||||
|
task_name=tname,
|
||||||
|
args=targs,
|
||||||
|
vars=tvars,
|
||||||
|
uuid=tuuid,
|
||||||
|
**kwargs)
|
||||||
|
if not rtn.errors:
|
||||||
|
rtn.uuid = tuuid
|
||||||
|
if immediate:
|
||||||
|
r_server = self.r_server
|
||||||
|
ticker = self.get_workers(only_ticker=True)
|
||||||
|
if ticker.keys():
|
||||||
|
ticker = ticker.keys()[0]
|
||||||
|
with r_server.pipeline() as pipe:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
pipe.watch('SET_WORKER_STATUS')
|
||||||
|
worker_key = self._nkey('worker_status:%s' % ticker)
|
||||||
|
pipe.hset(worker_key, 'status', 'PICK')
|
||||||
|
pipe.execute()
|
||||||
|
break
|
||||||
|
except RWatchError:
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
rtn.uuid = None
|
||||||
|
return rtn
|
||||||
|
|
||||||
|
def stop_task(self, ref):
|
||||||
|
"""Shortcut for task termination.
|
||||||
|
|
||||||
|
If the task is RUNNING it will terminate it, meaning that status
|
||||||
|
will be set as FAILED.
|
||||||
|
|
||||||
|
If the task is QUEUED, its stop_time will be set as to "now",
|
||||||
|
the enabled flag will be set to False, and the status to STOPPED
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ref: can be
|
||||||
|
|
||||||
|
- an integer : lookup will be done by scheduler_task.id
|
||||||
|
- a string : lookup will be done by scheduler_task.uuid
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- 1 if task was stopped (meaning an update has been done)
|
||||||
|
- None if task was not found, or if task was not RUNNING or QUEUED
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Experimental
|
||||||
|
"""
|
||||||
|
r_server = self.r_server
|
||||||
|
st = self.db.scheduler_task
|
||||||
|
if isinstance(ref, int):
|
||||||
|
q = st.id == ref
|
||||||
|
elif isinstance(ref, str):
|
||||||
|
q = st.uuid == ref
|
||||||
|
else:
|
||||||
|
raise SyntaxError(
|
||||||
|
"You can retrieve results only by id or uuid")
|
||||||
|
task = self.db(q).select(st.id, st.status, st.group_name)
|
||||||
|
task = task.first()
|
||||||
|
rtn = None
|
||||||
|
if not task:
|
||||||
|
return rtn
|
||||||
|
running_dict = self._nkey('running_dict:%s' % task.group_name)
|
||||||
|
if task.status == 'RUNNING':
|
||||||
|
worker_key = r_server.hget(running_dict, task.id)
|
||||||
|
worker_key = self._nkey('worker_status:%s' % (worker_key))
|
||||||
|
r_server.hset(worker_key, 'status', STOP_TASK)
|
||||||
|
elif task.status == 'QUEUED':
|
||||||
|
rtn = self.db(q).update(
|
||||||
|
stop_time=self.now(),
|
||||||
|
enabled=False,
|
||||||
|
status=STOPPED)
|
||||||
|
return rtn
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Developed by niphlod@gmail.com
|
Developed by niphlod@gmail.com
|
||||||
|
License MIT/BSD/GPL
|
||||||
|
|
||||||
|
Redis-backed sessions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import redis
|
|
||||||
from gluon import current
|
|
||||||
from gluon.storage import Storage
|
|
||||||
import time
|
|
||||||
import logging
|
import logging
|
||||||
import thread
|
import thread
|
||||||
|
from gluon import current
|
||||||
|
from gluon.storage import Storage
|
||||||
|
from gluon.contrib.redis_utils import acquire_lock, release_lock
|
||||||
|
from gluon.contrib.redis_utils import register_release_lock
|
||||||
|
|
||||||
logger = logging.getLogger("web2py.session.redis")
|
logger = logging.getLogger("web2py.session.redis")
|
||||||
|
|
||||||
@@ -16,10 +21,20 @@ locker = thread.allocate_lock()
|
|||||||
|
|
||||||
def RedisSession(*args, **vars):
|
def RedisSession(*args, **vars):
|
||||||
"""
|
"""
|
||||||
Usage example: put in models
|
Usage example: put in models::
|
||||||
from gluon.contrib.redis_session import RedisSession
|
|
||||||
sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False, password=None)
|
from gluon.contrib.redis_utils import RConn
|
||||||
session.connect(request, response, db = sessiondb)
|
rconn = RConn()
|
||||||
|
from gluon.contrib.redis_session
|
||||||
|
sessiondb = RedisSession(redis_conn=rconn, with_lock=True, session_expiry=False)
|
||||||
|
session.connect(request, response, db = sessiondb)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
redis_conn: a redis-like connection object
|
||||||
|
with_lock: prevent concurrent modifications to the same session
|
||||||
|
session_expiry: delete automatically sessions after n seconds
|
||||||
|
(still need to run sessions2trash.py every 1M sessions
|
||||||
|
or so)
|
||||||
|
|
||||||
Simple slip-in storage for session
|
Simple slip-in storage for session
|
||||||
"""
|
"""
|
||||||
@@ -36,30 +51,9 @@ def RedisSession(*args, **vars):
|
|||||||
|
|
||||||
class RedisClient(object):
|
class RedisClient(object):
|
||||||
|
|
||||||
meta_storage = {}
|
def __init__(self, redis_conn, session_expiry=False, with_lock=False):
|
||||||
MAX_RETRIES = 5
|
self.r_server = redis_conn
|
||||||
RETRIES = 0
|
self._release_script = register_release_lock(self.r_server)
|
||||||
_release_script = None
|
|
||||||
|
|
||||||
def __init__(self, server='localhost:6379', db=None, debug=False,
|
|
||||||
session_expiry=False, with_lock=False, password=None):
|
|
||||||
"""session_expiry can be an integer, in seconds, to set the default expiration
|
|
||||||
of sessions. The corresponding record will be deleted from the redis instance,
|
|
||||||
and there's virtually no need to run sessions2trash.py
|
|
||||||
"""
|
|
||||||
self.server = server
|
|
||||||
self.password = password
|
|
||||||
self.db = db or 0
|
|
||||||
host, port = (self.server.split(':') + ['6379'])[:2]
|
|
||||||
port = int(port)
|
|
||||||
self.debug = debug
|
|
||||||
if current and current.request:
|
|
||||||
self.app = current.request.application
|
|
||||||
else:
|
|
||||||
self.app = ''
|
|
||||||
self.r_server = redis.Redis(host=host, port=port, db=self.db, password=self.password)
|
|
||||||
if with_lock:
|
|
||||||
RedisClient._release_script = self.r_server.register_script(_LUA_RELEASE_LOCK)
|
|
||||||
self.tablename = None
|
self.tablename = None
|
||||||
self.session_expiry = session_expiry
|
self.session_expiry = session_expiry
|
||||||
self.with_lock = with_lock
|
self.with_lock = with_lock
|
||||||
@@ -93,12 +87,11 @@ class RedisClient(object):
|
|||||||
class MockTable(object):
|
class MockTable(object):
|
||||||
|
|
||||||
def __init__(self, db, r_server, tablename, session_expiry, with_lock=False):
|
def __init__(self, db, r_server, tablename, session_expiry, with_lock=False):
|
||||||
|
# here self.db is the RedisClient instance
|
||||||
self.db = db
|
self.db = db
|
||||||
self.r_server = r_server
|
|
||||||
self.tablename = tablename
|
self.tablename = tablename
|
||||||
# set the namespace for sessions of this app
|
# set the namespace for sessions of this app
|
||||||
self.keyprefix = 'w2p:sess:%s' % tablename.replace(
|
self.keyprefix = 'w2p:sess:%s' % tablename.replace('web2py_session_', '')
|
||||||
'web2py_session_', '')
|
|
||||||
# fast auto-increment id (needed for session handling)
|
# fast auto-increment id (needed for session handling)
|
||||||
self.serial = "%s:serial" % self.keyprefix
|
self.serial = "%s:serial" % self.keyprefix
|
||||||
# index of all the session keys of this app
|
# index of all the session keys of this app
|
||||||
@@ -126,7 +119,7 @@ class MockTable(object):
|
|||||||
if key == 'id':
|
if key == 'id':
|
||||||
# return a fake query. We need to query it just by id for normal operations
|
# return a fake query. We need to query it just by id for normal operations
|
||||||
self.query = MockQuery(
|
self.query = MockQuery(
|
||||||
field='id', db=self.r_server,
|
field='id', db=self.db,
|
||||||
prefix=self.keyprefix, session_expiry=self.session_expiry,
|
prefix=self.keyprefix, session_expiry=self.session_expiry,
|
||||||
with_lock=self.with_lock, unique_key=self.unique_key
|
with_lock=self.with_lock, unique_key=self.unique_key
|
||||||
)
|
)
|
||||||
@@ -140,12 +133,12 @@ class MockTable(object):
|
|||||||
# 'locked', 'client_ip','created_datetime','modified_datetime'
|
# 'locked', 'client_ip','created_datetime','modified_datetime'
|
||||||
# 'unique_key', 'session_data'
|
# 'unique_key', 'session_data'
|
||||||
# retrieve a new key
|
# retrieve a new key
|
||||||
newid = str(self.r_server.incr(self.serial))
|
newid = str(self.db.r_server.incr(self.serial))
|
||||||
key = self.keyprefix + ':' + newid
|
key = self.keyprefix + ':' + newid
|
||||||
if self.with_lock:
|
if self.with_lock:
|
||||||
key_lock = key + ':lock'
|
key_lock = key + ':lock'
|
||||||
acquire_lock(self.r_server, key_lock, newid)
|
acquire_lock(self.db.r_server, key_lock, newid)
|
||||||
with self.r_server.pipeline() as pipe:
|
with self.db.r_server.pipeline() as pipe:
|
||||||
# add it to the index
|
# add it to the index
|
||||||
pipe.sadd(self.id_idx, key)
|
pipe.sadd(self.id_idx, key)
|
||||||
# set a hash key with the Storage
|
# set a hash key with the Storage
|
||||||
@@ -154,7 +147,7 @@ class MockTable(object):
|
|||||||
pipe.expire(key, self.session_expiry)
|
pipe.expire(key, self.session_expiry)
|
||||||
pipe.execute()
|
pipe.execute()
|
||||||
if self.with_lock:
|
if self.with_lock:
|
||||||
release_lock(self.r_server, key_lock, newid)
|
release_lock(self.db, key_lock, newid)
|
||||||
return newid
|
return newid
|
||||||
|
|
||||||
|
|
||||||
@@ -186,8 +179,8 @@ class MockQuery(object):
|
|||||||
# means that someone wants to retrieve the key self.value
|
# means that someone wants to retrieve the key self.value
|
||||||
key = self.keyprefix + ':' + str(self.value)
|
key = self.keyprefix + ':' + str(self.value)
|
||||||
if self.with_lock:
|
if self.with_lock:
|
||||||
acquire_lock(self.db, key + ':lock', self.value)
|
acquire_lock(self.db.r_server, key + ':lock', self.value, 2)
|
||||||
rtn = self.db.hgetall(key)
|
rtn = self.db.r_server.hgetall(key)
|
||||||
if rtn:
|
if rtn:
|
||||||
if self.unique_key:
|
if self.unique_key:
|
||||||
# make sure the id and unique_key are correct
|
# make sure the id and unique_key are correct
|
||||||
@@ -201,13 +194,13 @@ class MockQuery(object):
|
|||||||
rtn = []
|
rtn = []
|
||||||
id_idx = "%s:id_idx" % self.keyprefix
|
id_idx = "%s:id_idx" % self.keyprefix
|
||||||
# find all session keys of this app
|
# find all session keys of this app
|
||||||
allkeys = self.db.smembers(id_idx)
|
allkeys = self.db.r_server.smembers(id_idx)
|
||||||
for sess in allkeys:
|
for sess in allkeys:
|
||||||
val = self.db.hgetall(sess)
|
val = self.db.r_server.hgetall(sess)
|
||||||
if not val:
|
if not val:
|
||||||
if self.session_expiry:
|
if self.session_expiry:
|
||||||
# clean up the idx, because the key expired
|
# clean up the idx, because the key expired
|
||||||
self.db.srem(id_idx, sess)
|
self.db.r_server.srem(id_idx, sess)
|
||||||
continue
|
continue
|
||||||
val = Storage(val)
|
val = Storage(val)
|
||||||
# add a delete_record method (necessary for sessions2trash.py)
|
# add a delete_record method (necessary for sessions2trash.py)
|
||||||
@@ -222,9 +215,9 @@ class MockQuery(object):
|
|||||||
# means that the session has been found and needs an update
|
# means that the session has been found and needs an update
|
||||||
if self.op == 'eq' and self.field == 'id' and self.value:
|
if self.op == 'eq' and self.field == 'id' and self.value:
|
||||||
key = self.keyprefix + ':' + str(self.value)
|
key = self.keyprefix + ':' + str(self.value)
|
||||||
if not self.db.exists(key):
|
if not self.db.r_server.exists(key):
|
||||||
return None
|
return None
|
||||||
with self.db.pipeline() as pipe:
|
with self.db.r_server.pipeline() as pipe:
|
||||||
pipe.hmset(key, kwargs)
|
pipe.hmset(key, kwargs)
|
||||||
if self.session_expiry:
|
if self.session_expiry:
|
||||||
pipe.expire(key, self.session_expiry)
|
pipe.expire(key, self.session_expiry)
|
||||||
@@ -238,7 +231,7 @@ class MockQuery(object):
|
|||||||
if self.op == 'eq' and self.field == 'id' and self.value:
|
if self.op == 'eq' and self.field == 'id' and self.value:
|
||||||
id_idx = "%s:id_idx" % self.keyprefix
|
id_idx = "%s:id_idx" % self.keyprefix
|
||||||
key = self.keyprefix + ':' + str(self.value)
|
key = self.keyprefix + ':' + str(self.value)
|
||||||
with self.db.pipeline() as pipe:
|
with self.db.r_server.pipeline() as pipe:
|
||||||
pipe.delete(key)
|
pipe.delete(key)
|
||||||
pipe.srem(id_idx, key)
|
pipe.srem(id_idx, key)
|
||||||
rtn = pipe.execute()
|
rtn = pipe.execute()
|
||||||
@@ -254,29 +247,6 @@ class RecordDeleter(object):
|
|||||||
def __call__(self):
|
def __call__(self):
|
||||||
id_idx = "%s:id_idx" % self.keyprefix
|
id_idx = "%s:id_idx" % self.keyprefix
|
||||||
# remove from the index
|
# remove from the index
|
||||||
self.db.srem(id_idx, self.key)
|
self.db.r_server.srem(id_idx, self.key)
|
||||||
# remove the key itself
|
# remove the key itself
|
||||||
self.db.delete(self.key)
|
self.db.r_server.delete(self.key)
|
||||||
|
|
||||||
|
|
||||||
def acquire_lock(conn, lockname, identifier, ltime=10):
|
|
||||||
while True:
|
|
||||||
if conn.set(lockname, identifier, ex=ltime, nx=True):
|
|
||||||
return identifier
|
|
||||||
time.sleep(.01)
|
|
||||||
|
|
||||||
|
|
||||||
_LUA_RELEASE_LOCK = """
|
|
||||||
if redis.call("get", KEYS[1]) == ARGV[1]
|
|
||||||
then
|
|
||||||
return redis.call("del", KEYS[1])
|
|
||||||
else
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def release_lock(conn, lockname, identifier):
|
|
||||||
return RedisClient._release_script(
|
|
||||||
keys=[lockname], args=[identifier],
|
|
||||||
client=conn)
|
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Developed by niphlod@gmail.com
|
||||||
|
License MIT/BSD/GPL
|
||||||
|
|
||||||
|
Serves as base to implement Redis connection object and various utils
|
||||||
|
for redis_cache, redis_session and redis_scheduler in the future
|
||||||
|
Should-could be overriden in case redis doesn't keep up (e.g. cluster support)
|
||||||
|
to ensure compatibility with another - similar - library
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import thread
|
||||||
|
import time
|
||||||
|
from gluon import current
|
||||||
|
|
||||||
|
logger = logging.getLogger("web2py.redis_utils")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import redis
|
||||||
|
from redis.exceptions import WatchError as RWatchError
|
||||||
|
from redis.exceptions import ConnectionError as RConnectionError
|
||||||
|
except ImportError:
|
||||||
|
logger.error("Needs redis library to work")
|
||||||
|
raise RuntimeError('Needs redis library to work')
|
||||||
|
|
||||||
|
|
||||||
|
locker = thread.allocate_lock()
|
||||||
|
|
||||||
|
|
||||||
|
def RConn(*args, **vars):
|
||||||
|
"""
|
||||||
|
Istantiates a StrictRedis connection with parameters, at the first time
|
||||||
|
only
|
||||||
|
"""
|
||||||
|
locker.acquire()
|
||||||
|
try:
|
||||||
|
instance_name = 'redis_conn_' + current.request.application
|
||||||
|
if not hasattr(RConn, instance_name):
|
||||||
|
setattr(RConn, instance_name, redis.StrictRedis(*args, **vars))
|
||||||
|
return getattr(RConn, instance_name)
|
||||||
|
finally:
|
||||||
|
locker.release()
|
||||||
|
|
||||||
|
def acquire_lock(conn, lockname, identifier, ltime=10):
|
||||||
|
while True:
|
||||||
|
if conn.set(lockname, identifier, ex=ltime, nx=True):
|
||||||
|
return identifier
|
||||||
|
time.sleep(.01)
|
||||||
|
|
||||||
|
|
||||||
|
_LUA_RELEASE_LOCK = """
|
||||||
|
if redis.call("get", KEYS[1]) == ARGV[1]
|
||||||
|
then
|
||||||
|
return redis.call("del", KEYS[1])
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def release_lock(instance, lockname, identifier):
|
||||||
|
return instance._release_script(
|
||||||
|
keys=[lockname], args=[identifier])
|
||||||
|
|
||||||
|
|
||||||
|
def register_release_lock(conn):
|
||||||
|
rtn = conn.register_script(_LUA_RELEASE_LOCK)
|
||||||
|
return rtn
|
||||||
-407
@@ -1,407 +0,0 @@
|
|||||||
import cgi
|
|
||||||
import copy_reg
|
|
||||||
from gluon import current, URL, DAL
|
|
||||||
from gluon.storage import Storage
|
|
||||||
from gluon.utils import web2py_uuid
|
|
||||||
from gluon.sanitizer import sanitize
|
|
||||||
|
|
||||||
# ################################################################
|
|
||||||
# New HTML Helpers
|
|
||||||
# ################################################################
|
|
||||||
|
|
||||||
def xmlescape(text):
|
|
||||||
return cgi.escape(text, True).replace("'", "'")
|
|
||||||
|
|
||||||
class TAG(object):
|
|
||||||
|
|
||||||
def __init__(self, name, *children, **attributes):
|
|
||||||
self.name = name
|
|
||||||
self.children = list(children)
|
|
||||||
self.attributes = attributes
|
|
||||||
for child in self.children:
|
|
||||||
if isinstance(child, TAG):
|
|
||||||
child.parent = self
|
|
||||||
|
|
||||||
def xml(self):
|
|
||||||
name = self.name
|
|
||||||
a = ' '.join('%s="%s"' %
|
|
||||||
(k[1:], k[1:] if v is True else xmlescape(unicode(v)))
|
|
||||||
for k,v in self.attributes.iteritems()
|
|
||||||
if k.startswith('_') and not v in (False,None))
|
|
||||||
if a:
|
|
||||||
a = ' '+a
|
|
||||||
if name.endswith('/'):
|
|
||||||
return '<%s%s/>' % (name, a)
|
|
||||||
else:
|
|
||||||
b = ''.join(s.xml() if isinstance(s,TAG) else xmlescape(unicode(s))
|
|
||||||
for s in self.children)
|
|
||||||
return '<%s%s>%s</%s>' %(name, a, b, name)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.xml()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.xml().encode('utf8')
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
if isinstance(key, int):
|
|
||||||
return self.children[key]
|
|
||||||
else:
|
|
||||||
return self.attributes[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
if isinstance(key, int):
|
|
||||||
self.children[key] = value
|
|
||||||
else:
|
|
||||||
self.attributes[key] = value
|
|
||||||
|
|
||||||
def append(self, value):
|
|
||||||
self.children.append(value)
|
|
||||||
|
|
||||||
def __delitem__(self,key):
|
|
||||||
if isinstance(key, int):
|
|
||||||
self.children = self.children[:key]+self.children[key+1:]
|
|
||||||
else:
|
|
||||||
del self.attributes[key]
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.children)
|
|
||||||
|
|
||||||
def find(self, query):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
class METATAG(object):
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return self(name)
|
|
||||||
|
|
||||||
def __call__(self, name):
|
|
||||||
return lambda *children, **attributes: TAG(name, *children, **attributes)
|
|
||||||
|
|
||||||
tag = METATAG()
|
|
||||||
DIV = tag('div')
|
|
||||||
SPAN = tag('span')
|
|
||||||
LI = tag('li')
|
|
||||||
OL = tag('ol')
|
|
||||||
UL = tag('ul')
|
|
||||||
A = tag('a')
|
|
||||||
H1 = tag('h1')
|
|
||||||
H2 = tag('h2')
|
|
||||||
H3 = tag('h3')
|
|
||||||
H4 = tag('h4')
|
|
||||||
H5 = tag('h5')
|
|
||||||
H6 = tag('h6')
|
|
||||||
EM = tag('em')
|
|
||||||
TR = tag('tr')
|
|
||||||
TD = tag('td')
|
|
||||||
TH = tag('th')
|
|
||||||
IMG = tag('img/')
|
|
||||||
FORM = tag('form')
|
|
||||||
HEAD = tag('head')
|
|
||||||
BODY = tag('body')
|
|
||||||
TABLE = tag('table')
|
|
||||||
INPUT = tag('input/')
|
|
||||||
LABEL = tag('label')
|
|
||||||
STRONG = tag('strong')
|
|
||||||
SELECT = tag('select')
|
|
||||||
OPTION = tag('option')
|
|
||||||
TEXTAREA = tag('textarea')
|
|
||||||
|
|
||||||
# ################################################################
|
|
||||||
# New XML Helpers
|
|
||||||
# ################################################################
|
|
||||||
|
|
||||||
class XML(TAG):
|
|
||||||
"""
|
|
||||||
use it to wrap a string that contains XML/HTML so that it will not be
|
|
||||||
escaped by the template
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
>>> XML('<h1>Hello</h1>').xml()
|
|
||||||
'<h1>Hello</h1>'
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
text,
|
|
||||||
sanitize=False,
|
|
||||||
permitted_tags=[
|
|
||||||
'a','b','blockquote','br/','i','li','ol','ul','p','cite',
|
|
||||||
'code','pre','img/','h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
||||||
'table', 'tr', 'td', 'div','strong', 'span'],
|
|
||||||
allowed_attributes={
|
|
||||||
'a': ['href', 'title', 'target'],
|
|
||||||
'img': ['src', 'alt'],
|
|
||||||
'blockquote': ['type'],
|
|
||||||
'td': ['colspan']},
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
text: the XML text
|
|
||||||
sanitize: sanitize text using the permitted tags and allowed
|
|
||||||
attributes (default False)
|
|
||||||
permitted_tags: list of permitted tags (default: simple list of
|
|
||||||
tags)
|
|
||||||
allowed_attributes: dictionary of allowed attributed (default
|
|
||||||
for A, IMG and BlockQuote).
|
|
||||||
The key is the tag; the value is a list of allowed attributes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if sanitize:
|
|
||||||
text = sanitize(text, permitted_tags, allowed_attributes)
|
|
||||||
if isinstance(text, unicode):
|
|
||||||
text = text.encode('utf8', 'xmlcharrefreplace')
|
|
||||||
elif not isinstance(text, str):
|
|
||||||
text = str(text)
|
|
||||||
self.text = text
|
|
||||||
|
|
||||||
def xml(self):
|
|
||||||
return self.text
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.text
|
|
||||||
|
|
||||||
def __add__(self, other):
|
|
||||||
return '%s%s' % (self, other)
|
|
||||||
|
|
||||||
def __radd__(self, other):
|
|
||||||
return '%s%s' % (other, self)
|
|
||||||
|
|
||||||
def __cmp__(self, other):
|
|
||||||
return cmp(str(self), str(other))
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(str(self))
|
|
||||||
|
|
||||||
def __getitem__(self, i):
|
|
||||||
return str(self)[i]
|
|
||||||
|
|
||||||
def __getslice__(self, i, j):
|
|
||||||
return str(self)[i:j]
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for c in str(self):
|
|
||||||
yield c
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(str(self))
|
|
||||||
|
|
||||||
def XML_unpickle(data):
|
|
||||||
return XML(marshal.loads(data))
|
|
||||||
|
|
||||||
def XML_pickle(data):
|
|
||||||
return XML_unpickle, (marshal.dumps(str(data)),)
|
|
||||||
copy_reg.pickle(XML, XML_pickle, XML_unpickle)
|
|
||||||
|
|
||||||
# ################################################################
|
|
||||||
# Simple Form Style Function (example for more complex styles)
|
|
||||||
# ################################################################
|
|
||||||
|
|
||||||
def FormStyleDefault(table, vars, errors, readonly, deletable):
|
|
||||||
|
|
||||||
form = FORM(TABLE(),_method='POST',_action='#',_enctype='multipart/form-data')
|
|
||||||
for field in table:
|
|
||||||
|
|
||||||
input_id = '%s_%s' % (field.tablename, field.name)
|
|
||||||
value = field.formatter(vars.get(field.name))
|
|
||||||
error = errors.get(field.name)
|
|
||||||
field_class = field.type.split()[0].replace(':','-')
|
|
||||||
|
|
||||||
if field.type == 'blob': # never display blobs (mistake?)
|
|
||||||
continue
|
|
||||||
elif readonly or field.type=='id':
|
|
||||||
if not field.readable:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
control = field.represent and field.represent(value) or value or ''
|
|
||||||
elif not field.writable:
|
|
||||||
continue
|
|
||||||
elif field.widget:
|
|
||||||
control = field.widget(table, value)
|
|
||||||
elif field.type == 'text':
|
|
||||||
control = TEXTAREA(value or '', _id=input_id,_name=field.name)
|
|
||||||
elif field.type == 'boolean':
|
|
||||||
control = INPUT(_type='checkbox', _id=input_id, _name=field.name,
|
|
||||||
_value='ON', _checked = value)
|
|
||||||
elif field.type == 'upload':
|
|
||||||
control = DIV(INPUT(_type='file', _id=input_id, _name=field.name))
|
|
||||||
if value:
|
|
||||||
control.append(A('download',
|
|
||||||
_href=URL('default','download',args=value)))
|
|
||||||
control.append(INPUT(_type='checkbox',_value='ON',
|
|
||||||
_name='_delete_'+field.name))
|
|
||||||
control.append('(check to remove)')
|
|
||||||
elif hasattr(field.requires, 'options'):
|
|
||||||
multiple = field.type.startswith('list:')
|
|
||||||
value = value if isinstance(value, list) else [value]
|
|
||||||
options = [OPTION(v,_value=k,_selected=(k in value))
|
|
||||||
for k,v in field.requires.options()]
|
|
||||||
control = SELECT(*options, _id=input_id, _name=field.name,
|
|
||||||
_multiple=multiple)
|
|
||||||
else:
|
|
||||||
field_type = 'password' if field.type == 'password' else 'text'
|
|
||||||
control = INPUT(_type=field_type, _id=input_id, _name=field.name,
|
|
||||||
_value=value, _class=field_class)
|
|
||||||
|
|
||||||
form[0].append(TR(TD(LABEL(field.label,_for=input_id)),
|
|
||||||
TD(control,DIV(error,_class='error') if error else ''),
|
|
||||||
TD(field.comment or '')))
|
|
||||||
|
|
||||||
td = TD(INPUT(_type='submit',_value='Submit'))
|
|
||||||
if deletable:
|
|
||||||
td.append(INPUT(_type='checkbox',_value='ON',_name='_delete'))
|
|
||||||
td.append('(check to delete)')
|
|
||||||
form[0].append(TR(TD(),td,TD()))
|
|
||||||
return form
|
|
||||||
|
|
||||||
# ################################################################
|
|
||||||
# Form object (replaced SQLFORM)
|
|
||||||
# ################################################################
|
|
||||||
|
|
||||||
class Form(object):
|
|
||||||
"""
|
|
||||||
Usage in web2py controller:
|
|
||||||
|
|
||||||
def index():
|
|
||||||
form = Form(db.thing, record=1)
|
|
||||||
if form.accepted: ...
|
|
||||||
elif form.errors: ...
|
|
||||||
else: ...
|
|
||||||
return dict(form=form)
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- table: a DAL table or a list of fields (equivalent to old SQLFORM.factory)
|
|
||||||
- record: a DAL record or record id
|
|
||||||
- readonly: set to True to make a readonly form
|
|
||||||
- deletable: set to False to disallow deletion of record
|
|
||||||
- formstyle: a function that renders the form using helpers (FormStyleDefault)
|
|
||||||
- dbio: set to False to prevent any DB write
|
|
||||||
- keepvalues: (NOT IMPLEMENTED)
|
|
||||||
- formname: the optional name of this form
|
|
||||||
- csrf: set to False to disable CRSF protection
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
table,
|
|
||||||
record=None,
|
|
||||||
readonly=False,
|
|
||||||
deletable=True,
|
|
||||||
formstyle=FormStyleDefault,
|
|
||||||
dbio=True,
|
|
||||||
keepvalues=False,
|
|
||||||
formname=False,
|
|
||||||
csrf=True):
|
|
||||||
|
|
||||||
if isinstance(table, list):
|
|
||||||
dbio = False
|
|
||||||
# mimic a table from a list of fields without calling define_table
|
|
||||||
formname = formname or 'none'
|
|
||||||
for field in table: field.tablename = formname
|
|
||||||
|
|
||||||
if isinstance(record, (int, long, basestring)):
|
|
||||||
record_id = int(str(record))
|
|
||||||
self.record = table[record_id]
|
|
||||||
else:
|
|
||||||
self.record = record
|
|
||||||
|
|
||||||
self.table = table
|
|
||||||
self.readonly = readonly
|
|
||||||
self.deletable = deletable and not readonly and self.record
|
|
||||||
self.formstyle = formstyle
|
|
||||||
self.dbio = dbio
|
|
||||||
self.keepvalues = True if keepvalues or self.record else False
|
|
||||||
self.csrf = csrf
|
|
||||||
self.vars = Storage()
|
|
||||||
self.errors = Storage()
|
|
||||||
self.submitted = False
|
|
||||||
self.deleted = False
|
|
||||||
self.accepted = False
|
|
||||||
self.cached_helper = False
|
|
||||||
self.formname = formname or table._tablename
|
|
||||||
self.formkey = None
|
|
||||||
|
|
||||||
request = current.request
|
|
||||||
session = current.session
|
|
||||||
post_vars = request.post_vars
|
|
||||||
|
|
||||||
if readonly or request.env.request_method=='GET':
|
|
||||||
if self.record:
|
|
||||||
self.vars = self.record
|
|
||||||
else:
|
|
||||||
print post_vars
|
|
||||||
self.submitted = True
|
|
||||||
# check for CSRF
|
|
||||||
if csrf and self.formname in (session._formkeys or {}):
|
|
||||||
self.formkey = session._formkeys[self.formname]
|
|
||||||
# validate fields
|
|
||||||
if not csrf or post_vars._formkey == self.formkey:
|
|
||||||
if not post_vars._delete:
|
|
||||||
for field in self.table:
|
|
||||||
if field.writable:
|
|
||||||
value = post_vars.get(field.name)
|
|
||||||
(value, error) = field.validate(value)
|
|
||||||
if field.type == 'upload':
|
|
||||||
delete = post_vars.get('_delete_'+field.name)
|
|
||||||
if value is not None and hasattr(value,'file'):
|
|
||||||
value = field.store(value.file,
|
|
||||||
value.filename,
|
|
||||||
field.uploadfolder)
|
|
||||||
elif self.record and not delete:
|
|
||||||
value = self.record.get(field.name)
|
|
||||||
else:
|
|
||||||
value = None
|
|
||||||
self.vars[field.name] = value
|
|
||||||
if error:
|
|
||||||
self.errors[field.name] = error
|
|
||||||
if self.record:
|
|
||||||
self.vars.id = self.record.id
|
|
||||||
if not self.errors:
|
|
||||||
self.accepted = True
|
|
||||||
if dbio:
|
|
||||||
if self.record:
|
|
||||||
self.record.update_record(**self.vars)
|
|
||||||
else:
|
|
||||||
# warning, should we really insert if record
|
|
||||||
self.vars.id = self.table.insert(**self.vars)
|
|
||||||
elif dbio:
|
|
||||||
self.deleted = True
|
|
||||||
self.record.delete_record()
|
|
||||||
# store key for future CSRF
|
|
||||||
if csrf:
|
|
||||||
if not session._formkeys:
|
|
||||||
session._formkeys = {}
|
|
||||||
if self.formname not in session._formkeys:
|
|
||||||
session._formkeys[self.formname] = web2py_uuid()
|
|
||||||
self.formkey = session._formkeys[self.formname]
|
|
||||||
|
|
||||||
def clear():
|
|
||||||
self.vars.clear()
|
|
||||||
self.errors.clear()
|
|
||||||
for field in self.table:
|
|
||||||
self.vars[field.name] = field.default
|
|
||||||
|
|
||||||
def helper(self):
|
|
||||||
if not self.cached_helper:
|
|
||||||
cached_helper = self.formstyle(self.table,
|
|
||||||
self.vars,
|
|
||||||
self.errors,
|
|
||||||
self.readonly,
|
|
||||||
self.deletable)
|
|
||||||
if self.csrf:
|
|
||||||
cached_helper.append(INPUT(_type='hidden',_name='_formkey',
|
|
||||||
_value=self.formkey))
|
|
||||||
self.cached_helper = cached_helper
|
|
||||||
return cached_helper
|
|
||||||
|
|
||||||
def xml(self):
|
|
||||||
return self.helper().xml()
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.xml()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.xml().encode('utf8')
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
print(DIV(SPAN('this',STRONG('a test'),XML('1<2')),_id=1,_class="my class"))
|
|
||||||
+18
-8
@@ -362,20 +362,30 @@ class Request(Storage):
|
|||||||
redirect(URL(scheme='https', args=self.args, vars=self.vars))
|
redirect(URL(scheme='https', args=self.args, vars=self.vars))
|
||||||
|
|
||||||
def restful(self):
|
def restful(self):
|
||||||
def wrapper(action, self=self):
|
def wrapper(action, request=self):
|
||||||
def f(_action=action, _self=self, *a, **b):
|
def f(_action=action, *a, **b):
|
||||||
self.is_restful = True
|
request.is_restful = True
|
||||||
method = _self.env.request_method
|
env = request.env
|
||||||
if len(_self.args) and '.' in _self.args[-1]:
|
is_json = env.content_type=='application/json'
|
||||||
_self.args[-1], _, self.extension = self.args[-1].rpartition('.')
|
method = env.request_method
|
||||||
|
if len(request.args) and '.' in request.args[-1]:
|
||||||
|
request.args[-1], _, request.extension = request.args[-1].rpartition('.')
|
||||||
current.response.headers['Content-Type'] = \
|
current.response.headers['Content-Type'] = \
|
||||||
contenttype('.' + _self.extension.lower())
|
contenttype('.' + request.extension.lower())
|
||||||
rest_action = _action().get(method, None)
|
rest_action = _action().get(method, None)
|
||||||
if not (rest_action and method == method.upper()
|
if not (rest_action and method == method.upper()
|
||||||
and callable(rest_action)):
|
and callable(rest_action)):
|
||||||
raise HTTP(405, "method not allowed")
|
raise HTTP(405, "method not allowed")
|
||||||
try:
|
try:
|
||||||
return rest_action(*_self.args, **getattr(_self, 'vars', {}))
|
vars = request.vars
|
||||||
|
if method == 'POST' and is_json:
|
||||||
|
body = request.body.read()
|
||||||
|
if len(body):
|
||||||
|
vars = sj.loads(body)
|
||||||
|
res = rest_action(*request.args, **vars)
|
||||||
|
if is_json and not isinstance(res, str):
|
||||||
|
res = json(res)
|
||||||
|
return res
|
||||||
except TypeError, e:
|
except TypeError, e:
|
||||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
if len(traceback.extract_tb(exc_traceback)) == 1:
|
if len(traceback.extract_tb(exc_traceback)) == 1:
|
||||||
|
|||||||
+1
-1
Submodule gluon/packages/dal updated: dcfb5f58aa...4b37722a22
@@ -53,8 +53,8 @@ except:
|
|||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
import win32con
|
import win32con
|
||||||
import win32file
|
|
||||||
import pywintypes
|
import pywintypes
|
||||||
|
import win32file
|
||||||
os_locking = 'windows'
|
os_locking = 'windows'
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|||||||
+4
-18
@@ -9,7 +9,7 @@
|
|||||||
Generates names for cache and session files
|
Generates names for cache and session files
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
"""
|
"""
|
||||||
import os, uuid
|
import os
|
||||||
|
|
||||||
|
|
||||||
def generate(filename, depth=2, base=512):
|
def generate(filename, depth=2, base=512):
|
||||||
@@ -17,10 +17,10 @@ def generate(filename, depth=2, base=512):
|
|||||||
path, filename = os.path.split(filename)
|
path, filename = os.path.split(filename)
|
||||||
else:
|
else:
|
||||||
path = None
|
path = None
|
||||||
dummyhash = sum(ord(c)*256**(i % 4) for i, c in enumerate(filename)) % base**depth
|
dummyhash = sum(ord(c) * 256 ** (i % 4) for i, c in enumerate(filename)) % base ** depth
|
||||||
folders = []
|
folders = []
|
||||||
for level in range(depth-1, -1, -1):
|
for level in range(depth - 1, -1, -1):
|
||||||
code, dummyhash = divmod(dummyhash, base**level)
|
code, dummyhash = divmod(dummyhash, base ** level)
|
||||||
folders.append("%03x" % code)
|
folders.append("%03x" % code)
|
||||||
folders.append(filename)
|
folders.append(filename)
|
||||||
if path:
|
if path:
|
||||||
@@ -63,17 +63,3 @@ def open(filename, mode="r", path=None):
|
|||||||
if mode.startswith('w') and not os.path.exists(os.path.dirname(fullfilename)):
|
if mode.startswith('w') and not os.path.exists(os.path.dirname(fullfilename)):
|
||||||
os.makedirs(os.path.dirname(fullfilename))
|
os.makedirs(os.path.dirname(fullfilename))
|
||||||
return file(fullfilename, mode)
|
return file(fullfilename, mode)
|
||||||
|
|
||||||
|
|
||||||
def test():
|
|
||||||
if not os.path.exists('tests'):
|
|
||||||
os.mkdir('tests')
|
|
||||||
for k in range(20):
|
|
||||||
filename = os.path.join('tests', str(uuid.uuid4()) + '.test')
|
|
||||||
open(filename, "w").write('test')
|
|
||||||
assert open(filename, "r").read() == 'test'
|
|
||||||
if exists(filename):
|
|
||||||
remove(filename)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
test()
|
|
||||||
|
|||||||
@@ -1870,3 +1870,46 @@ class WSGIWorker(Worker):
|
|||||||
sock_file.close()
|
sock_file.close()
|
||||||
|
|
||||||
# Monolithic build...end of module: rocket/methods/wsgi.py
|
# Monolithic build...end of module: rocket/methods/wsgi.py
|
||||||
|
def demo_app(environ, start_response):
|
||||||
|
global static_folder
|
||||||
|
import os
|
||||||
|
types = {'htm': 'text/html','html': 'text/html','gif': 'image/gif',
|
||||||
|
'jpg': 'image/jpeg','png': 'image/png','pdf': 'applications/pdf'}
|
||||||
|
if static_folder:
|
||||||
|
if not static_folder.startswith('/'):
|
||||||
|
static_folder = os.path.join(os.getcwd(),static_folder)
|
||||||
|
path = os.path.join(static_folder, environ['PATH_INFO'][1:] or 'index.html')
|
||||||
|
type = types.get(path.split('.')[-1],'text')
|
||||||
|
if os.path.exists(path):
|
||||||
|
try:
|
||||||
|
data = open(path,'rb').read()
|
||||||
|
start_response('200 OK', [('Content-Type', type)])
|
||||||
|
except IOError:
|
||||||
|
start_response('404 NOT FOUND', [])
|
||||||
|
data = '404 NOT FOUND'
|
||||||
|
else:
|
||||||
|
start_response('500 INTERNAL SERVER ERROR', [])
|
||||||
|
data = '500 INTERNAL SERVER ERROR'
|
||||||
|
else:
|
||||||
|
start_response('200 OK', [('Content-Type', 'text/html')])
|
||||||
|
data = '<html><body><h1>Hello from Rocket Web Server</h1></body></html>'
|
||||||
|
return [data]
|
||||||
|
|
||||||
|
def demo():
|
||||||
|
from optparse import OptionParser
|
||||||
|
parser = OptionParser()
|
||||||
|
parser.add_option("-i", "--ip", dest="ip",default="127.0.0.1",
|
||||||
|
help="ip address of the network interface")
|
||||||
|
parser.add_option("-p", "--port", dest="port",default="8000",
|
||||||
|
help="post where to run web server")
|
||||||
|
parser.add_option("-s", "--static", dest="static",default=None,
|
||||||
|
help="folder containing static files")
|
||||||
|
(options, args) = parser.parse_args()
|
||||||
|
global static_folder
|
||||||
|
static_folder = options.static
|
||||||
|
print 'Rocket running on %s:%s' % (options.ip, options.port)
|
||||||
|
r=Rocket((options.ip,int(options.port)),'wsgi', {'wsgi_app':demo_app})
|
||||||
|
r.start()
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
demo()
|
||||||
|
|||||||
+47
-38
@@ -215,7 +215,7 @@ class JobGraph(object):
|
|||||||
nested_dict = dict(
|
nested_dict = dict(
|
||||||
(item, (dep - ordered)) for item, dep in nested_dict.items()
|
(item, (dep - ordered)) for item, dep in nested_dict.items()
|
||||||
if item not in ordered
|
if item not in ordered
|
||||||
)
|
)
|
||||||
assert not nested_dict, "A cyclic dependency exists amongst %r" % nested_dict
|
assert not nested_dict, "A cyclic dependency exists amongst %r" % nested_dict
|
||||||
db.commit()
|
db.commit()
|
||||||
return rtn
|
return rtn
|
||||||
@@ -297,7 +297,7 @@ def executor(queue, task, out):
|
|||||||
f = task.function
|
f = task.function
|
||||||
functions = current._scheduler.tasks
|
functions = current._scheduler.tasks
|
||||||
if not functions:
|
if not functions:
|
||||||
#look into env
|
# look into env
|
||||||
_function = _env.get(f)
|
_function = _env.get(f)
|
||||||
else:
|
else:
|
||||||
_function = functions.get(f)
|
_function = functions.get(f)
|
||||||
@@ -314,7 +314,7 @@ def executor(queue, task, out):
|
|||||||
vars = loads(task.vars, object_hook=_decode_dict)
|
vars = loads(task.vars, object_hook=_decode_dict)
|
||||||
result = dumps(_function(*args, **vars))
|
result = dumps(_function(*args, **vars))
|
||||||
else:
|
else:
|
||||||
### for testing purpose only
|
# for testing purpose only
|
||||||
result = eval(task.function)(
|
result = eval(task.function)(
|
||||||
*loads(task.args, object_hook=_decode_dict),
|
*loads(task.args, object_hook=_decode_dict),
|
||||||
**loads(task.vars, object_hook=_decode_dict))
|
**loads(task.vars, object_hook=_decode_dict))
|
||||||
@@ -391,7 +391,6 @@ class MetaScheduler(threading.Thread):
|
|||||||
except:
|
except:
|
||||||
p.terminate()
|
p.terminate()
|
||||||
p.join()
|
p.join()
|
||||||
self.have_heartbeat = False
|
|
||||||
logger.debug(' task stopped by general exception')
|
logger.debug(' task stopped by general exception')
|
||||||
tr = TaskReport(STOPPED)
|
tr = TaskReport(STOPPED)
|
||||||
else:
|
else:
|
||||||
@@ -406,7 +405,6 @@ class MetaScheduler(threading.Thread):
|
|||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
tr = TaskReport(TIMEOUT)
|
tr = TaskReport(TIMEOUT)
|
||||||
elif queue.empty():
|
elif queue.empty():
|
||||||
self.have_heartbeat = False
|
|
||||||
logger.debug(' task stopped')
|
logger.debug(' task stopped')
|
||||||
tr = TaskReport(STOPPED)
|
tr = TaskReport(STOPPED)
|
||||||
else:
|
else:
|
||||||
@@ -665,7 +663,7 @@ class Scheduler(MetaScheduler):
|
|||||||
Field('traceback', 'text'),
|
Field('traceback', 'text'),
|
||||||
Field('worker_name', default=self.worker_name),
|
Field('worker_name', default=self.worker_name),
|
||||||
migrate=self.__get_migrate('scheduler_run', migrate)
|
migrate=self.__get_migrate('scheduler_run', migrate)
|
||||||
)
|
)
|
||||||
|
|
||||||
db.define_table(
|
db.define_table(
|
||||||
'scheduler_worker',
|
'scheduler_worker',
|
||||||
@@ -677,23 +675,30 @@ class Scheduler(MetaScheduler):
|
|||||||
Field('group_names', 'list:string', default=self.group_names),
|
Field('group_names', 'list:string', default=self.group_names),
|
||||||
Field('worker_stats', 'json'),
|
Field('worker_stats', 'json'),
|
||||||
migrate=self.__get_migrate('scheduler_worker', migrate)
|
migrate=self.__get_migrate('scheduler_worker', migrate)
|
||||||
)
|
)
|
||||||
|
|
||||||
db.define_table(
|
db.define_table(
|
||||||
'scheduler_task_deps',
|
'scheduler_task_deps',
|
||||||
Field('job_name', default='job_0'),
|
Field('job_name', default='job_0'),
|
||||||
Field('task_parent', 'integer',
|
Field('task_parent', 'integer',
|
||||||
requires=IS_IN_DB(db, 'scheduler_task.id',
|
requires=IS_IN_DB(db, 'scheduler_task.id', '%(task_name)s')
|
||||||
'%(task_name)s')
|
),
|
||||||
),
|
|
||||||
Field('task_child', 'reference scheduler_task'),
|
Field('task_child', 'reference scheduler_task'),
|
||||||
Field('can_visit', 'boolean', default=False),
|
Field('can_visit', 'boolean', default=False),
|
||||||
migrate=self.__get_migrate('scheduler_task_deps', migrate)
|
migrate=self.__get_migrate('scheduler_task_deps', migrate)
|
||||||
)
|
)
|
||||||
|
|
||||||
if migrate is not False:
|
if migrate is not False:
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def total_seconds(td):
|
||||||
|
# backport for py2.6
|
||||||
|
if hasattr(td, 'total_seconds'):
|
||||||
|
return td.total_seconds()
|
||||||
|
else:
|
||||||
|
return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10.0 ** 6
|
||||||
|
|
||||||
def loop(self, worker_name=None):
|
def loop(self, worker_name=None):
|
||||||
"""Main loop
|
"""Main loop
|
||||||
|
|
||||||
@@ -718,7 +723,7 @@ class Scheduler(MetaScheduler):
|
|||||||
while True and self.have_heartbeat:
|
while True and self.have_heartbeat:
|
||||||
if self.w_stats.status == DISABLED:
|
if self.w_stats.status == DISABLED:
|
||||||
logger.debug('Someone stopped me, sleeping until better'
|
logger.debug('Someone stopped me, sleeping until better'
|
||||||
' times come (%s)', self.w_stats.sleep)
|
' times come (%s)', self.w_stats.sleep)
|
||||||
self.sleep()
|
self.sleep()
|
||||||
continue
|
continue
|
||||||
logger.debug('looping...')
|
logger.debug('looping...')
|
||||||
@@ -735,7 +740,8 @@ class Scheduler(MetaScheduler):
|
|||||||
logger.debug('sleeping...')
|
logger.debug('sleeping...')
|
||||||
if self.max_empty_runs != 0:
|
if self.max_empty_runs != 0:
|
||||||
logger.debug('empty runs %s/%s',
|
logger.debug('empty runs %s/%s',
|
||||||
self.w_stats.empty_runs, self.max_empty_runs)
|
self.w_stats.empty_runs,
|
||||||
|
self.max_empty_runs)
|
||||||
if self.w_stats.empty_runs >= self.max_empty_runs:
|
if self.w_stats.empty_runs >= self.max_empty_runs:
|
||||||
logger.info(
|
logger.info(
|
||||||
'empty runs limit reached, killing myself')
|
'empty runs limit reached, killing myself')
|
||||||
@@ -791,15 +797,15 @@ class Scheduler(MetaScheduler):
|
|||||||
now = self.now()
|
now = self.now()
|
||||||
st = self.db.scheduler_task
|
st = self.db.scheduler_task
|
||||||
if self.is_a_ticker and self.do_assign_tasks:
|
if self.is_a_ticker and self.do_assign_tasks:
|
||||||
#I'm a ticker, and 5 loops passed without reassigning tasks,
|
# I'm a ticker, and 5 loops passed without reassigning tasks,
|
||||||
#let's do that and loop again
|
# let's do that and loop again
|
||||||
self.wrapped_assign_tasks(db)
|
self.wrapped_assign_tasks(db)
|
||||||
return None
|
return None
|
||||||
# ready to process something
|
# ready to process something
|
||||||
grabbed = db(
|
grabbed = db(
|
||||||
(st.assigned_worker_name == self.worker_name) &
|
(st.assigned_worker_name == self.worker_name) &
|
||||||
(st.status == ASSIGNED)
|
(st.status == ASSIGNED)
|
||||||
)
|
)
|
||||||
|
|
||||||
task = grabbed.select(limitby=(0, 1), orderby=st.next_run_time).first()
|
task = grabbed.select(limitby=(0, 1), orderby=st.next_run_time).first()
|
||||||
if task:
|
if task:
|
||||||
@@ -819,11 +825,15 @@ class Scheduler(MetaScheduler):
|
|||||||
if not task.prevent_drift:
|
if not task.prevent_drift:
|
||||||
next_run_time = task.last_run_time + datetime.timedelta(
|
next_run_time = task.last_run_time + datetime.timedelta(
|
||||||
seconds=task.period
|
seconds=task.period
|
||||||
)
|
|
||||||
else:
|
|
||||||
next_run_time = task.start_time + datetime.timedelta(
|
|
||||||
seconds=task.period * times_run
|
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
# calc next_run_time based on available slots
|
||||||
|
# see #1191
|
||||||
|
next_run_time = task.start_time
|
||||||
|
secondspassed = self.total_seconds(now - next_run_time)
|
||||||
|
steps = secondspassed // task.period + 1
|
||||||
|
next_run_time += datetime.timedelta(seconds=task.period * steps)
|
||||||
|
|
||||||
if times_run < task.repeats or task.repeats == 0:
|
if times_run < task.repeats or task.repeats == 0:
|
||||||
# need to run (repeating task)
|
# need to run (repeating task)
|
||||||
run_again = True
|
run_again = True
|
||||||
@@ -845,7 +855,7 @@ class Scheduler(MetaScheduler):
|
|||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
db.rollback()
|
db.rollback()
|
||||||
logger.info('new task %(id)s "%(task_name)s"'
|
logger.info('new task %(id)s "%(task_name)s"'
|
||||||
' %(application_name)s.%(function_name)s' % task)
|
' %(application_name)s.%(function_name)s' % task)
|
||||||
return Task(
|
return Task(
|
||||||
app=task.application_name,
|
app=task.application_name,
|
||||||
function=task.function_name,
|
function=task.function_name,
|
||||||
@@ -922,13 +932,13 @@ class Scheduler(MetaScheduler):
|
|||||||
else:
|
else:
|
||||||
st_mapping = {'FAILED': 'FAILED',
|
st_mapping = {'FAILED': 'FAILED',
|
||||||
'TIMEOUT': 'TIMEOUT',
|
'TIMEOUT': 'TIMEOUT',
|
||||||
'STOPPED': 'QUEUED'}[task_report.status]
|
'STOPPED': 'FAILED'}[task_report.status]
|
||||||
status = (task.retry_failed
|
status = (task.retry_failed
|
||||||
and task.times_failed < task.retry_failed
|
and task.times_failed < task.retry_failed
|
||||||
and QUEUED or task.retry_failed == -1
|
and QUEUED or task.retry_failed == -1
|
||||||
and QUEUED or st_mapping)
|
and QUEUED or st_mapping)
|
||||||
db(st.id == task.task_id).update(
|
db(st.id == task.task_id).update(
|
||||||
times_failed=db.scheduler_task.times_failed + 1,
|
times_failed=st.times_failed + 1,
|
||||||
next_run_time=task.next_run_time,
|
next_run_time=task.next_run_time,
|
||||||
status=status
|
status=status
|
||||||
)
|
)
|
||||||
@@ -982,7 +992,7 @@ class Scheduler(MetaScheduler):
|
|||||||
# keep sleeping
|
# keep sleeping
|
||||||
self.w_stats.status = DISABLED
|
self.w_stats.status = DISABLED
|
||||||
logger.debug('........recording heartbeat (%s)',
|
logger.debug('........recording heartbeat (%s)',
|
||||||
self.w_stats.status)
|
self.w_stats.status)
|
||||||
db(sw.worker_name == self.worker_name).update(
|
db(sw.worker_name == self.worker_name).update(
|
||||||
last_heartbeat=now,
|
last_heartbeat=now,
|
||||||
worker_stats=self.w_stats)
|
worker_stats=self.w_stats)
|
||||||
@@ -999,7 +1009,7 @@ class Scheduler(MetaScheduler):
|
|||||||
logger.info('Asked to kill the current task')
|
logger.info('Asked to kill the current task')
|
||||||
self.terminate_process()
|
self.terminate_process()
|
||||||
logger.debug('........recording heartbeat (%s)',
|
logger.debug('........recording heartbeat (%s)',
|
||||||
self.w_stats.status)
|
self.w_stats.status)
|
||||||
db(sw.worker_name == self.worker_name).update(
|
db(sw.worker_name == self.worker_name).update(
|
||||||
last_heartbeat=now, status=ACTIVE,
|
last_heartbeat=now, status=ACTIVE,
|
||||||
worker_stats=self.w_stats)
|
worker_stats=self.w_stats)
|
||||||
@@ -1025,7 +1035,7 @@ class Scheduler(MetaScheduler):
|
|||||||
db(
|
db(
|
||||||
(st.assigned_worker_name.belongs(dead_workers_name)) &
|
(st.assigned_worker_name.belongs(dead_workers_name)) &
|
||||||
(st.status == RUNNING)
|
(st.status == RUNNING)
|
||||||
).update(assigned_worker_name='', status=QUEUED)
|
).update(assigned_worker_name='', status=QUEUED)
|
||||||
dead_workers.delete()
|
dead_workers.delete()
|
||||||
try:
|
try:
|
||||||
self.is_a_ticker = self.being_a_ticker()
|
self.is_a_ticker = self.being_a_ticker()
|
||||||
@@ -1110,20 +1120,20 @@ class Scheduler(MetaScheduler):
|
|||||||
(sd.can_visit == False) &
|
(sd.can_visit == False) &
|
||||||
(~sd.task_child.belongs(
|
(~sd.task_child.belongs(
|
||||||
db(sd.can_visit == False)._select(sd.task_parent)
|
db(sd.can_visit == False)._select(sd.task_parent)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)._select(sd.task_child)
|
)
|
||||||
|
)._select(sd.task_child)
|
||||||
no_deps = db(
|
no_deps = db(
|
||||||
(st.status.belongs((QUEUED, ASSIGNED))) &
|
(st.status.belongs((QUEUED, ASSIGNED))) &
|
||||||
(
|
(
|
||||||
(sd.id == None) | (st.id.belongs(deps_with_no_deps))
|
(sd.id == None) | (st.id.belongs(deps_with_no_deps))
|
||||||
|
|
||||||
)
|
)
|
||||||
)._select(st.id, distinct=True, left=sd.on(
|
)._select(st.id, distinct=True, left=sd.on(
|
||||||
(st.id == sd.task_parent) &
|
(st.id == sd.task_parent) &
|
||||||
(sd.can_visit == False)
|
(sd.can_visit == False)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
all_available = db(
|
all_available = db(
|
||||||
(st.status.belongs((QUEUED, ASSIGNED))) &
|
(st.status.belongs((QUEUED, ASSIGNED))) &
|
||||||
@@ -1135,7 +1145,6 @@ class Scheduler(MetaScheduler):
|
|||||||
(st.id.belongs(no_deps))
|
(st.id.belongs(no_deps))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
limit = len(all_workers) * (50 / (len(wkgroups) or 1))
|
limit = len(all_workers) * (50 / (len(wkgroups) or 1))
|
||||||
# if there are a moltitude of tasks, let's figure out a maximum of
|
# if there are a moltitude of tasks, let's figure out a maximum of
|
||||||
# tasks per worker. This can be further tuned with some added
|
# tasks per worker. This can be further tuned with some added
|
||||||
@@ -1179,7 +1188,7 @@ class Scheduler(MetaScheduler):
|
|||||||
db(
|
db(
|
||||||
(st.id == task.id) &
|
(st.id == task.id) &
|
||||||
(st.status.belongs((QUEUED, ASSIGNED)))
|
(st.status.belongs((QUEUED, ASSIGNED)))
|
||||||
).update(**d)
|
).update(**d)
|
||||||
wkgroups[gname]['workers'][myw]['c'] += 1
|
wkgroups[gname]['workers'][myw]['c'] += 1
|
||||||
db.commit()
|
db.commit()
|
||||||
# I didn't report tasks but I'm working nonetheless!!!!
|
# I didn't report tasks but I'm working nonetheless!!!!
|
||||||
@@ -1217,7 +1226,7 @@ class Scheduler(MetaScheduler):
|
|||||||
self.db(
|
self.db(
|
||||||
(ws.group_names.contains(group)) &
|
(ws.group_names.contains(group)) &
|
||||||
(~ws.status.belongs(exclusion))
|
(~ws.status.belongs(exclusion))
|
||||||
).update(status=action)
|
).update(status=action)
|
||||||
else:
|
else:
|
||||||
for group in group_names:
|
for group in group_names:
|
||||||
workers = self.db((ws.group_names.contains(group)) &
|
workers = self.db((ws.group_names.contains(group)) &
|
||||||
@@ -1302,7 +1311,7 @@ class Scheduler(MetaScheduler):
|
|||||||
if immediate:
|
if immediate:
|
||||||
self.db(
|
self.db(
|
||||||
(self.db.scheduler_worker.is_ticker == True)
|
(self.db.scheduler_worker.is_ticker == True)
|
||||||
).update(status=PICK)
|
).update(status=PICK)
|
||||||
else:
|
else:
|
||||||
rtn.uuid = None
|
rtn.uuid = None
|
||||||
return rtn
|
return rtn
|
||||||
@@ -1352,7 +1361,7 @@ class Scheduler(MetaScheduler):
|
|||||||
**dict(orderby=orderby,
|
**dict(orderby=orderby,
|
||||||
left=left,
|
left=left,
|
||||||
limitby=(0, 1))
|
limitby=(0, 1))
|
||||||
).first()
|
).first()
|
||||||
if row and output:
|
if row and output:
|
||||||
row.result = row.scheduler_run.run_result and \
|
row.result = row.scheduler_run.run_result and \
|
||||||
loads(row.scheduler_run.run_result,
|
loads(row.scheduler_run.run_result,
|
||||||
|
|||||||
+35
-8
@@ -27,7 +27,7 @@ from gluon.html import FORM, INPUT, LABEL, OPTION, SELECT, COL, COLGROUP
|
|||||||
from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, SCRIPT
|
from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, SCRIPT
|
||||||
from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
|
from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
|
||||||
from pydal.base import DEFAULT
|
from pydal.base import DEFAULT
|
||||||
from pydal.objects import Table, Row, Expression, Field
|
from pydal.objects import Table, Row, Expression, Field, Set
|
||||||
from pydal.adapters.base import CALLABLETYPES
|
from pydal.adapters.base import CALLABLETYPES
|
||||||
from pydal.helpers.methods import smart_query, bar_encode, _repr_ref
|
from pydal.helpers.methods import smart_query, bar_encode, _repr_ref
|
||||||
from pydal.helpers.classes import Reference, SQLCustomType
|
from pydal.helpers.classes import Reference, SQLCustomType
|
||||||
@@ -677,7 +677,23 @@ class AutocompleteWidget(object):
|
|||||||
def callback(self):
|
def callback(self):
|
||||||
if self.keyword in self.request.vars:
|
if self.keyword in self.request.vars:
|
||||||
field = self.fields[0]
|
field = self.fields[0]
|
||||||
if settings and settings.global_settings.web2py_runtime_gae:
|
if type(field) is FieldVirtual:
|
||||||
|
records = []
|
||||||
|
table_rows = self.db(self.db[field.tablename]).select(orderby=self.orderby)
|
||||||
|
count = 0
|
||||||
|
for row in table_rows:
|
||||||
|
if self.at_beginning:
|
||||||
|
if row[field.name].lower().startswith(self.request.vars[self.keyword]):
|
||||||
|
count += 1
|
||||||
|
records.append(row)
|
||||||
|
else:
|
||||||
|
if self.request.vars[self.keyword] in row[field.name].lower():
|
||||||
|
count += 1
|
||||||
|
records.append(row)
|
||||||
|
if count == 10:
|
||||||
|
break
|
||||||
|
rows = Rows(self.db, records, table_rows.colnames, compact=table_rows.compact)
|
||||||
|
elif settings and settings.global_settings.web2py_runtime_gae:
|
||||||
rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *(self.fields+self.help_fields))
|
rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *(self.fields+self.help_fields))
|
||||||
elif self.at_beginning:
|
elif self.at_beginning:
|
||||||
rows = self.db(field.like(self.request.vars[self.keyword] + '%', case_sensitive=False)).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *(self.fields+self.help_fields))
|
rows = self.db(field.like(self.request.vars[self.keyword] + '%', case_sensitive=False)).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *(self.fields+self.help_fields))
|
||||||
@@ -725,8 +741,16 @@ class AutocompleteWidget(object):
|
|||||||
del attr['requires']
|
del attr['requires']
|
||||||
attr['_name'] = key2
|
attr['_name'] = key2
|
||||||
value = attr['value']
|
value = attr['value']
|
||||||
record = self.db(
|
if type(self.fields[0]) is FieldVirtual:
|
||||||
self.fields[1] == value).select(self.fields[0]).first()
|
record = None
|
||||||
|
table_rows = self.db(self.db[self.fields[0].tablename]).select(orderby=self.orderby)
|
||||||
|
for row in table_rows:
|
||||||
|
if row.id == value:
|
||||||
|
record = row
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
record = self.db(
|
||||||
|
self.fields[1] == value).select(self.fields[0]).first()
|
||||||
attr['value'] = record and record[self.fields[0].name]
|
attr['value'] = record and record[self.fields[0].name]
|
||||||
attr['_onblur'] = "jQuery('#%(div_id)s').delay(1000).fadeOut('slow');" % \
|
attr['_onblur'] = "jQuery('#%(div_id)s').delay(1000).fadeOut('slow');" % \
|
||||||
dict(div_id=div_id, u='F' + self.keyword)
|
dict(div_id=div_id, u='F' + self.keyword)
|
||||||
@@ -912,7 +936,7 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
|
|||||||
# wrappers
|
# wrappers
|
||||||
_help = SPAN(help, _class='help-block')
|
_help = SPAN(help, _class='help-block')
|
||||||
# embed _help into _controls
|
# embed _help into _controls
|
||||||
_controls = DIV(controls, _help, _class=col_class)
|
_controls = DIV(controls, _help, _class="%s" % (col_class))
|
||||||
if isinstance(controls, INPUT):
|
if isinstance(controls, INPUT):
|
||||||
if controls['_type'] == 'submit':
|
if controls['_type'] == 'submit':
|
||||||
controls.add_class('btn btn-primary')
|
controls.add_class('btn btn-primary')
|
||||||
@@ -938,8 +962,6 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
|
|||||||
elif isinstance(controls, UL):
|
elif isinstance(controls, UL):
|
||||||
for e in controls.elements("input"):
|
for e in controls.elements("input"):
|
||||||
e.add_class('form-control')
|
e.add_class('form-control')
|
||||||
elif controls is None or isinstance(controls, basestring):
|
|
||||||
_controls = P(controls, _class="form-control-static %s" % col_class)
|
|
||||||
if isinstance(label, LABEL):
|
if isinstance(label, LABEL):
|
||||||
label['_class'] = add_class(label.get('_class'),'control-label %s' % label_col_class)
|
label['_class'] = add_class(label.get('_class'),'control-label %s' % label_col_class)
|
||||||
|
|
||||||
@@ -2014,6 +2036,8 @@ class SQLFORM(FORM):
|
|||||||
use_cursor=False):
|
use_cursor=False):
|
||||||
|
|
||||||
formstyle = formstyle or current.response.formstyle
|
formstyle = formstyle or current.response.formstyle
|
||||||
|
if isinstance(query, Set):
|
||||||
|
query = query.query
|
||||||
|
|
||||||
# jQuery UI ThemeRoller classes (empty if ui is disabled)
|
# jQuery UI ThemeRoller classes (empty if ui is disabled)
|
||||||
if ui == 'jquery-ui':
|
if ui == 'jquery-ui':
|
||||||
@@ -2180,12 +2204,15 @@ class SQLFORM(FORM):
|
|||||||
|
|
||||||
dbset = db(query, ignore_common_filters=ignore_common_filters)
|
dbset = db(query, ignore_common_filters=ignore_common_filters)
|
||||||
tablenames = db._adapter.tables(dbset.query)
|
tablenames = db._adapter.tables(dbset.query)
|
||||||
|
print dbset.query
|
||||||
|
print tablenames
|
||||||
if left is not None:
|
if left is not None:
|
||||||
if not isinstance(left, (list, tuple)):
|
if not isinstance(left, (list, tuple)):
|
||||||
left = [left]
|
left = [left]
|
||||||
for join in left:
|
for join in left:
|
||||||
tablenames += db._adapter.tables(join)
|
tablenames += db._adapter.tables(join)
|
||||||
tables = [db[tablename] for tablename in tablenames]
|
tables = [db[tablename] for tablename in tablenames]
|
||||||
|
print tables
|
||||||
if fields:
|
if fields:
|
||||||
# add missing tablename to virtual fields
|
# add missing tablename to virtual fields
|
||||||
for table in tables:
|
for table in tables:
|
||||||
@@ -2337,7 +2364,7 @@ class SQLFORM(FORM):
|
|||||||
if deletable(record):
|
if deletable(record):
|
||||||
if ondelete:
|
if ondelete:
|
||||||
ondelete(table, request.args[-1])
|
ondelete(table, request.args[-1])
|
||||||
record.delete_record()
|
db(table[table._id.name] == request.args[-1]).delete()
|
||||||
if request.ajax:
|
if request.ajax:
|
||||||
# this means javascript is enabled, so we don't need to do
|
# this means javascript is enabled, so we don't need to do
|
||||||
# a redirect
|
# a redirect
|
||||||
|
|||||||
+4
-4
@@ -25,7 +25,6 @@ regex_stop_range = re.compile('(?<=\-)\d+')
|
|||||||
|
|
||||||
DEFAULT_CHUNK_SIZE = 64 * 1024
|
DEFAULT_CHUNK_SIZE = 64 * 1024
|
||||||
|
|
||||||
|
|
||||||
def streamer(stream, chunk_size=DEFAULT_CHUNK_SIZE, bytes=None):
|
def streamer(stream, chunk_size=DEFAULT_CHUNK_SIZE, bytes=None):
|
||||||
offset = 0
|
offset = 0
|
||||||
while bytes is None or offset < bytes:
|
while bytes is None or offset < bytes:
|
||||||
@@ -51,11 +50,12 @@ def stream_file_or_304_or_206(
|
|||||||
status=200,
|
status=200,
|
||||||
error_message=None
|
error_message=None
|
||||||
):
|
):
|
||||||
if error_message is None:
|
# FIX THIS
|
||||||
error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
|
# if error_message is None:
|
||||||
|
# error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
|
||||||
try:
|
try:
|
||||||
open = file # this makes no sense but without it GAE cannot open files
|
open = file # this makes no sense but without it GAE cannot open files
|
||||||
fp = open(static_file)
|
fp = open(static_file,'rb')
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
if e[0] == errno.EISDIR:
|
if e[0] == errno.EISDIR:
|
||||||
raise HTTP(403, error_message, web2py_error='file is a directory')
|
raise HTTP(403, error_message, web2py_error='file is a directory')
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import sys
|
|||||||
from test_http import *
|
from test_http import *
|
||||||
from test_cache import *
|
from test_cache import *
|
||||||
from test_contenttype import *
|
from test_contenttype import *
|
||||||
|
from test_compileapp import *
|
||||||
from test_fileutils import *
|
from test_fileutils import *
|
||||||
from test_globals import *
|
from test_globals import *
|
||||||
from test_html import *
|
from test_html import *
|
||||||
from test_is_url import *
|
from test_is_url import *
|
||||||
from test_languages import *
|
from test_languages import *
|
||||||
from test_router import *
|
from test_router import *
|
||||||
|
from test_recfile import *
|
||||||
from test_routes import *
|
from test_routes import *
|
||||||
from test_storage import *
|
from test_storage import *
|
||||||
from test_serializers import *
|
from test_serializers import *
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
""" Unit tests for utils.py """
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from fix_path import fix_sys_path
|
||||||
|
|
||||||
|
fix_sys_path(__file__)
|
||||||
|
|
||||||
|
from compileapp import compile_application, remove_compiled_application
|
||||||
|
from gluon.fileutils import w2p_pack, w2p_unpack
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class TestPack(unittest.TestCase):
|
||||||
|
""" Tests the compileapp.py module """
|
||||||
|
|
||||||
|
def test_compile(self):
|
||||||
|
#apps = ['welcome', 'admin', 'examples']
|
||||||
|
apps = ['welcome']
|
||||||
|
for appname in apps:
|
||||||
|
appname_path = os.path.join(os.getcwd(), 'applications', appname)
|
||||||
|
compile_application(appname_path)
|
||||||
|
remove_compiled_application(appname_path)
|
||||||
|
test_path = os.path.join(os.getcwd(), "%s.w2p" % appname)
|
||||||
|
unpack_path = os.path.join(os.getcwd(), 'unpack', appname)
|
||||||
|
w2p_pack(test_path, appname_path, compiled=True, filenames=None)
|
||||||
|
w2p_pack(test_path, appname_path, compiled=False, filenames=None)
|
||||||
|
w2p_unpack(test_path, unpack_path)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Unit tests for gluon.recfile
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import uuid
|
||||||
|
from fix_path import fix_sys_path
|
||||||
|
|
||||||
|
fix_sys_path(__file__)
|
||||||
|
|
||||||
|
from gluon import recfile
|
||||||
|
|
||||||
|
|
||||||
|
class TestRecfile(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
os.mkdir('tests')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
shutil.rmtree('tests')
|
||||||
|
|
||||||
|
def testgeneration(self):
|
||||||
|
for k in range(20):
|
||||||
|
teststring = 'test%s' % k
|
||||||
|
filename = os.path.join('tests', str(uuid.uuid4()) + '.test')
|
||||||
|
with recfile.open(filename, "w") as g:
|
||||||
|
g.write(teststring)
|
||||||
|
self.assertEqual(recfile.open(filename, "r").read(), teststring)
|
||||||
|
is_there = recfile.exists(filename)
|
||||||
|
self.assertTrue(is_there)
|
||||||
|
recfile.remove(filename)
|
||||||
|
is_there = recfile.exists(filename)
|
||||||
|
self.assertFalse(is_there)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
+28
-19
@@ -13,7 +13,7 @@ from utils import compare
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
from hashlib import md5, sha1, sha224, sha256, sha384, sha512
|
from hashlib import md5, sha1, sha224, sha256, sha384, sha512
|
||||||
from utils import simple_hash, get_digest
|
from utils import simple_hash, get_digest, secure_dumps, secure_loads
|
||||||
|
|
||||||
|
|
||||||
class TestUtils(unittest.TestCase):
|
class TestUtils(unittest.TestCase):
|
||||||
@@ -65,27 +65,36 @@ class TestUtils(unittest.TestCase):
|
|||||||
self.assertEqual(data_sha512, 'fa3237f594743e1d7b6c800bb134b3255cf4a98ab8b01e2ec23256328c9f8059'
|
self.assertEqual(data_sha512, 'fa3237f594743e1d7b6c800bb134b3255cf4a98ab8b01e2ec23256328c9f8059'
|
||||||
'64fdef25a038d6cc3fda1b2fb45d66461eeed5c4669e506ec8bdfee71348db7e')
|
'64fdef25a038d6cc3fda1b2fb45d66461eeed5c4669e506ec8bdfee71348db7e')
|
||||||
|
|
||||||
|
def test_secure_dumps_and_loads(self):
|
||||||
|
""" Tests secure_dumps and secure_loads"""
|
||||||
|
testobj = {'a': 1, 'b': 2}
|
||||||
|
testkey = 'mysecret'
|
||||||
|
secured = secure_dumps(testobj, testkey)
|
||||||
|
original = secure_loads(secured, testkey)
|
||||||
|
self.assertEqual(testobj, original)
|
||||||
|
self.assertTrue(isinstance(secured, basestring))
|
||||||
|
self.assertTrue(':' in secured)
|
||||||
|
|
||||||
|
large_testobj = [x for x in range(1000)]
|
||||||
|
secured_comp = secure_dumps(large_testobj, testkey, compression_level=9)
|
||||||
|
original_comp = secure_loads(secured_comp, testkey, compression_level=9)
|
||||||
|
self.assertEqual(large_testobj, original_comp)
|
||||||
|
secured = secure_dumps(large_testobj, testkey)
|
||||||
|
self.assertTrue(len(secured_comp) < len(secured))
|
||||||
|
|
||||||
class TestPack(unittest.TestCase):
|
testhash = 'myhash'
|
||||||
""" Tests the compileapp.py module """
|
secured = secure_dumps(testobj, testkey, testhash)
|
||||||
|
original = secure_loads(secured, testkey, testhash)
|
||||||
|
self.assertEqual(testobj, original)
|
||||||
|
|
||||||
def test_compile(self):
|
wrong1 = secure_loads(secured, testkey, 'wronghash')
|
||||||
from compileapp import compile_application, remove_compiled_application
|
self.assertEqual(wrong1, None)
|
||||||
from gluon.fileutils import w2p_pack, w2p_unpack
|
wrong2 = secure_loads(secured, 'wrongkey', testhash)
|
||||||
import os
|
self.assertEqual(wrong2, None)
|
||||||
#apps = ['welcome', 'admin', 'examples']
|
wrong3 = secure_loads(secured, 'wrongkey', 'wronghash')
|
||||||
apps = ['welcome']
|
self.assertEqual(wrong3, None)
|
||||||
for appname in apps:
|
wrong4 = secure_loads('abc', 'a', 'b')
|
||||||
appname_path = os.path.join(os.getcwd(), 'applications', appname)
|
self.assertEqual(wrong4, None)
|
||||||
compile_application(appname_path)
|
|
||||||
remove_compiled_application(appname_path)
|
|
||||||
test_path = os.path.join(os.getcwd(), "%s.w2p" % appname)
|
|
||||||
unpack_path = os.path.join(os.getcwd(), 'unpack', appname)
|
|
||||||
w2p_pack(test_path, appname_path, compiled=True, filenames=None)
|
|
||||||
w2p_pack(test_path, appname_path, compiled=False, filenames=None)
|
|
||||||
w2p_unpack(test_path, unpack_path)
|
|
||||||
return
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -618,6 +618,9 @@ class TestValidators(unittest.TestCase):
|
|||||||
self.assertEqual(rtn, (u'hell', None))
|
self.assertEqual(rtn, (u'hell', None))
|
||||||
rtn = IS_MATCH('hell', is_unicode=True)(u'hell')
|
rtn = IS_MATCH('hell', is_unicode=True)(u'hell')
|
||||||
self.assertEqual(rtn, (u'hell', None))
|
self.assertEqual(rtn, (u'hell', None))
|
||||||
|
# regr test for #1044
|
||||||
|
rtn = IS_MATCH('hello')(u'\xff')
|
||||||
|
self.assertEqual(rtn, (u'\xff', 'Invalid expression'))
|
||||||
|
|
||||||
|
|
||||||
def test_IS_EQUAL_TO(self):
|
def test_IS_EQUAL_TO(self):
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
+52
-57
@@ -34,13 +34,12 @@ import email.utils
|
|||||||
import random
|
import random
|
||||||
import hmac
|
import hmac
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
|
||||||
from email import MIMEBase, MIMEMultipart, MIMEText, Encoders, Header, message_from_string, Charset
|
from email import MIMEBase, MIMEMultipart, MIMEText, Encoders, Header, message_from_string, Charset
|
||||||
|
|
||||||
from gluon.serializers import json_parser
|
from gluon.serializers import json_parser
|
||||||
from gluon.contenttype import contenttype
|
from gluon.contenttype import contenttype
|
||||||
from gluon.storage import Storage, StorageList, Settings, Messages
|
from gluon.storage import Storage, StorageList, Settings, Messages
|
||||||
from gluon.utils import web2py_uuid
|
from gluon.utils import web2py_uuid, compare
|
||||||
from gluon.fileutils import read_file, check_credentials
|
from gluon.fileutils import read_file, check_credentials
|
||||||
from gluon import *
|
from gluon import *
|
||||||
from gluon.contrib.autolinks import expand_one
|
from gluon.contrib.autolinks import expand_one
|
||||||
@@ -53,17 +52,6 @@ import gluon.serializers as serializers
|
|||||||
Table = DAL.Table
|
Table = DAL.Table
|
||||||
Field = DAL.Field
|
Field = DAL.Field
|
||||||
|
|
||||||
try:
|
|
||||||
# try stdlib (Python 2.6)
|
|
||||||
import json as json_parser
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
# try external module
|
|
||||||
import simplejson as json_parser
|
|
||||||
except:
|
|
||||||
# fallback to pure-Python module
|
|
||||||
import gluon.contrib.simplejson as json_parser
|
|
||||||
|
|
||||||
__all__ = ['Mail', 'Auth', 'Recaptcha', 'Recaptcha2', 'Crud', 'Service', 'Wiki',
|
__all__ = ['Mail', 'Auth', 'Recaptcha', 'Recaptcha2', 'Crud', 'Service', 'Wiki',
|
||||||
'PluginManager', 'fetch', 'geocode', 'reverse_geocode', 'prettydate']
|
'PluginManager', 'fetch', 'geocode', 'reverse_geocode', 'prettydate']
|
||||||
|
|
||||||
@@ -265,7 +253,7 @@ class Mail(object):
|
|||||||
MIMEBase.MIMEBase.__init__(self, *content_type.split('/', 1))
|
MIMEBase.MIMEBase.__init__(self, *content_type.split('/', 1))
|
||||||
self.set_payload(payload)
|
self.set_payload(payload)
|
||||||
self['Content-Disposition'] = 'attachment; filename="%s"' % filename
|
self['Content-Disposition'] = 'attachment; filename="%s"' % filename
|
||||||
if not content_id is None:
|
if content_id is not None:
|
||||||
self['Content-Id'] = '<%s>' % content_id.encode(encoding)
|
self['Content-Id'] = '<%s>' % content_id.encode(encoding)
|
||||||
Encoders.encode_base64(self)
|
Encoders.encode_base64(self)
|
||||||
|
|
||||||
@@ -789,16 +777,16 @@ class Mail(object):
|
|||||||
if attachments:
|
if attachments:
|
||||||
result = mail.send_mail(
|
result = mail.send_mail(
|
||||||
sender=sender, to=origTo,
|
sender=sender, to=origTo,
|
||||||
subject=unicode(subject), body=unicode(text), html=html,
|
subject=unicode(subject, encoding), body=unicode(text, encoding), html=html,
|
||||||
attachments=attachments, **xcc)
|
attachments=attachments, **xcc)
|
||||||
elif html and (not raw):
|
elif html and (not raw):
|
||||||
result = mail.send_mail(
|
result = mail.send_mail(
|
||||||
sender=sender, to=origTo,
|
sender=sender, to=origTo,
|
||||||
subject=unicode(subject), body=unicode(text), html=html, **xcc)
|
subject=unicode(subject, encoding), body=unicode(text, encoding), html=html, **xcc)
|
||||||
else:
|
else:
|
||||||
result = mail.send_mail(
|
result = mail.send_mail(
|
||||||
sender=sender, to=origTo,
|
sender=sender, to=origTo,
|
||||||
subject=unicode(subject), body=unicode(text), **xcc)
|
subject=unicode(subject, encoding), body=unicode(text, encoding), **xcc)
|
||||||
else:
|
else:
|
||||||
smtp_args = self.settings.server.split(':')
|
smtp_args = self.settings.server.split(':')
|
||||||
kwargs = dict(timeout=self.settings.timeout)
|
kwargs = dict(timeout=self.settings.timeout)
|
||||||
@@ -1143,8 +1131,7 @@ def addrow(form, a, b, c, style, _id, position=-1):
|
|||||||
class AuthJWT(object):
|
class AuthJWT(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
If left externally, this needs the usual "singleton" approach.
|
Experimental!
|
||||||
Given I (we) don't know if to include in auth yet, let's stick to basics.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- secret_key: the secret. Without salting, an attacker knowing this can impersonate
|
- secret_key: the secret. Without salting, an attacker knowing this can impersonate
|
||||||
@@ -1175,7 +1162,9 @@ class AuthJWT(object):
|
|||||||
return payload
|
return payload
|
||||||
- before_authorization: can be a callable that takes the deserialized token (a dict) as input.
|
- before_authorization: can be a callable that takes the deserialized token (a dict) as input.
|
||||||
Gets called right after signature verification but before the actual
|
Gets called right after signature verification but before the actual
|
||||||
authorization takes place. You can raise with HTTP a proper error message
|
authorization takes place. It may be use to cast
|
||||||
|
the extra auth_user fields to their actual types.
|
||||||
|
You can raise with HTTP a proper error message
|
||||||
Example:
|
Example:
|
||||||
def mybefore_authorization(tokend):
|
def mybefore_authorization(tokend):
|
||||||
if not tokend['my_name_is'] == 'bond,james bond':
|
if not tokend['my_name_is'] == 'bond,james bond':
|
||||||
@@ -1192,8 +1181,8 @@ class AuthJWT(object):
|
|||||||
def login_and_take_token():
|
def login_and_take_token():
|
||||||
return myjwt.jwt_token_manager()
|
return myjwt.jwt_token_manager()
|
||||||
|
|
||||||
A call then to /app/controller/login_and_take_token/auth with username and password returns the token
|
A call then to /app/controller/login_and_take_token with username and password returns the token
|
||||||
A call to /app/controller/login_and_take_token/refresh with the original token returns the refreshed token
|
A call to /app/controller/login_and_take_token with the original token returns the refreshed token
|
||||||
|
|
||||||
To protect a function with JWT
|
To protect a function with JWT
|
||||||
|
|
||||||
@@ -1256,7 +1245,7 @@ class AuthJWT(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def jwt_b64e(string):
|
def jwt_b64e(string):
|
||||||
if isinstance(string, unicode):
|
if isinstance(string, unicode):
|
||||||
string = string.encode('uft-8', 'strict')
|
string = string.encode('utf-8', 'strict')
|
||||||
return base64.urlsafe_b64encode(string).strip(b'=')
|
return base64.urlsafe_b64encode(string).strip(b'=')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -1279,7 +1268,7 @@ class AuthJWT(object):
|
|||||||
if isinstance(secret, unicode):
|
if isinstance(secret, unicode):
|
||||||
secret = secret.encode('ascii', 'ignore')
|
secret = secret.encode('ascii', 'ignore')
|
||||||
b64h = self.cached_b64h
|
b64h = self.cached_b64h
|
||||||
b64p = self.jwt_b64e(json_parser.dumps(payload))
|
b64p = self.jwt_b64e(serializers.json(payload))
|
||||||
jbody = b64h + '.' + b64p
|
jbody = b64h + '.' + b64p
|
||||||
mauth = hmac.new(key=secret, msg=jbody, digestmod=self.digestmod)
|
mauth = hmac.new(key=secret, msg=jbody, digestmod=self.digestmod)
|
||||||
jsign = self.jwt_b64e(mauth.digest())
|
jsign = self.jwt_b64e(mauth.digest())
|
||||||
@@ -1287,7 +1276,7 @@ class AuthJWT(object):
|
|||||||
|
|
||||||
def verify_signature(self, body, signature, secret):
|
def verify_signature(self, body, signature, secret):
|
||||||
mauth = hmac.new(key=secret, msg=body, digestmod=self.digestmod)
|
mauth = hmac.new(key=secret, msg=body, digestmod=self.digestmod)
|
||||||
return hmac.compare_digest(self.jwt_b64e(mauth.digest()), signature)
|
return compare(self.jwt_b64e(mauth.digest()), signature)
|
||||||
|
|
||||||
def load_token(self, token):
|
def load_token(self, token):
|
||||||
if isinstance(token, unicode):
|
if isinstance(token, unicode):
|
||||||
@@ -1298,7 +1287,7 @@ class AuthJWT(object):
|
|||||||
# header not the same
|
# header not the same
|
||||||
raise HTTP(400, u'Invalid JWT Header')
|
raise HTTP(400, u'Invalid JWT Header')
|
||||||
secret = self.secret_key
|
secret = self.secret_key
|
||||||
tokend = json_parser.loads(self.jwt_b64d(b64b))
|
tokend = serializers.loads_json(self.jwt_b64d(b64b))
|
||||||
if self.salt:
|
if self.salt:
|
||||||
if callable(self.salt):
|
if callable(self.salt):
|
||||||
secret = "%s$%s" % (secret, self.salt(tokend))
|
secret = "%s$%s" % (secret, self.salt(tokend))
|
||||||
@@ -1324,7 +1313,10 @@ class AuthJWT(object):
|
|||||||
We (mis)use the heavy default auth mechanism to avoid any further computation,
|
We (mis)use the heavy default auth mechanism to avoid any further computation,
|
||||||
while sticking to a somewhat-stable Auth API.
|
while sticking to a somewhat-stable Auth API.
|
||||||
"""
|
"""
|
||||||
now = time.mktime(datetime.datetime.utcnow().timetuple())
|
## is the following safe or should we use
|
||||||
|
## calendar.timegm(datetime.datetime.utcnow().timetuple())
|
||||||
|
## result seem to be the same (seconds since epoch, in UTC)
|
||||||
|
now = time.mktime(datetime.datetime.now().timetuple())
|
||||||
expires = now + self.expiration
|
expires = now + self.expiration
|
||||||
payload = dict(
|
payload = dict(
|
||||||
hmac_key=session_auth['hmac_key'],
|
hmac_key=session_auth['hmac_key'],
|
||||||
@@ -1336,7 +1328,7 @@ class AuthJWT(object):
|
|||||||
return payload
|
return payload
|
||||||
|
|
||||||
def refresh_token(self, orig_payload):
|
def refresh_token(self, orig_payload):
|
||||||
now = time.mktime(datetime.datetime.utcnow().timetuple())
|
now = time.mktime(datetime.datetime.now().timetuple())
|
||||||
if self.verify_expiration:
|
if self.verify_expiration:
|
||||||
orig_exp = orig_payload['exp']
|
orig_exp = orig_payload['exp']
|
||||||
if orig_exp + self.leeway < now:
|
if orig_exp + self.leeway < now:
|
||||||
@@ -1346,7 +1338,7 @@ class AuthJWT(object):
|
|||||||
if orig_iat + self.refresh_expiration_delta < now:
|
if orig_iat + self.refresh_expiration_delta < now:
|
||||||
# refreshed too long ago
|
# refreshed too long ago
|
||||||
raise HTTP(400, u'Token issued too long ago')
|
raise HTTP(400, u'Token issued too long ago')
|
||||||
expires = now + self.refresh_expiration_delta
|
expires = now + self.expiration
|
||||||
orig_payload.update(
|
orig_payload.update(
|
||||||
orig_iat=orig_iat,
|
orig_iat=orig_iat,
|
||||||
iat=now,
|
iat=now,
|
||||||
@@ -1372,14 +1364,17 @@ class AuthJWT(object):
|
|||||||
def api_auth():
|
def api_auth():
|
||||||
return myjwt.jwt_token_manager()
|
return myjwt.jwt_token_manager()
|
||||||
|
|
||||||
Then, a call to /app/c/api_auth/auth with username and password
|
Then, a call to /app/c/api_auth with username and password
|
||||||
returns a token, while /app/c/api_auth/refresh with the current token
|
returns a token, while /app/c/api_auth with the current token
|
||||||
issues another token
|
issues another token
|
||||||
"""
|
"""
|
||||||
request = current.request
|
request = current.request
|
||||||
response = current.response
|
response = current.response
|
||||||
session = current.session
|
session = current.session
|
||||||
# forget and unlock response
|
# forget and unlock response
|
||||||
|
session.forget(response)
|
||||||
|
valid_user = None
|
||||||
|
ret = None
|
||||||
if request.vars.token:
|
if request.vars.token:
|
||||||
if not self.allow_refresh:
|
if not self.allow_refresh:
|
||||||
raise HTTP(403, u'Refreshing token is not allowed')
|
raise HTTP(403, u'Refreshing token is not allowed')
|
||||||
@@ -1387,24 +1382,23 @@ class AuthJWT(object):
|
|||||||
tokend = self.load_token(token)
|
tokend = self.load_token(token)
|
||||||
# verification can fail here
|
# verification can fail here
|
||||||
refreshed = self.refresh_token(tokend)
|
refreshed = self.refresh_token(tokend)
|
||||||
ret = {'token':self.generate_token(refreshed)}
|
ret = {'token': self.generate_token(refreshed)}
|
||||||
elif self.user_param in request.vars and self.pass_param in request.vars:
|
elif self.user_param in request.vars and self.pass_param in request.vars:
|
||||||
session.forget(response)
|
|
||||||
username = request.vars[self.user_param]
|
username = request.vars[self.user_param]
|
||||||
password = request.vars[self.pass_param]
|
password = request.vars[self.pass_param]
|
||||||
valid_user = self.auth.login_bare(username, password)
|
valid_user = self.auth.login_bare(username, password)
|
||||||
if valid_user:
|
|
||||||
payload = self.serialize_auth_session(current.session.auth)
|
|
||||||
self.alter_payload(payload)
|
|
||||||
ret = {'token':self.generate_token(payload)}
|
|
||||||
else:
|
|
||||||
raise HTTP(
|
|
||||||
401, u'Not Authorized',
|
|
||||||
**{'WWW-Authenticate': u'JWT realm="%s"' % self.realm})
|
|
||||||
else:
|
else:
|
||||||
raise HTTP(400, u'Must pass token for refresh or username and password for login')
|
valid_user = self.auth.user
|
||||||
response.headers['content-type'] = 'application/json'
|
if valid_user:
|
||||||
return json.dumps(ret)
|
payload = self.serialize_auth_session(current.session.auth)
|
||||||
|
self.alter_payload(payload)
|
||||||
|
ret = {'token': self.generate_token(payload)}
|
||||||
|
elif ret is None:
|
||||||
|
raise HTTP(
|
||||||
|
401, u'Not Authorized - need to be logged in, to pass a token for refresh or username and password for login',
|
||||||
|
**{'WWW-Authenticate': u'JWT realm="%s"' % self.realm})
|
||||||
|
response.headers['Content-Type'] = 'application/json'
|
||||||
|
return serializers.json(ret)
|
||||||
|
|
||||||
def inject_token(self, tokend):
|
def inject_token(self, tokend):
|
||||||
"""
|
"""
|
||||||
@@ -1716,8 +1710,9 @@ class Auth(object):
|
|||||||
args = []
|
args = []
|
||||||
if vars is None:
|
if vars is None:
|
||||||
vars = {}
|
vars = {}
|
||||||
|
host = scheme and self.settings.host
|
||||||
return URL(c=self.settings.controller,
|
return URL(c=self.settings.controller,
|
||||||
f=f, args=args, vars=vars, scheme=scheme)
|
f=f, args=args, vars=vars, scheme=scheme, host=host)
|
||||||
|
|
||||||
def here(self):
|
def here(self):
|
||||||
return URL(args=current.request.args, vars=current.request.get_vars)
|
return URL(args=current.request.args, vars=current.request.get_vars)
|
||||||
@@ -1726,7 +1721,7 @@ class Auth(object):
|
|||||||
hmac_key=None, controller='default', function='user',
|
hmac_key=None, controller='default', function='user',
|
||||||
cas_provider=None, signature=True, secure=False,
|
cas_provider=None, signature=True, secure=False,
|
||||||
csrf_prevention=True, propagate_extension=None,
|
csrf_prevention=True, propagate_extension=None,
|
||||||
url_index=None, jwt=None):
|
url_index=None, jwt=None, host=None):
|
||||||
|
|
||||||
## next two lines for backward compatibility
|
## next two lines for backward compatibility
|
||||||
if not db and environment and isinstance(environment, DAL):
|
if not db and environment and isinstance(environment, DAL):
|
||||||
@@ -1770,8 +1765,9 @@ class Auth(object):
|
|||||||
|
|
||||||
settings = self.settings = Settings()
|
settings = self.settings = Settings()
|
||||||
settings.update(Auth.default_settings)
|
settings.update(Auth.default_settings)
|
||||||
|
host = host or request.env.http_host
|
||||||
settings.update(
|
settings.update(
|
||||||
cas_domains=[request.env.http_host],
|
cas_domains=[host],
|
||||||
enable_tokens=False,
|
enable_tokens=False,
|
||||||
cas_provider=cas_provider,
|
cas_provider=cas_provider,
|
||||||
cas_actions=dict(login='login',
|
cas_actions=dict(login='login',
|
||||||
@@ -1821,6 +1817,7 @@ class Auth(object):
|
|||||||
label_separator=current.response.form_label_separator,
|
label_separator=current.response.form_label_separator,
|
||||||
two_factor_methods = [],
|
two_factor_methods = [],
|
||||||
two_factor_onvalidation = [],
|
two_factor_onvalidation = [],
|
||||||
|
host = host,
|
||||||
)
|
)
|
||||||
settings.lock_keys = True
|
settings.lock_keys = True
|
||||||
# ## these are messages that can be customized
|
# ## these are messages that can be customized
|
||||||
@@ -1846,7 +1843,6 @@ class Auth(object):
|
|||||||
self.define_signature()
|
self.define_signature()
|
||||||
else:
|
else:
|
||||||
self.signature = None
|
self.signature = None
|
||||||
|
|
||||||
self.jwt_handler = jwt and AuthJWT(self, **jwt)
|
self.jwt_handler = jwt and AuthJWT(self, **jwt)
|
||||||
|
|
||||||
def get_vars_next(self):
|
def get_vars_next(self):
|
||||||
@@ -4045,13 +4041,13 @@ class Auth(object):
|
|||||||
def jwt(self):
|
def jwt(self):
|
||||||
"""
|
"""
|
||||||
To use JWT authentication:
|
To use JWT authentication:
|
||||||
1) instantiate auth with
|
1) instantiate auth with::
|
||||||
|
|
||||||
auth = Auth(db, jwt = {'secret_key':'secret'})
|
auth = Auth(db, jwt = {'secret_key':'secret'})
|
||||||
|
|
||||||
where 'secret' is your own secret string.
|
where 'secret' is your own secret string.
|
||||||
|
|
||||||
2) Secorate functions that require login but should accept the JWT token credentials:
|
2) Decorate functions that require login but should accept the JWT token credentials::
|
||||||
|
|
||||||
@auth.allows_jwt()
|
@auth.allows_jwt()
|
||||||
@auth.requires_login()
|
@auth.requires_login()
|
||||||
@@ -4074,7 +4070,7 @@ class Auth(object):
|
|||||||
|
|
||||||
Authorization: Bearer <the jwt token>
|
Authorization: Bearer <the jwt token>
|
||||||
|
|
||||||
Any additional attributes in the jwt argument of Auth() below:
|
Any additional attributes in the jwt argument of Auth() below::
|
||||||
|
|
||||||
auth = Auth(db, jwt = {...})
|
auth = Auth(db, jwt = {...})
|
||||||
|
|
||||||
@@ -4083,8 +4079,8 @@ class Auth(object):
|
|||||||
if not self.jwt_handler:
|
if not self.jwt_handler:
|
||||||
raise HTTP(400, "Not authorized")
|
raise HTTP(400, "Not authorized")
|
||||||
else:
|
else:
|
||||||
current.response.headers['content-type'] = 'application/json'
|
rtn = self.jwt_handler.jwt_token_manager()
|
||||||
raise HTTP(200, self.jwt_handler.jwt_token_manager())
|
raise HTTP(200, rtn, cookies=None, **current.response.headers)
|
||||||
|
|
||||||
def is_impersonating(self):
|
def is_impersonating(self):
|
||||||
return self.is_logged_in() and 'impersonator' in current.session.auth
|
return self.is_logged_in() and 'impersonator' in current.session.auth
|
||||||
@@ -4188,7 +4184,7 @@ class Auth(object):
|
|||||||
if not self.jwt_handler:
|
if not self.jwt_handler:
|
||||||
raise HTTP(400, "Not authorized")
|
raise HTTP(400, "Not authorized")
|
||||||
else:
|
else:
|
||||||
return self.jwt_handler.allows_jwt()
|
return self.jwt_handler.allows_jwt(otherwise=otherwise)
|
||||||
|
|
||||||
def requires(self, condition, requires_login=True, otherwise=None):
|
def requires(self, condition, requires_login=True, otherwise=None):
|
||||||
"""
|
"""
|
||||||
@@ -4201,7 +4197,6 @@ class Auth(object):
|
|||||||
|
|
||||||
basic_allowed, basic_accepted, user = self.basic()
|
basic_allowed, basic_accepted, user = self.basic()
|
||||||
user = user or self.user
|
user = user or self.user
|
||||||
|
|
||||||
login_required = requires_login
|
login_required = requires_login
|
||||||
if callable(login_required):
|
if callable(login_required):
|
||||||
login_required = login_required()
|
login_required = login_required()
|
||||||
@@ -4210,7 +4205,7 @@ class Auth(object):
|
|||||||
if not user:
|
if not user:
|
||||||
if current.request.ajax:
|
if current.request.ajax:
|
||||||
raise HTTP(401, self.messages.ajax_failed_authentication)
|
raise HTTP(401, self.messages.ajax_failed_authentication)
|
||||||
elif not otherwise is None:
|
elif otherwise is not None:
|
||||||
if callable(otherwise):
|
if callable(otherwise):
|
||||||
return otherwise()
|
return otherwise()
|
||||||
redirect(otherwise)
|
redirect(otherwise)
|
||||||
@@ -4248,7 +4243,7 @@ class Auth(object):
|
|||||||
return self.requires(True, otherwise=otherwise)
|
return self.requires(True, otherwise=otherwise)
|
||||||
|
|
||||||
def requires_login_or_token(self, otherwise=None):
|
def requires_login_or_token(self, otherwise=None):
|
||||||
if self.settings.enable_tokens == True:
|
if self.settings.enable_tokens is True:
|
||||||
user = None
|
user = None
|
||||||
request = current.request
|
request = current.request
|
||||||
token = request.env.http_web2py_user_token or request.vars._token
|
token = request.env.http_web2py_user_token or request.vars._token
|
||||||
|
|||||||
+9
-1
@@ -64,6 +64,10 @@ else:
|
|||||||
except (ImportError, ValueError):
|
except (ImportError, ValueError):
|
||||||
HAVE_PBKDF2 = False
|
HAVE_PBKDF2 = False
|
||||||
|
|
||||||
|
HAVE_COMPARE_DIGEST = False
|
||||||
|
if hasattr(hmac, 'compare_digest'):
|
||||||
|
HAVE_COMPARE_DIGEST = True
|
||||||
|
|
||||||
logger = logging.getLogger("web2py")
|
logger = logging.getLogger("web2py")
|
||||||
|
|
||||||
|
|
||||||
@@ -77,6 +81,8 @@ def AES_new(key, IV=None):
|
|||||||
|
|
||||||
def compare(a, b):
|
def compare(a, b):
|
||||||
""" Compares two strings and not vulnerable to timing attacks """
|
""" Compares two strings and not vulnerable to timing attacks """
|
||||||
|
if HAVE_COMPARE_DIGEST:
|
||||||
|
return hmac.compare_digest(a, b)
|
||||||
if len(a) != len(b):
|
if len(a) != len(b):
|
||||||
return False
|
return False
|
||||||
result = 0
|
result = 0
|
||||||
@@ -143,6 +149,7 @@ DIGEST_ALG_BY_SIZE = {
|
|||||||
512 / 4: 'sha512',
|
512 / 4: 'sha512',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_callable_argspec(fn):
|
def get_callable_argspec(fn):
|
||||||
if inspect.isfunction(fn) or inspect.ismethod(fn):
|
if inspect.isfunction(fn) or inspect.ismethod(fn):
|
||||||
inspectable = fn
|
inspectable = fn
|
||||||
@@ -154,6 +161,7 @@ def get_callable_argspec(fn):
|
|||||||
inspectable = fn
|
inspectable = fn
|
||||||
return inspect.getargspec(inspectable)
|
return inspect.getargspec(inspectable)
|
||||||
|
|
||||||
|
|
||||||
def pad(s, n=32, padchar=' '):
|
def pad(s, n=32, padchar=' '):
|
||||||
return s + (32 - len(s) % 32) * padchar
|
return s + (32 - len(s) % 32) * padchar
|
||||||
|
|
||||||
@@ -172,7 +180,7 @@ def secure_dumps(data, encryption_key, hash_key=None, compression_level=None):
|
|||||||
|
|
||||||
|
|
||||||
def secure_loads(data, encryption_key, hash_key=None, compression_level=None):
|
def secure_loads(data, encryption_key, hash_key=None, compression_level=None):
|
||||||
if not ':' in data:
|
if ':' not in data:
|
||||||
return None
|
return None
|
||||||
if not hash_key:
|
if not hash_key:
|
||||||
hash_key = sha1(encryption_key).hexdigest()
|
hash_key = sha1(encryption_key).hexdigest()
|
||||||
|
|||||||
+12
-10
@@ -201,12 +201,15 @@ class IS_MATCH(Validator):
|
|||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
if self.is_unicode:
|
if self.is_unicode:
|
||||||
if isinstance(value,unicode):
|
if not isinstance(value, unicode):
|
||||||
match = self.regex.search(value)
|
|
||||||
else:
|
|
||||||
match = self.regex.search(str(value).decode('utf8'))
|
match = self.regex.search(str(value).decode('utf8'))
|
||||||
|
else:
|
||||||
|
match = self.regex.search(value)
|
||||||
else:
|
else:
|
||||||
match = self.regex.search(str(value))
|
if not isinstance(value, unicode):
|
||||||
|
match = self.regex.search(str(value))
|
||||||
|
else:
|
||||||
|
match = self.regex.search(value.encode('utf8'))
|
||||||
if match is not None:
|
if match is not None:
|
||||||
return (self.extract and match.group() or value, None)
|
return (self.extract and match.group() or value, None)
|
||||||
return (value, translate(self.error_message))
|
return (value, translate(self.error_message))
|
||||||
@@ -630,12 +633,11 @@ class IS_IN_DB(Validator):
|
|||||||
if self.field.type in ('id','integer'):
|
if self.field.type in ('id','integer'):
|
||||||
new_values = []
|
new_values = []
|
||||||
for value in values:
|
for value in values:
|
||||||
if isinstance(value,(int,long)) or value.isdigit():
|
if not (isinstance(value,(int,long)) or value.isdigit()):
|
||||||
value = int(value)
|
if self.auto_add:
|
||||||
elif self.auto_add:
|
value = str(self.maybe_add(table, self.fieldnames[0], value))
|
||||||
value = self.maybe_add(table, self.fieldnames[0], value)
|
else:
|
||||||
else:
|
return (values, translate(self.error_message))
|
||||||
return (values, translate(self.error_message))
|
|
||||||
new_values.append(value)
|
new_values.append(value)
|
||||||
values = new_values
|
values = new_values
|
||||||
|
|
||||||
|
|||||||
+26
-16
@@ -150,7 +150,7 @@ class web2pyDialog(object):
|
|||||||
self.scheduler_processes = {}
|
self.scheduler_processes = {}
|
||||||
self.menu = Tkinter.Menu(self.root)
|
self.menu = Tkinter.Menu(self.root)
|
||||||
servermenu = Tkinter.Menu(self.menu, tearoff=0)
|
servermenu = Tkinter.Menu(self.menu, tearoff=0)
|
||||||
httplog = os.path.join(self.options.folder, 'httpserver.log')
|
httplog = os.path.join(self.options.folder, self.options.log_filename)
|
||||||
iconphoto = os.path.join('extras', 'icons', 'web2py.gif')
|
iconphoto = os.path.join('extras', 'icons', 'web2py.gif')
|
||||||
if os.path.exists(iconphoto):
|
if os.path.exists(iconphoto):
|
||||||
img = Tkinter.PhotoImage(file=iconphoto)
|
img = Tkinter.PhotoImage(file=iconphoto)
|
||||||
@@ -225,9 +225,9 @@ class web2pyDialog(object):
|
|||||||
text=str(ProgramVersion + "\n" + ProgramAuthor),
|
text=str(ProgramVersion + "\n" + ProgramAuthor),
|
||||||
font=('Helvetica', 11), justify=Tkinter.CENTER,
|
font=('Helvetica', 11), justify=Tkinter.CENTER,
|
||||||
foreground='#195866', background=bg_color,
|
foreground='#195866', background=bg_color,
|
||||||
height=3).pack( side='top',
|
height=3).pack(side='top',
|
||||||
fill='both',
|
fill='both',
|
||||||
expand='yes')
|
expand='yes')
|
||||||
|
|
||||||
self.bannerarea.after(1000, self.update_canvas)
|
self.bannerarea.after(1000, self.update_canvas)
|
||||||
|
|
||||||
@@ -322,11 +322,15 @@ class web2pyDialog(object):
|
|||||||
self.tb = None
|
self.tb = None
|
||||||
|
|
||||||
def update_schedulers(self, start=False):
|
def update_schedulers(self, start=False):
|
||||||
|
applications_folder = os.path.join(self.options.folder, 'applications')
|
||||||
apps = []
|
apps = []
|
||||||
available_apps = [arq for arq in os.listdir('applications/')]
|
##FIXME - can't start scheduler in the correct dir from Tk
|
||||||
available_apps = [arq for arq in available_apps
|
if self.options.folder:
|
||||||
if os.path.exists(
|
return
|
||||||
'applications/%s/models/scheduler.py' % arq)]
|
available_apps = [
|
||||||
|
arq for arq in os.listdir(applications_folder)
|
||||||
|
if os.path.exists(os.path.join(applications_folder, arq, 'models', 'scheduler.py'))
|
||||||
|
]
|
||||||
if start:
|
if start:
|
||||||
# the widget takes care of starting the scheduler
|
# the widget takes care of starting the scheduler
|
||||||
if self.options.scheduler and self.options.with_scheduler:
|
if self.options.scheduler and self.options.with_scheduler:
|
||||||
@@ -414,9 +418,11 @@ class web2pyDialog(object):
|
|||||||
def connect_pages(self):
|
def connect_pages(self):
|
||||||
""" Connects pages """
|
""" Connects pages """
|
||||||
# reset the menu
|
# reset the menu
|
||||||
available_apps = [arq for arq in os.listdir('applications/')
|
applications_folder = os.path.join(self.options.folder, 'applications')
|
||||||
if os.path.exists(
|
available_apps = [
|
||||||
'applications/%s/__init__.py' % arq)]
|
arq for arq in os.listdir(applications_folder)
|
||||||
|
if os.path.exists(os.path.join(applications_folder, arq, '__init__.py'))
|
||||||
|
]
|
||||||
self.pagesmenu.delete(0, len(available_apps))
|
self.pagesmenu.delete(0, len(available_apps))
|
||||||
for arq in available_apps:
|
for arq in available_apps:
|
||||||
url = self.url + arq
|
url = self.url + arq
|
||||||
@@ -552,14 +558,15 @@ class web2pyDialog(object):
|
|||||||
def update_canvas(self):
|
def update_canvas(self):
|
||||||
""" Updates canvas """
|
""" Updates canvas """
|
||||||
|
|
||||||
|
httplog = os.path.join(self.options.folder, self.options.log_filename)
|
||||||
try:
|
try:
|
||||||
t1 = os.path.getsize('httpserver.log')
|
t1 = os.path.getsize(httplog)
|
||||||
except:
|
except:
|
||||||
self.canvas.after(1000, self.update_canvas)
|
self.canvas.after(1000, self.update_canvas)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fp = open('httpserver.log', 'r')
|
fp = open(httplog, 'r')
|
||||||
fp.seek(self.t0)
|
fp.seek(self.t0)
|
||||||
data = fp.read(t1 - self.t0)
|
data = fp.read(t1 - self.t0)
|
||||||
fp.close()
|
fp.close()
|
||||||
@@ -1051,6 +1058,8 @@ def start_schedulers(options):
|
|||||||
apps = options.scheduler_groups
|
apps = options.scheduler_groups
|
||||||
code = "from gluon import current;current._scheduler.loop()"
|
code = "from gluon import current;current._scheduler.loop()"
|
||||||
logging.getLogger().setLevel(options.debuglevel)
|
logging.getLogger().setLevel(options.debuglevel)
|
||||||
|
if options.folder:
|
||||||
|
os.chdir(options.folder)
|
||||||
if len(apps) == 1 and not options.with_scheduler:
|
if len(apps) == 1 and not options.with_scheduler:
|
||||||
app_, code = get_code_for_scheduler(apps[0], options)
|
app_, code = get_code_for_scheduler(apps[0], options)
|
||||||
if not app_:
|
if not app_:
|
||||||
@@ -1118,11 +1127,12 @@ def start(cron=True):
|
|||||||
setattr(options, key, getattr(options2, key))
|
setattr(options, key, getattr(options2, key))
|
||||||
|
|
||||||
logfile0 = os.path.join('examples', 'logging.example.conf')
|
logfile0 = os.path.join('examples', 'logging.example.conf')
|
||||||
if not os.path.exists('logging.conf') and os.path.exists(logfile0):
|
logfile1 = os.path.join(options.folder, 'logging.conf')
|
||||||
|
if not os.path.exists(logfile1) and os.path.exists(logfile0):
|
||||||
import shutil
|
import shutil
|
||||||
sys.stdout.write("Copying logging.conf.example to logging.conf ... ")
|
sys.stdout.write("Copying logging.conf.example to logging.conf ... ")
|
||||||
shutil.copyfile(logfile0, 'logging.conf')
|
shutil.copyfile(logfile0, logfile1)
|
||||||
sys.stdout.write('OK\n')
|
sys.stdout.write("OK\n")
|
||||||
|
|
||||||
# ## if -T run doctests (no cron)
|
# ## if -T run doctests (no cron)
|
||||||
if hasattr(options, 'test') and options.test:
|
if hasattr(options, 'test') and options.test:
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class ServiceBase(Base):
|
|||||||
key = config['https_key']
|
key = config['https_key']
|
||||||
cert = config['https_cert']
|
cert = config['https_cert']
|
||||||
if key != '' and cert != '':
|
if key != '' and cert != '':
|
||||||
interfaces.append('%s:%s:%s:%s' % (ip, port, cert, key))
|
interfaces.append('%s:%s:%s:%s' % (ip, port, key, cert))
|
||||||
ports.append(ports)
|
ports.append(ports)
|
||||||
if len(interfaces) == 0:
|
if len(interfaces) == 0:
|
||||||
sys.exit('Configuration error. Must have settings for http and/or https')
|
sys.exit('Configuration error. Must have settings for http and/or https')
|
||||||
@@ -92,7 +92,7 @@ class ServiceBase(Base):
|
|||||||
interfaces = ';'.join(interfaces)
|
interfaces = ';'.join(interfaces)
|
||||||
args.append('--interfaces=%s' % interfaces)
|
args.append('--interfaces=%s' % interfaces)
|
||||||
|
|
||||||
if 'log_filename' in config.key():
|
if 'log_filename' in config.keys():
|
||||||
log_filename = config['log_filename']
|
log_filename = config['log_filename']
|
||||||
args.append('--log_filename=%s' % log_filename)
|
args.append('--log_filename=%s' % log_filename)
|
||||||
|
|
||||||
|
|||||||
@@ -104,12 +104,10 @@ class SessionSetDb(SessionSet):
|
|||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
"""Return list of SessionDb instances for existing sessions."""
|
"""Return list of SessionDb instances for existing sessions."""
|
||||||
sessions = []
|
|
||||||
table = current.response.session_db_table
|
table = current.response.session_db_table
|
||||||
if table:
|
if table:
|
||||||
for row in table._db(table.id > 0).select():
|
for row in table._db(table.id > 0).select():
|
||||||
sessions.append(SessionDb(row))
|
yield SessionDb(row)
|
||||||
return sessions
|
|
||||||
|
|
||||||
|
|
||||||
class SessionSetFiles(SessionSet):
|
class SessionSetFiles(SessionSet):
|
||||||
|
|||||||
@@ -6,9 +6,21 @@ if [[ $EUID -ne 0 ]]; then
|
|||||||
echo "You must run the script as root or using sudo"
|
echo "You must run the script as root or using sudo"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
# parse command line arguments
|
||||||
|
nopassword=0
|
||||||
|
nocertificate=0
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--no-password) nopassword=1; shift 1;;
|
||||||
|
--no-certificate) nocertificate=1; shift 1;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
# Get Web2py Admin Password
|
# Get Web2py Admin Password
|
||||||
echo -e "Web2py Admin Password: \c "
|
if [ "$nopassword" -eq 0 ]
|
||||||
read PW
|
then
|
||||||
|
echo -e "Web2py Admin Password: \c "
|
||||||
|
read PW
|
||||||
|
fi
|
||||||
# Upgrade and install needed software
|
# Upgrade and install needed software
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get -y upgrade
|
apt-get -y upgrade
|
||||||
@@ -115,13 +127,13 @@ ln -s /etc/nginx/sites-available/web2py /etc/nginx/sites-enabled/web2py
|
|||||||
rm /etc/nginx/sites-enabled/default
|
rm /etc/nginx/sites-enabled/default
|
||||||
mkdir /etc/nginx/ssl
|
mkdir /etc/nginx/ssl
|
||||||
cd /etc/nginx/ssl
|
cd /etc/nginx/ssl
|
||||||
|
if [ "$nocertificate" -eq 0 ]
|
||||||
openssl genrsa 1024 > web2py.key
|
then
|
||||||
chmod 400 web2py.key
|
openssl genrsa 1024 > web2py.key
|
||||||
openssl req -new -x509 -nodes -sha1 -days 1780 -key web2py.key > web2py.crt
|
chmod 400 web2py.key
|
||||||
openssl x509 -noout -fingerprint -text < web2py.crt > web2py.info
|
openssl req -new -x509 -nodes -sha1 -days 1780 -key web2py.key > web2py.crt
|
||||||
|
openssl x509 -noout -fingerprint -text < web2py.crt > web2py.info
|
||||||
|
fi
|
||||||
# Prepare folders for uwsgi
|
# Prepare folders for uwsgi
|
||||||
sudo mkdir /etc/uwsgi
|
sudo mkdir /etc/uwsgi
|
||||||
sudo mkdir /var/log/uwsgi
|
sudo mkdir /var/log/uwsgi
|
||||||
@@ -176,13 +188,24 @@ mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py
|
|||||||
rm web2py_src.zip
|
rm web2py_src.zip
|
||||||
chown -R www-data:www-data web2py
|
chown -R www-data:www-data web2py
|
||||||
cd /home/www-data/web2py
|
cd /home/www-data/web2py
|
||||||
sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)"
|
if [ "$nopassword" -eq 0 ]
|
||||||
|
then
|
||||||
|
sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)"
|
||||||
|
fi
|
||||||
start uwsgi-emperor
|
start uwsgi-emperor
|
||||||
/etc/init.d/nginx restart
|
/etc/init.d/nginx restart
|
||||||
|
|
||||||
## you can reload uwsgi with
|
echo <<EOF
|
||||||
# restart uwsgi-emperor
|
you can reload uwsgi with
|
||||||
## and stop it with
|
|
||||||
# stop uwsgi-emperor
|
restart uwsgi-emperor
|
||||||
## to reload web2py only (without restarting uwsgi)
|
|
||||||
# touch /etc/uwsgi/web2py.ini
|
and stop it with
|
||||||
|
|
||||||
|
stop uwsgi-emperor
|
||||||
|
|
||||||
|
to reload web2py only (without restarting uwsgi)
|
||||||
|
|
||||||
|
touch /etc/uwsgi/web2py.ini
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user