This commit is contained in:
Michele Comitini
2012-09-21 23:25:09 +02:00
13 changed files with 257 additions and 59 deletions
+1 -1
View File
@@ -1 +1 @@
Version 2.0.9 (2012-09-18 13:39:12) stable
Version 2.0.9 (2012-09-20 13:25:48) stable
@@ -1652,7 +1652,6 @@ def git_pull():
session.flash = T("Application updated via git pull")
redirect(URL('site'))
except CheckoutError, message:
logging.error(message)
session.flash = T("Pull failed, certain files could not be checked out. Check logs for details.")
redirect(URL('site'))
except UnmergedEntriesError:
@@ -1662,11 +1661,9 @@ def git_pull():
session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.")
redirect(URL('site'))
except GitCommandError, status:
logging.error(str(status))
session.flash = T("Pull failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
except Exception,e:
logging.error("Unexpected error:", sys.exc_info()[0])
session.flash = T("Pull failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
elif 'cancel' in request.vars:
@@ -1698,7 +1695,6 @@ def git_push():
session.flash = T("Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.")
redirect(URL('site'))
except Exception, e:
logging.error("Unexpected error:", sys.exc_info()[0])
session.flash = T("Push failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
return dict(app=app,form=form)
@@ -19,6 +19,10 @@
<script src="{{=cm}}/mode/css/css.js"></script>
<script src="{{=cm}}/mode/javascript/javascript.js"></script>
<script src="{{=cm}}/mode/htmlmixed/htmlmixed.js"></script>
<script src="{{=cm}}/lib/util/search.js"></script>
<script src="{{=cm}}/lib/util/searchcursor.js"></script>
<script src="{{=cm}}/lib/util/dialog.js"></script>
<link rel="stylesheet" href="{{=cm}}/lib/util/dialog.css">
<script src="{{=cm}}/emmet.min.js"></script>
<script language="Javascript" type="text/javascript" src="{{=URL('static','js/ajax_editor.js')}}"></script>
{{elif TEXT_EDITOR == 'ace':}}
@@ -246,6 +250,11 @@ window.onload = function() {
<ul>
{{=shortcut('Ctrl+S', T('Save via Ajax'))}}
{{=shortcut('Ctrl+F11', T('Toggle Fullscreen'))}}
{{=shortcut('Ctrl-F / Cmd-F', T('Start searching'))}}
{{=shortcut('Ctrl-G / Cmd-G', T('Find Next'))}}
{{=shortcut('Shift-Ctrl-G / Shift-Cmd-G', T('Find Previous'))}}
{{=shortcut('Shift-Ctrl-F / Cmd-Option-F', T('Replace'))}}
{{=shortcut('Shift-Ctrl-R / Shift-Cmd-Option-F', T('Replace All'))}}
{{=shortcut('Tab', T('Expand Abbreviation'))}}
</ul>
{{elif TEXT_EDITOR == 'codemirror':}}
@@ -253,6 +262,11 @@ window.onload = function() {
<ul>
{{=shortcut('Ctrl+S', T('Save via Ajax'))}}
{{=shortcut('Ctrl+F11', T('Toggle Fullscreen'))}}
{{=shortcut('Ctrl-F / Cmd-F', T('Start searching'))}}
{{=shortcut('Ctrl-G / Cmd-G', T('Find Next'))}}
{{=shortcut('Shift-Ctrl-G / Shift-Cmd-G', T('Find Previous'))}}
{{=shortcut('Shift-Ctrl-F / Cmd-Option-F', T('Replace'))}}
{{=shortcut('Shift-Ctrl-R / Shift-Cmd-Option-F', T('Replace All'))}}
</ul>
{{else:}}
<h3>{{=T("Key bindings")}}</h3>
+2 -2
View File
@@ -33,9 +33,9 @@ def _():
# shortcuts
app = request.application
ctr = request.controller
# useful links to internal and external resources
# useful links to internal and external resources
response.menu+=[
(SPAN('web2py',_style='color:yellow'),False, 'http://web2py.com', [
(SPAN('web2py',_class='highlighted'),False, 'http://web2py.com', [
(T('My Sites'),False,URL('admin','default','site')),
(T('This App'),False,URL('admin','default','design/%s' % app), [
(T('Controller'),False,
@@ -70,7 +70,15 @@ div.controls .error{
//display:inline; /* uncommenting this, the animation effect is lost */
}
div.controls .inline-help{color:#3A87AD;}
div.controls .error_wrapper+.inline-help{margin-left:-99999px}
div.controls .error_wrapper+.inline-help{margin-left:-99999px;}
/* beautify brand */
.navbar-inverse .brand{color:#c6cecc;}
.navbar-inverse .brand b{display:inline-block;margin-top:-1px;}
.navbar-inverse .brand b>span{font-size:22px;color:white}
.navbar-inverse .brand:hover b>span{color:white}
/* beautify web2py link in navbar */
span.highlighted{color:#d8d800;}
.open span.highlighted{color:#ffff00;}
/*=============================================================
OVERRIDING WEB2PY.CSS RULES
@@ -185,6 +193,8 @@ td.w2p_fw ul{margin-left:0px;}
margin:3px 0 0 2px;
}
.web2py_grid form table{width:auto;}
/* auth_user_remember checkbox extrapadding in IE fix */
.ie-lte9 input#auth_user_remember.checkbox {padding-left:0;}
/*=============================================================
MEDIA QUERIES
@@ -1,3 +1,23 @@
/*=============================================================
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;
@@ -15,8 +35,7 @@
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;
@@ -35,4 +54,69 @@ ul.dropdown-menu li.dropdown > a:after {
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;}
+4 -4
View File
@@ -76,7 +76,7 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="http://www.web2py.com/">web2py&trade;&nbsp;</a>
<a class="brand" href="http://www.web2py.com/"><b>web<span>2</span>py</b>&trade;&nbsp;</a>
<ul id="navbar" class="nav pull-right">{{='auth' in globals() and auth.navbar(mode="dropdown") or ''}}</ul>
<div class="nav-collapse">
{{is_mobile=request.user_agent().is_mobile}}
@@ -159,10 +159,10 @@
});
if(jQuery(document).width()>=980) {
jQuery('ul.nav a.dropdown-toggle').parent().hover(function() {
mi = jQuery(this);
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400,function(){mi.addClass('open')});
mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function() {
mi = jQuery(this);
mi = jQuery(this);
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
});
}
+2 -1
View File
@@ -4323,7 +4323,8 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
if first.type != 'id':
return [GAEF(first.name,'!=',self.represent(second,first.type),lambda a,b:a!=b)]
else:
second = Key.from_path(first._tablename, long(second))
if not second is None:
second = Key.from_path(first._tablename, long(second))
return [GAEF(first.name,'!=',second,lambda a,b:a!=b)]
def LT(self,first,second=None):
+2 -2
View File
@@ -155,9 +155,9 @@ def read_possible_plural_rules():
create list of all possible plural rules files
result is cached in PLURAL_RULES dictionary to increase speed
"""
plurals = {}
try:
import gluon.contrib.plural_rules as package
plurals = {}
import contrib.plural_rules as package
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
if len(modname)==2:
module = __import__(package.__name__+'.'+modname,
+1
View File
@@ -192,6 +192,7 @@ def url_out(request, env, application, controller, function,
port = ''
else:
port = ':%s' % port
host = host.split(':')[0]
url = '%s://%s%s%s' % (scheme, host, port, url)
return url
+7 -5
View File
@@ -264,6 +264,7 @@ class Mail(object):
cc=None,
bcc=None,
reply_to=None,
sender='%(sender)s',
encoding='utf-8',
raw=False,
headers={}
@@ -606,7 +607,8 @@ class Mail(object):
# no cryptography process as usual
payload=payload_in
payload['From'] = encoded_or_raw(self.settings.sender.decode(encoding))
sender = sender % dict(sender=self.settings.sender)
payload['From'] = encoded_or_raw(sender.decode(encoding))
origTo = to[:]
if to:
payload['To'] = encoded_or_raw(', '.join(to).decode(encoding))
@@ -623,10 +625,10 @@ class Mail(object):
for k,v in headers.iteritems():
payload[k] = encoded_or_raw(v.decode(encoding))
result = {}
try:
try:
if self.settings.server == 'logging':
logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' % \
('-'*40,self.settings.sender,
('-'*40,sender,
', '.join(to),subject,
text or html,'-'*40))
elif self.settings.server == 'gae':
@@ -1195,7 +1197,7 @@ class Auth(object):
'reset_password','request_reset_password',
'change_password','profile','groups',
'impersonate','not_authorized'):
if len(request.args) >= 2:
if len(request.args) >= 2 and args[0]=='impersonate':
return getattr(self,args[0])(request.args[1])
else:
return getattr(self,args[0])()
@@ -2443,7 +2445,7 @@ class Auth(object):
session = current.session
if next is DEFAULT:
next = self.settings.reset_password_next
next = self.next or self.settings.reset_password_next
try:
key = request.vars.key or getarg(-1)
t0 = int(key.split('-')[0])
+87 -10
View File
@@ -2725,6 +2725,43 @@ class CRYPT(object):
return ('', translate(self.error_message))
return (LazyCrypt(self,value),None)
# entropy calculator for IS_STRONG
#
lowerset = frozenset(unicode('abcdefghijklmnopqrstuvwxyz'))
upperset = frozenset(unicode('ABCDEFGHIJKLMNOPQRSTUVWXYZ'))
numberset = frozenset(unicode('0123456789'))
sym1set = frozenset(unicode('!@#$%^&*()'))
sym2set = frozenset(unicode('~`-_=+[]{}\\|;:\'",.<>?/'))
otherset = frozenset(unicode('0123456789abcdefghijklmnopqrstuvwxyz')) # anything else
def calc_entropy(string):
" calculate a simple entropy for a given string "
import math
alphabet = 0 # alphabet size
other = set()
seen = set()
lastset = None
if isinstance(string, str):
string = unicode(string, encoding='utf8')
for c in string:
# classify this character
inset = otherset
for cset in (lowerset, upperset, numberset, sym1set, sym2set):
if c in cset:
inset = cset
break
# calculate effect of character on alphabet size
if inset not in seen:
seen.add(inset)
alphabet += len(inset) # credit for a new character set
elif c not in other:
alphabet += 1 # credit for unique characters
other.add(c)
if inset is not lastset:
alphabet += 1 # credit for set transitions
lastset = cset
entropy = len(string) * math.log(alphabet) / 0.6931471805599453 # math.log(2)
return round(entropy, 2)
class IS_STRONG(object):
"""
@@ -2734,23 +2771,61 @@ class IS_STRONG(object):
requires=IS_STRONG(min=10, special=2, upper=2))
enforces complexity requirements on a field
>>> IS_STRONG(es=True)('Abcd1234')
('Abcd1234', 'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|')
>>> IS_STRONG(es=True)('Abcd1234!')
('Abcd1234!', None)
>>> IS_STRONG(es=True, entropy=1)('a')
('a', None)
>>> IS_STRONG(es=True, entropy=1, min=2)('a')
('a', 'Minimum length is 2')
>>> IS_STRONG(es=True, entropy=100)('abc123')
('abc123', 'Entropy (32.35) less than required (100)')
>>> IS_STRONG(es=True, entropy=100)('and')
('and', 'Entropy (14.57) less than required (100)')
>>> IS_STRONG(es=True, entropy=100)('aaa')
('aaa', 'Entropy (14.42) less than required (100)')
>>> IS_STRONG(es=True, entropy=100)('a1d')
('a1d', 'Entropy (15.97) less than required (100)')
>>> IS_STRONG(es=True, entropy=100)('añd')
('a\\xc3\\xb1d', 'Entropy (18.13) less than required (100)')
"""
def __init__(self, min=8, max=20, upper=1, lower=1, number=1,
special=1, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|',
invalid=' "', error_message=None):
self.min = min
self.max = max
self.upper = upper
self.lower = lower
self.number = number
self.special = special
def __init__(self, min=None, max=None, upper=None, lower=None, number=None,
entropy=None,
special=None, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|',
invalid=' "', error_message=None, es=False):
self.entropy = entropy
if entropy is None:
# enforce default requirements
self.min = 8 if min is None else min
self.max = max # was 20, but that doesn't make sense
self.upper = 1 if upper is None else upper
self.lower = 1 if lower is None else lower
self.number = 1 if number is None else number
self.special = 1 if special is None else special
else:
# by default, an entropy spec is exclusive
self.min = min
self.max = max
self.upper = upper
self.lower = lower
self.number = number
self.special = special
self.specials = specials
self.invalid = invalid
self.error_message = error_message
self.estring = es # return error message as string (for doctest)
def __call__(self, value):
failures = []
if self.entropy is not None:
entropy = calc_entropy(value)
if entropy < self.entropy:
failures.append(translate("Entropy (%(have)s) less than required (%(need)s)") \
% dict(have=entropy, need=self.entropy))
if type(self.min) == int and self.min > 0:
if not len(value) >= self.min:
failures.append(translate("Minimum length is %s") % self.min)
@@ -2761,7 +2836,7 @@ class IS_STRONG(object):
all_special = [ch in value for ch in self.specials]
if self.special > 0:
if not all_special.count(True) >= self.special:
failures.append(translate("Must include at least %s of the following : %s") \
failures.append(translate("Must include at least %s of the following: %s") \
% (self.special, self.specials))
if self.invalid:
all_invalid = [ch in value for ch in self.invalid]
@@ -2801,6 +2876,8 @@ class IS_STRONG(object):
if len(failures) == 0:
return (value, None)
if not self.error_message:
if self.estring:
return (value, '|'.join(failures))
from html import XML
return (value, XML('<br />'.join(failures)))
else:
+39 -26
View File
@@ -3,32 +3,45 @@
## launch with python web2py.py -S myapp -R scripts/zip_static_files.py
ALLOWED_EXTS = ['.css', '.js']
import os
import gzip
static_path = os.path.abspath(os.path.join(request.folder, 'static'))
filelist = []
for root, dir, files in os.walk(static_path):
for file in files:
filelist.append(os.path.join(root, file))
tsave = 0
for fi in filelist:
extension = os.path.splitext(fi)
extension = len(extension) > 1 and extension[1] or None
if not extension or extension not in ALLOWED_EXTS:
print 'skipping %s' % os.path.basename(fi)
continue
fstats = os.stat(fi)
atime, mtime = fstats.st_atime, fstats.st_mtime
gfi = fi + '.gz'
print 'gzipping %s to %s' % (os.path.basename(fi), os.path.basename(gfi))
f_in = open(fi, 'rb')
f_out = gzip.open(gfi, 'wb')
f_out.writelines(f_in)
f_out.close()
f_in.close()
os.utime(gfi, (atime,mtime))
saved = fstats.st_size - os.stat(gfi).st_size
tsave+= saved
print 'saved %s KB' % (int(tsave)/1000.0)
def zip_static(filelist=[]):
tsave = 0
for fi in filelist:
extension = os.path.splitext(fi)
extension = len(extension) > 1 and extension[1] or None
if not extension or extension not in ALLOWED_EXTS:
print 'skipping %s' % os.path.basename(fi)
continue
fstats = os.stat(fi)
atime, mtime = fstats.st_atime, fstats.st_mtime
gfi = fi + '.gz'
if os.path.isfile(gfi):
zstats = os.stat(gfi)
zatime, zmtime = zstats.st_atime, zstats.st_mtime
if zatime == atime and zmtime == mtime:
print 'skipping %s, already gzipped to the latest version' % os.path.basename(fi)
continue
print 'gzipping %s to %s' % (os.path.basename(fi), os.path.basename(gfi))
f_in = open(fi, 'rb')
f_out = gzip.open(gfi, 'wb')
f_out.writelines(f_in)
f_out.close()
f_in.close()
os.utime(gfi, (atime,mtime))
saved = fstats.st_size - os.stat(gfi).st_size
tsave+= saved
print 'saved %s KB' % (int(tsave)/1000.0)
if __name__ == '__main__':
ALLOWED_EXTS = ['.css', '.js']
static_path = os.path.abspath(os.path.join(request.folder, 'static'))
filelist = []
for root, dir, files in os.walk(static_path):
for file in files:
filelist.append(os.path.join(root, file))
zip_static(filelist)