diff --git a/VERSION b/VERSION index d44d196a..45616fdb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-19 16:56:40) dev +Version 2.00.0 (2012-07-20 17:37:48) dev diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 102cac82..711f0330 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -392,7 +392,7 @@ def peek(): app = get_app(request.vars.app) filename = '/'.join(request.args) if request.vars.app: - path = abspath(filename, gluon=False) + path = abspath(filename) else: path = apath(filename, r=request) try: @@ -696,7 +696,7 @@ def edit_language(): s = strings[key] (prefix, sep, key) = key.partition('\x01') if sep: - prefix = SPAN(prefix+': ', _style='color: blue;') + prefix = SPAN(prefix+': ', _class='tm_ftag') k = key else: (k, prefix) = (prefix, '') @@ -1034,7 +1034,7 @@ def create_file(): anchor='#'+request.vars.id if request.vars.id else '' if request.vars.app: app = get_app(request.vars.app) - path = abspath(request.vars.location, gluon=False) + path = abspath(request.vars.location) else: app = get_app(name=request.vars.location.split('/')[0]) path = apath(request.vars.location, r=request) diff --git a/applications/admin/static/css/styles.css b/applications/admin/static/css/styles.css index f75bdfe1..8375f145 100644 --- a/applications/admin/static/css/styles.css +++ b/applications/admin/static/css/styles.css @@ -1248,3 +1248,4 @@ color: #222; .error, .error a {color:red} .pluralsform thead td {font-weight:bold; font-size:1.2em; padding-bottom:5px} .pluralsform td {padding-left:5px} +.tm_ftag {color:blue} diff --git a/applications/welcome/controllers/default.py b/applications/welcome/controllers/default.py index f449da20..4c4b563d 100644 --- a/applications/welcome/controllers/default.py +++ b/applications/welcome/controllers/default.py @@ -13,6 +13,9 @@ def index(): """ example action using the internationalization operator T and flash 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!") return dict(message=T('Hello World')) diff --git a/applications/welcome/models/db.py b/applications/welcome/models/db.py index 980084c9..26d28122 100644 --- a/applications/welcome/models/db.py +++ b/applications/welcome/models/db.py @@ -40,7 +40,7 @@ response.generic_patterns = ['*'] if request.is_local else [] ######################################################################### 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() ## create all tables needed by auth if not custom tables diff --git a/gluon/contrib/markmin/markmin.html b/gluon/contrib/markmin/markmin.html index d68ae23a..450d599b 100644 --- a/gluon/contrib/markmin/markmin.html +++ b/gluon/contrib/markmin/markmin.html @@ -1,17 +1,22 @@ - - -

Markmin markup language

About

This is a new markup language that we call markmin designed to produce high quality scientific papers and books and also put them online. We provide serializers for html, latex and pdf. It is implemented in the markmin2html function in the markmin2html.py.

Example of usage:

