Merge branch 'master' of github.com:web2py/web2py

This commit is contained in:
Michele Comitini
2012-07-21 01:02:53 +02:00
13 changed files with 179 additions and 85 deletions
+1 -1
View File
@@ -1 +1 @@
Version 2.00.0 (2012-07-19 16:56:40) dev Version 2.00.0 (2012-07-20 17:37:48) dev
+3 -3
View File
@@ -392,7 +392,7 @@ def peek():
app = get_app(request.vars.app) app = get_app(request.vars.app)
filename = '/'.join(request.args) filename = '/'.join(request.args)
if request.vars.app: if request.vars.app:
path = abspath(filename, gluon=False) path = abspath(filename)
else: else:
path = apath(filename, r=request) path = apath(filename, r=request)
try: try:
@@ -696,7 +696,7 @@ def edit_language():
s = strings[key] s = strings[key]
(prefix, sep, key) = key.partition('\x01') (prefix, sep, key) = key.partition('\x01')
if sep: if sep:
prefix = SPAN(prefix+': ', _style='color: blue;') prefix = SPAN(prefix+': ', _class='tm_ftag')
k = key k = key
else: else:
(k, prefix) = (prefix, '') (k, prefix) = (prefix, '')
@@ -1034,7 +1034,7 @@ def create_file():
anchor='#'+request.vars.id if request.vars.id else '' anchor='#'+request.vars.id if request.vars.id else ''
if request.vars.app: if request.vars.app:
app = get_app(request.vars.app) app = get_app(request.vars.app)
path = abspath(request.vars.location, gluon=False) path = abspath(request.vars.location)
else: else:
app = get_app(name=request.vars.location.split('/')[0]) app = get_app(name=request.vars.location.split('/')[0])
path = apath(request.vars.location, r=request) path = apath(request.vars.location, r=request)
+1
View File
@@ -1248,3 +1248,4 @@ color: #222;
.error, .error a {color:red} .error, .error a {color:red}
.pluralsform thead td {font-weight:bold; font-size:1.2em; padding-bottom:5px} .pluralsform thead td {font-weight:bold; font-size:1.2em; padding-bottom:5px}
.pluralsform td {padding-left:5px} .pluralsform td {padding-left:5px}
.tm_ftag {color:blue}
@@ -13,6 +13,9 @@ def index():
""" """
example action using the internationalization operator T and flash example action using the internationalization operator T and flash
rendered by views/default/index.html or views/generic.html rendered by views/default/index.html or views/generic.html
if you need a simple wiki simple replace the two lines below with:
return auth.wiki()
""" """
response.flash = T("Welcome to web2py!") response.flash = T("Welcome to web2py!")
return dict(message=T('Hello World')) return dict(message=T('Hello World'))
+1 -1
View File
@@ -40,7 +40,7 @@ response.generic_patterns = ['*'] if request.is_local else []
######################################################################### #########################################################################
from gluon.tools import Auth, Crud, Service, PluginManager, prettydate from gluon.tools import Auth, Crud, Service, PluginManager, prettydate
auth = Auth(db, hmac_key=Auth.get_or_create_key(), salt=True) auth = Auth(db)
crud, service, plugins = Crud(db), Service(), PluginManager() crud, service, plugins = Crud(db), Service(), PluginManager()
## create all tables needed by auth if not custom tables ## create all tables needed by auth if not custom tables
File diff suppressed because one or more lines are too long
+49 -21
View File
@@ -667,19 +667,19 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo
'<p>[[probe]]</p>' '<p>[[probe]]</p>'
>>> render(r"\\\\[[probe]]") >>> render(r"\\\\[[probe]]")
'<p>\\\\<span id="probe"></span></p>' '<p>\\\\<a name="markmin_probe"></a></p>'
>>> render(r"\\\\\\[[probe]]") >>> render(r"\\\\\\[[probe]]")
'<p>\\\\[[probe]]</p>' '<p>\\\\[[probe]]</p>'
>>> render(r"\\\\\\\\[[probe]]") >>> render(r"\\\\\\\\[[probe]]")
'<p>\\\\\\\\<span id="probe"></span></p>' '<p>\\\\\\\\<a name="markmin_probe"></a></p>'
>>> render(r"\\\\\\\\\[[probe]]") >>> render(r"\\\\\\\\\[[probe]]")
'<p>\\\\\\\\[[probe]]</p>' '<p>\\\\\\\\[[probe]]</p>'
>>> render(r"\\\\\\\\\\\[[probe]]") >>> render(r"\\\\\\\\\\\[[probe]]")
'<p>\\\\\\\\\\\\<span id="probe"></span></p>' '<p>\\\\\\\\\\\\<a name="markmin_probe"></a></p>'
>>> render("``[[ [\\[[probe\]\\]] URL\\[x\\]]]``:red[dummy_params]") >>> render("``[[ [\\[[probe\]\\]] URL\\[x\\]]]``:red[dummy_params]")
'<span style="color: red"><a href="URL[x]" title="[[probe]]">URL[x]</a></span>' '<span style="color: red"><a href="URL[x]" title="[[probe]]">URL[x]</a></span>'
@@ -725,6 +725,10 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo
>>> render("**@{probe}**", environment=dict(probe="this is a test")) >>> render("**@{probe}**", environment=dict(probe="this is a test"))
'<p><strong>this is a test</strong></p>' '<p><strong>this is a test</strong></p>'
>>> render('[[id1 [span **messag** in ''markmin''] ]] ... [[**link** to id [link\\\'s title] #mark1]]')
'<p><a name="markmin_id1">span <strong>messag</strong> in markmin</a> ... <a href="#markmin_mark1" title="link\\\'s title"><strong>link</strong> to id</a></p>'
""" """
text = str(text or '') text = str(text or '')
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text) text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
@@ -855,7 +859,7 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo
""" paragraphs in lists """ """ paragraphs in lists """
lent=len(t) lent=len(t)
if lent>lev: if lent>lev:
return parse_list(t, '.', s, 'ul', lev, mtag) return parse_list(t, '.', s, 'ul', lev, mtag, lineno)
elif lent<lev: elif lent<lev:
while ltags[-1]>lent: while ltags[-1]>lent:
ltags.pop() ltags.pop()
@@ -986,7 +990,9 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo
URL, URL,
environment, environment,
latex, latex,
auto) auto,
class_prefix,
id_prefix)
) )
mtag='q' mtag='q'
else: else:
@@ -1100,7 +1106,8 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo
elif p in ('left','right'): elif p in ('left','right'):
style = ' style="float:%s"' % p style = ' style="float:%s"' % p
if p in ('video','audio'): if p in ('video','audio'):
t = render(t, {}, {}, 'br', URL, environment, latex, auto) t = render(t, {}, {}, 'br', URL, environment, latex,
auto, class_prefix, id_prefix)
return '<%(p)s controls="controls"%(title)s%(width)s><source src="%(k)s" />%(t)s</%(p)s>' \ return '<%(p)s controls="controls"%(title)s%(width)s><source src="%(k)s" />%(t)s</%(p)s>' \
% dict(p=p, title=title, width=width, k=k, t=t) % dict(p=p, title=title, width=width, k=k, t=t)
alt = ' alt="%s"'%escape(t).replace(META, DISABLED_META) if t else '' alt = ' alt="%s"'%escape(t).replace(META, DISABLED_META) if t else ''
@@ -1115,13 +1122,19 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo
t = t or '' t = t or ''
a = escape(a) if a else '' a = escape(a) if a else ''
if k: if k:
if k.startswith('#'):
k = '#'+id_prefix+k[1:]
k = escape(k) k = escape(k)
title = ' title="%s"' % a.replace(META, DISABLED_META) if a else '' title = ' title="%s"' % a.replace(META, DISABLED_META) if a else ''
target = ' target="_blank"' if p == 'popup' else '' target = ' target="_blank"' if p == 'popup' else ''
t = render(t, {}, {}, 'br', URL, environment, latex, auto) if t else k t = render(t, {}, {}, 'br', URL, environment, latex, auto,
class_prefix, id_prefix) if t else k
return '<a href="%(k)s"%(title)s%(target)s>%(t)s</a>' \ return '<a href="%(k)s"%(title)s%(target)s>%(t)s</a>' \
% dict(k=k, title=title, target=target, t=t) % dict(k=k, title=title, target=target, t=t)
return '<span id="%s">%s</span>' % (escape(t),a) return '<a name="%s">%s</a>' % (escape(id_prefix+t),
render(a, {},{},'br', URL,
environment, latex, auto,
class_prefix, id_prefix))
parts = text.split(LINK) parts = text.split(LINK)
text = parts[0] text = parts[0]
@@ -1183,21 +1196,36 @@ def markmin2html(text, extra={}, allowed={}, sep='p', auto=True):
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
import doctest import doctest
from textwrap import dedent
html=dedent("""
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
%(style)s
<title>%(title)s</title>
</head>
<body>
%(body)s
</body>
</html>""")[1:]
if sys.argv[1:2] == ['-h']: if sys.argv[1:2] == ['-h']:
print """<html><body> style=dedent("""
<style> <style>
blockquote { background-color: lime; } blockquote { background-color: lime; }
thead { color: white; background-color: gray; text-align: center; } thead { color: white; background-color: gray; text-align: center; }
tfoot { color: white; background-color: gray; } tfoot { color: white; background-color: gray; }
.tableclass1 { background-color: yellow; }
.tableclass1 thead { color: yellow; background-color: green; }
.tableclass1 tfoot { color: yellow; background-color: green; }
.tableclass1 { background-color: yellow; } td.num { text-align: right; }
.tableclass1 thead { color: yellow; background-color: green; } pre { background-color: #E0E0E0; }
.tableclass1 tfoot { color: yellow; background-color: green; } </style>""")[1:]
td.num { text-align: right; } print html % dict(title="Markmin markup language", style=style, body=markmin2html(__doc__))
pre { background-color: #E0E0E0; }
</style>
"""+markmin2html(__doc__)+'</body></html>'
elif sys.argv[1:2] == ['-t']: elif sys.argv[1:2] == ['-t']:
from timeit import Timer from timeit import Timer
loops=1000 loops=1000
@@ -1208,7 +1236,7 @@ if __name__ == '__main__':
elif len(sys.argv) > 1: elif len(sys.argv) > 1:
fargv = open(sys.argv[1],'r') fargv = open(sys.argv[1],'r')
try: try:
print '<html><body>'+markmin2html(fargv.read())+'</body></html>' print html % dict(title=sys.argv[1], style='', body=markmin2html(fargv.read()))
finally: finally:
fargv.close() fargv.close()
else: else:
+5 -1
View File
@@ -1800,6 +1800,7 @@ class BaseAdapter(ConnectionPool):
query = query & newquery query = query & newquery
return query return query
################################################################################### ###################################################################################
# List of all the available adapters; they all extend BaseAdapter. # List of all the available adapters; they all extend BaseAdapter.
################################################################################### ###################################################################################
@@ -8593,7 +8594,10 @@ class Rows(object):
""" """
returns a list of sorted elements (not sorted in place) returns a list of sorted elements (not sorted in place)
""" """
return Rows(self.db,sorted(self,key=f,reverse=reverse),self.colnames) rows = Rows(self.db,[],self.colnames,compact=False)
rows.records = sorted(self,key=f,reverse=reverse)
return rows
def group_by_value(self, field): def group_by_value(self, field):
""" """
+4 -4
View File
@@ -880,7 +880,7 @@ class Auth(object):
def here(self): def here(self):
return URL(args=current.request.args,vars=current.request.vars) return URL(args=current.request.args,vars=current.request.vars)
def __init__(self, environment=None, db=None, mailer=True, salt = False, def __init__(self, environment=None, db=None, mailer=True,
hmac_key=None, controller='default', function='user', cas_provider=None): hmac_key=None, controller='default', function='user', cas_provider=None):
""" """
auth=Auth(db) auth=Auth(db)
@@ -922,7 +922,6 @@ class Auth(object):
settings.hideerror = False settings.hideerror = False
settings.password_min_length = 4 settings.password_min_length = 4
settings.salt = salt
settings.cas_domains = [request.env.http_host] settings.cas_domains = [request.env.http_host]
settings.cas_provider = cas_provider settings.cas_provider = cas_provider
settings.cas_actions = {'login':'login', settings.cas_actions = {'login':'login',
@@ -1198,7 +1197,8 @@ class Auth(object):
if URL() == action: if URL() == action:
next = '' next = ''
else: else:
next = '?_next=' + urllib.quote(URL(args=request.args, vars=request.get_vars)) next = '?_next=' + urllib.quote(URL(args=request.args,
vars=request.get_vars))
href = lambda function: '%s/%s%s' % (action, function, href = lambda function: '%s/%s%s' % (action, function,
next if referrer_actions is DEFAULT or function in referrer_actions else '') next if referrer_actions is DEFAULT or function in referrer_actions else '')
@@ -1409,7 +1409,7 @@ class Auth(object):
table.last_name.requires = \ table.last_name.requires = \
IS_NOT_EMPTY(error_message=self.messages.is_empty) IS_NOT_EMPTY(error_message=self.messages.is_empty)
table[passfield].requires = [ table[passfield].requires = [
CRYPT(key=settings.hmac_key,salt=settings.salt, CRYPT(key=settings.hmac_key,
min_length=settings.password_min_length)] min_length=settings.password_min_length)]
table.email.requires = \ table.email.requires = \
[IS_EMAIL(error_message=self.messages.invalid_email), [IS_EMAIL(error_message=self.messages.invalid_email),
+24 -23
View File
@@ -15,7 +15,7 @@ import __builtin__
__all__ = ['Utf8'] __all__ = ['Utf8']
repr_escape_tab={} repr_escape_tab={}
for i in xrange(1,32): repr_escape_tab[i]=ur'\x%02i'%i for i in range(1,32): repr_escape_tab[i]=ur'\x%02x'%i
repr_escape_tab[7]=u'\\a' repr_escape_tab[7]=u'\\a'
repr_escape_tab[8]=u'\\b' repr_escape_tab[8]=u'\\b'
repr_escape_tab[9]=u'\\t' repr_escape_tab[9]=u'\\t'
@@ -71,6 +71,24 @@ def size(string):
""" """
return Utf8(string).__size__() return Utf8(string).__size__()
def truncate(string, length, dots='...'):
""" returns string of length < *length* or truncate
string with adding *dots* suffix to the string's end
args:
length (int): max length of string
dots (str or unicode): string suffix, when string is cutted
returns:
(utf8-str): original or cutted string
"""
text = unicode(string, 'utf-8')
dots = unicode(dots, 'utf-8') if isinstance(dots, str) else dots
if len(text) > length:
text = text[:length-len(dots)] + dots
return str.__new__(Utf8, text.encode('utf-8'))
class Utf8(str): class Utf8(str):
""" """
Class for utf8 string storing and manipulations Class for utf8 string storing and manipulations
@@ -131,23 +149,6 @@ class Utf8(str):
else: else:
return "'"+unicode(self, 'utf-8').translate(repr_escape_tab2).encode('utf-8')+"'" return "'"+unicode(self, 'utf-8').translate(repr_escape_tab2).encode('utf-8')+"'"
def truncate(self, length, dots='...'):
""" returns string of length < *length* or truncate
string with adding *dots* suffix to the string's end
args:
length (int): max length of string
dots (str or unicode): string suffix, when string is cutted
returns:
(utf8-str): original or cutted string
"""
text = unicode(self, 'utf-8')
dots = unicode(dots, 'utf-8') if isinstance(dots, str) else dots
if len(text) > length:
text = text[:length-len(dots)] + dots
return str.__new__(Utf8, text.encode('utf-8'))
def __size__(self): def __size__(self):
""" length of utf-8 string in bytes """ """ length of utf-8 string in bytes """
return str.__len__(self) return str.__len__(self)
@@ -419,15 +420,15 @@ if __name__ == '__main__':
'прОБА є prOBE' 'прОБА є prOBE'
>>> type(s.swapcase()) >>> type(s.swapcase())
<class '__main__.Utf8'> <class '__main__.Utf8'>
>>> s.truncate(10) >>> truncate(s, 10)
'ПРоба Є...' 'ПРоба Є...'
>>> s.truncate(20) >>> truncate(s, 20)
'ПРоба Є PRobe' 'ПРоба Є PRobe'
>>> s.truncate(10, '•••') # utf-8 string as *dots* >>> truncate(s, 10, '•••') # utf-8 string as *dots*
'ПРоба Є•••' 'ПРоба Є•••'
>>> s.truncate(10, u'®') # you can use unicode string as *dots* >>> truncate(s, 10, u'®') # you can use unicode string as *dots*
'ПРоба Є P®' 'ПРоба Є P®'
>>> type(s.truncate(10)) >>> type(truncate(s, 10))
<class '__main__.Utf8'> <class '__main__.Utf8'>
>>> Utf8(s.encode('koi8-u'), 'koi8-u') >>> Utf8(s.encode('koi8-u'), 'koi8-u')
'ПРоба Є PRobe' 'ПРоба Є PRobe'
+8 -8
View File
@@ -16,7 +16,7 @@ import random
import time import time
import os import os
import logging import logging
from gluon.contrib.pbkdf2 import pbkdf2_hex from contrib.pbkdf2 import pbkdf2_hex
logger = logging.getLogger("web2py") logger = logging.getLogger("web2py")
@@ -69,15 +69,15 @@ def get_digest(value):
elif value == "sha512": elif value == "sha512":
return hashlib.sha512 return hashlib.sha512
else: else:
raise ValueError("Invalid digest algorithm") raise ValueError("Invalid digest algorithm: %s" % value)
DIGEST_ALG_BY_SIZE = { DIGEST_ALG_BY_SIZE = {
128/16: 'md5', 128/4: 'md5',
160/16: 'sha1', 160/4: 'sha1',
224/16: 'sha224', 224/4: 'sha224',
256/16: 'sha256', 256/4: 'sha256',
384/16: 'sha384', 384/4: 'sha384',
512/16: 'sha512', 512/4: 'sha512',
} }
def hmac_hash(value, salt, digest_alg='md5'): def hmac_hash(value, salt, digest_alg='md5'):
+56 -7
View File
@@ -130,7 +130,7 @@ class IS_MATCH(Validator):
('hello', None) ('hello', None)
>>> IS_MATCH('hell')('hello') >>> IS_MATCH('hell')('hello')
('hello', 'invalid expression') ('hello', None)
>>> IS_MATCH('hell.*', strict=False)('hello') >>> IS_MATCH('hell.*', strict=False)('hello')
('hello', None) ('hello', None)
@@ -139,10 +139,10 @@ class IS_MATCH(Validator):
('shello', 'invalid expression') ('shello', 'invalid expression')
>>> IS_MATCH('hello', search=True)('shello') >>> IS_MATCH('hello', search=True)('shello')
('hello', None) ('shello', None)
>>> IS_MATCH('hello', search=True, strict=False)('shellox') >>> IS_MATCH('hello', search=True, strict=False)('shellox')
('hello', None) ('shellox', None)
>>> IS_MATCH('.*hello.*', search=True, strict=False)('shellox') >>> IS_MATCH('.*hello.*', search=True, strict=False)('shellox')
('shellox', None) ('shellox', None)
@@ -2579,7 +2579,13 @@ class LazyCrypt(object):
""" """
compares the current lazy crypted password with a stored password compares the current lazy crypted password with a stored password
""" """
key = self.crypt.key.split(':')[1] if ':' in self.crypt.key else '' if self.crypt.key:
if ':' in self.crypt.key:
key = self.crypt.key.split(':')[1]
else:
key = self.crypt.key
else:
key = ''
if stored_password.count('$')==2: if stored_password.count('$')==2:
(digest_alg, salt, hash) = stored_password.split('$') (digest_alg, salt, hash) = stored_password.split('$')
masterkey = key+salt masterkey = key+salt
@@ -2629,13 +2635,55 @@ class CRYPT(object):
Important: hashed password is returned as a LazyCrypt object and computed only if needed. Important: hashed password is returned as a LazyCrypt object and computed only if needed.
The LasyCrypt object also knows how to compare itself with an existing salted password The LasyCrypt object also knows how to compare itself with an existing salted password
Supports standard algorithms
>>> for alg in ('md5','sha1','sha256','sha384','sha512'):
... print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
md5$...$...
sha1$...$...
sha256$...$...
sha384$...$...
sha512$...$...
The syntax is always alg$salt$hash
Supports for pbkdf2
>>> alg = 'pbkdf2(1000,20,sha512)'
>>> print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
pbkdf2(1000,20,sha512)$...$...
An optional hmac_key can be specified and it is used as salt prefix
>>> a = str(CRYPT(digest_alg='md5',key='mykey',salt=True)('test')[0])
>>> print a
md5$...$...
Even if the algorithm changes the hash can still be validated
>>> CRYPT(digest_alg='sha1',key='mykey',salt=True)('test')[0] == a
True
If no salt is specified CRYPT can guess the algorithms from length:
>>> a = str(CRYPT(digest_alg='sha1',salt=False)('test')[0])
>>> a
'sha1$$a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'
>>> CRYPT(digest_alg='sha1',salt=False)('test')[0] == a
True
>>> CRYPT(digest_alg='sha1',salt=False)('test')[0] == a[6:]
True
>>> CRYPT(digest_alg='md5',salt=False)('test')[0] == a
True
>>> CRYPT(digest_alg='md5',salt=False)('test')[0] == a[6:]
True
""" """
def __init__(self, def __init__(self,
key=None, key=None,
digest_alg='md5', digest_alg='pbkdf2(1000,20,sha512)',
min_length=0, min_length=0,
error_message='too short', salt=None): error_message='too short', salt=True):
""" """
important, digest_alg='md5' is not the default hashing algorithm for important, digest_alg='md5' is not the default hashing algorithm for
web2py. This is only an example of usage of this function. web2py. This is only an example of usage of this function.
@@ -3093,7 +3141,8 @@ class IS_IPV4(Validator):
if __name__ == '__main__': if __name__ == '__main__':
import doctest import doctest
doctest.testmod() doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
+2 -1
View File
@@ -9,6 +9,7 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
The widget is called from web2py. The widget is called from web2py.
""" """
import datetime
import sys import sys
import cStringIO import cStringIO
import time import time
@@ -41,7 +42,7 @@ except NameError:
BaseException = Exception BaseException = Exception
ProgramName = 'web2py Web Framework' ProgramName = 'web2py Web Framework'
ProgramAuthor = 'Created by Massimo Di Pierro, Copyright 2007-2011' ProgramAuthor = 'Created by Massimo Di Pierro, Copyright 2007-' + str(datetime.datetime.now().year)
ProgramVersion = read_file('VERSION').strip() ProgramVersion = read_file('VERSION').strip()
ProgramInfo = '''%s ProgramInfo = '''%s