m = "Hello **world** [[link http://web2py.com]]"
+  td.num { text-align: right; }
+  pre { background-color: #E0E0E0; }
+
+Markmin markup language
+
+
+

Markmin markup language

About

This is a new markup language that we call markmin designed to produce high quality scientific papers and books and also put them online. We provide serializers for html, latex and pdf. It is implemented in the markmin2html function in the markmin2html.py.

Example of usage:

m = "Hello **world** [[link http://web2py.com]]"
 from markmin2html import markmin2html
 print markmin2html(m)
 from markmin2latex import markmin2latex
@@ -24,7 +29,7 @@ line 2
   line 3
This item finishes with this paragraph.

Item in sublevel 3 can be continued with paragraphs.

  this is another
 code block
     in the
-  sublevel 3 item
  1. The last item in sublevel 3

This is a continuous paragraph for item 2 in sublevel 2. You can use such structure to create difficult structured documents.

  • item 3 in sublevel 2
    1. item 1 in sublevel 2 (new ordered list)
    2. item 2 in sublevel 2
    3. item 3 in sublevle 2
  • item 2 in level 1
  • item 3 in level 1
  • This is the last section of the test

    Single paragraph with '----' in it will be turned into separator:


    And this is the last paragraph in the test. Be happy!

    ====================

    Why?

    We wanted a markup language with the following requirements:

    (results depend on text but in average for text ~100K markmin is 30% faster than markdown, for text ~10K it is 10x faster)

    The web2py book published by lulu, for example, was entirely generated with markmin2pdf from the online web2py wiki

    Download

    markmin2html.py and markmin2latex.py are single files and have no web2py dependence. Their license is BSD.

    Examples

    Bold, italic, code and links

    SOURCEOUTPUT
    # titletitle
    ## sectionsection
    ### subsectionsubsection
    **bold**bold
    ''italic''italic
    ~~strikeout~~strikeout
    ``verbatim``verbatim
    ``color with **bold**``:redcolor with bold
    ``many colors``:color[blue:#ffff00]many colors
    http://google.comhttp://google.com
    [[**click** me #myanchor]]click me
    [[click me [extra info] #myanchor popup]]click me

    More on links

    The format is always [[title link]] or [[title [extra] link]]. Notice you can nest bold, italic, strikeout and code inside the link title.

    Anchors

    You can place an anchor anywhere in the text using the syntax [[name]] where name is the name of the anchor. You can then link the anchor with link, i.e. [[link #myanchor]] or link with an extra info, i.e. [[link with an extra info [extra info] #myanchor]].

    Images

    alt-string for the image This paragraph has an image aligned to the right with a width of 200px. Its is placed using the code

    [[alt-string for the image [the image title] http://www.web2py.com/examples/static/web2py_logo.png right 200px]].

    Unordered Lists

    - Dog
    +  sublevel 3 item
    1. The last item in sublevel 3

    This is a continuous paragraph for item 2 in sublevel 2. You can use such structure to create difficult structured documents.

  • item 3 in sublevel 2
    1. item 1 in sublevel 2 (new ordered list)
    2. item 2 in sublevel 2
    3. item 3 in sublevle 2
  • item 2 in level 1
  • item 3 in level 1
  • This is the last section of the test

    Single paragraph with '----' in it will be turned into separator:


    And this is the last paragraph in the test. Be happy!

    ====================

    Why?

    We wanted a markup language with the following requirements:

    (results depend on text but in average for text ~100K markmin is 30% faster than markdown, for text ~10K it is 10x faster)

    The web2py book published by lulu, for example, was entirely generated with markmin2pdf from the online web2py wiki

    Download

    markmin2html.py and markmin2latex.py are single files and have no web2py dependence. Their license is BSD.

    Examples

    Bold, italic, code and links

    SOURCEOUTPUT
    # titletitle
    ## sectionsection
    ### subsectionsubsection
    **bold**bold
    ''italic''italic
    ~~strikeout~~strikeout
    ``verbatim``verbatim
    ``color with **bold**``:redcolor with bold
    ``many colors``:color[blue:#ffff00]many colors
    http://google.comhttp://google.com
    [[**click** me #myanchor]]click me
    [[click me [extra info] #myanchor popup]]click me

    More on links

    The format is always [[title link]] or [[title [extra] link]]. Notice you can nest bold, italic, strikeout and code inside the link title.

    Anchors

    You can place an anchor anywhere in the text using the syntax [[name]] where name is the name of the anchor. You can then link the anchor with link, i.e. [[link #myanchor]] or link with an extra info, i.e. [[link with an extra info [extra info] #myanchor]].

    Images

    alt-string for the image This paragraph has an image aligned to the right with a width of 200px. Its is placed using the code

    [[alt-string for the image [the image title] http://www.web2py.com/examples/static/web2py_logo.png right 200px]].

    Unordered Lists

    - Dog
     - Cat
     - Mouse

    is rendered as

    Two new lines between items break the list in two lists.

    Ordered Lists

    + Dog
     + Cat
    @@ -79,4 +84,6 @@ markmin2html(text,{'latex':lambda code: LATEX % code.replace('"','\"')})<
     
     ## References
     
    -- [[mdipierro]] web2py Manual, 3rd Edition, lulu.com

    Caveats

    <ul/>, <ol/>, <code/>, <table/>, <blockquote/>, <h1/>, ..., <h6/> do not have <p>...</p> around them.

    +- [[mdipierro]] web2py Manual, 3rd Edition, lulu.com

    Caveats

    <ul/>, <ol/>, <code/>, <table/>, <blockquote/>, <h1/>, ..., <h6/> do not have <p>...</p> around them.

    + + diff --git a/gluon/contrib/markmin/markmin2html.py b/gluon/contrib/markmin/markmin2html.py index 2b305608..2c6da1e7 100755 --- a/gluon/contrib/markmin/markmin2html.py +++ b/gluon/contrib/markmin/markmin2html.py @@ -667,19 +667,19 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo '

    [[probe]]

    ' >>> render(r"\\\\[[probe]]") - '

    \\\\

    ' + '

    \\\\

    ' >>> render(r"\\\\\\[[probe]]") '

    \\\\[[probe]]

    ' >>> render(r"\\\\\\\\[[probe]]") - '

    \\\\\\\\

    ' + '

    \\\\\\\\

    ' >>> render(r"\\\\\\\\\[[probe]]") '

    \\\\\\\\[[probe]]

    ' >>> render(r"\\\\\\\\\\\[[probe]]") - '

    \\\\\\\\\\\\

    ' + '

    \\\\\\\\\\\\

    ' >>> render("``[[ [\\[[probe\]\\]] URL\\[x\\]]]``:red[dummy_params]") 'URL[x]' @@ -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")) '

    this is a test

    ' + + >>> render('[[id1 [span **messag** in ''markmin''] ]] ... [[**link** to id [link\\\'s title] #mark1]]') + '

    span messag in markmin ... link to id

    ' + """ text = str(text or '') 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 """ lent=len(t) if lent>lev: - return parse_list(t, '.', s, 'ul', lev, mtag) + return parse_list(t, '.', s, 'ul', lev, mtag, lineno) elif lentlent: ltags.pop() @@ -986,7 +990,9 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo URL, environment, latex, - auto) + auto, + class_prefix, + id_prefix) ) mtag='q' else: @@ -1100,7 +1106,8 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo elif p in ('left','right'): style = ' style="float:%s"' % p 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>%(t)s' \ % dict(p=p, title=title, width=width, k=k, t=t) 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 '' a = escape(a) if a else '' if k: + if k.startswith('#'): + k = '#'+id_prefix+k[1:] k = escape(k) title = ' title="%s"' % a.replace(META, DISABLED_META) if a 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 '%(t)s' \ % dict(k=k, title=title, target=target, t=t) - return '%s' % (escape(t),a) + return '%s' % (escape(id_prefix+t), + render(a, {},{},'br', URL, + environment, latex, auto, + class_prefix, id_prefix)) parts = text.split(LINK) text = parts[0] @@ -1183,21 +1196,36 @@ def markmin2html(text, extra={}, allowed={}, sep='p', auto=True): if __name__ == '__main__': import sys import doctest + from textwrap import dedent + + html=dedent(""" + + + + + %(style)s + %(title)s + + + %(body)s + + """)[1:] + if sys.argv[1:2] == ['-h']: - print """ - """)[1:] - td.num { text-align: right; } - pre { background-color: #E0E0E0; } - - """+markmin2html(__doc__)+'' + print html % dict(title="Markmin markup language", style=style, body=markmin2html(__doc__)) elif sys.argv[1:2] == ['-t']: from timeit import Timer loops=1000 @@ -1208,7 +1236,7 @@ if __name__ == '__main__': elif len(sys.argv) > 1: fargv = open(sys.argv[1],'r') try: - print ''+markmin2html(fargv.read())+'' + print html % dict(title=sys.argv[1], style='', body=markmin2html(fargv.read())) finally: fargv.close() else: diff --git a/gluon/dal.py b/gluon/dal.py index c7322082..42d357e1 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -1800,6 +1800,7 @@ class BaseAdapter(ConnectionPool): query = query & newquery return query + ################################################################################### # 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) """ - 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): """ diff --git a/gluon/tools.py b/gluon/tools.py index 4eaafd8e..24467443 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -880,7 +880,7 @@ class Auth(object): def here(self): 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): """ auth=Auth(db) @@ -922,7 +922,6 @@ class Auth(object): settings.hideerror = False settings.password_min_length = 4 - settings.salt = salt settings.cas_domains = [request.env.http_host] settings.cas_provider = cas_provider settings.cas_actions = {'login':'login', @@ -1198,7 +1197,8 @@ class Auth(object): if URL() == action: next = '' 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, next if referrer_actions is DEFAULT or function in referrer_actions else '') @@ -1409,7 +1409,7 @@ class Auth(object): table.last_name.requires = \ IS_NOT_EMPTY(error_message=self.messages.is_empty) table[passfield].requires = [ - CRYPT(key=settings.hmac_key,salt=settings.salt, + CRYPT(key=settings.hmac_key, min_length=settings.password_min_length)] table.email.requires = \ [IS_EMAIL(error_message=self.messages.invalid_email), diff --git a/gluon/utf8.py b/gluon/utf8.py index e8baf75d..d21efb8f 100644 --- a/gluon/utf8.py +++ b/gluon/utf8.py @@ -15,7 +15,7 @@ import __builtin__ __all__ = ['Utf8'] 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[8]=u'\\b' repr_escape_tab[9]=u'\\t' @@ -71,6 +71,24 @@ def size(string): """ 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 for utf8 string storing and manipulations @@ -131,23 +149,6 @@ class Utf8(str): else: 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): """ length of utf-8 string in bytes """ return str.__len__(self) @@ -419,15 +420,15 @@ if __name__ == '__main__': 'прОБА є prOBE' >>> type(s.swapcase()) - >>> s.truncate(10) + >>> truncate(s, 10) 'ПРоба Є...' - >>> s.truncate(20) + >>> truncate(s, 20) 'ПРоба Є 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®' - >>> type(s.truncate(10)) + >>> type(truncate(s, 10)) >>> Utf8(s.encode('koi8-u'), 'koi8-u') 'ПРоба Є PRobe' diff --git a/gluon/utils.py b/gluon/utils.py index 64a714ab..d429b100 100644 --- a/gluon/utils.py +++ b/gluon/utils.py @@ -16,7 +16,7 @@ import random import time import os import logging -from gluon.contrib.pbkdf2 import pbkdf2_hex +from contrib.pbkdf2 import pbkdf2_hex logger = logging.getLogger("web2py") @@ -69,15 +69,15 @@ def get_digest(value): elif value == "sha512": return hashlib.sha512 else: - raise ValueError("Invalid digest algorithm") + raise ValueError("Invalid digest algorithm: %s" % value) DIGEST_ALG_BY_SIZE = { - 128/16: 'md5', - 160/16: 'sha1', - 224/16: 'sha224', - 256/16: 'sha256', - 384/16: 'sha384', - 512/16: 'sha512', + 128/4: 'md5', + 160/4: 'sha1', + 224/4: 'sha224', + 256/4: 'sha256', + 384/4: 'sha384', + 512/4: 'sha512', } def hmac_hash(value, salt, digest_alg='md5'): diff --git a/gluon/validators.py b/gluon/validators.py index 85d23539..a18099fb 100644 --- a/gluon/validators.py +++ b/gluon/validators.py @@ -130,7 +130,7 @@ class IS_MATCH(Validator): ('hello', None) >>> IS_MATCH('hell')('hello') - ('hello', 'invalid expression') + ('hello', None) >>> IS_MATCH('hell.*', strict=False)('hello') ('hello', None) @@ -139,10 +139,10 @@ class IS_MATCH(Validator): ('shello', 'invalid expression') >>> IS_MATCH('hello', search=True)('shello') - ('hello', None) + ('shello', None) >>> IS_MATCH('hello', search=True, strict=False)('shellox') - ('hello', None) + ('shellox', None) >>> IS_MATCH('.*hello.*', search=True, strict=False)('shellox') ('shellox', None) @@ -2579,7 +2579,13 @@ class LazyCrypt(object): """ 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: (digest_alg, salt, hash) = stored_password.split('$') masterkey = key+salt @@ -2629,13 +2635,55 @@ class CRYPT(object): 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 + 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, key=None, - digest_alg='md5', + digest_alg='pbkdf2(1000,20,sha512)', 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 web2py. This is only an example of usage of this function. @@ -3093,7 +3141,8 @@ class IS_IPV4(Validator): if __name__ == '__main__': import doctest - doctest.testmod() + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) + diff --git a/gluon/widget.py b/gluon/widget.py index f0f7278b..96ef1d6e 100644 --- a/gluon/widget.py +++ b/gluon/widget.py @@ -9,6 +9,7 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) The widget is called from web2py. """ +import datetime import sys import cStringIO import time @@ -41,7 +42,7 @@ except NameError: BaseException = Exception 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() ProgramInfo = '''%s