From 51d8932252988a9a2847ba4c03fcc4670dff0d7e Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 2 Sep 2012 14:52:52 -0500 Subject: [PATCH 01/19] allow navbar without define_tables, thanks Anthony --- VERSION | 2 +- gluon/main.py | 28 ++++------------------------ gluon/rewrite.py | 21 +++++++++++++++++++++ gluon/tools.py | 25 ++++++++++++++----------- 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/VERSION b/VERSION index 1150a441..852bce5d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.6 (2012-09-02 12:26:12) stable +Version 2.0.6 (2012-09-02 14:52:46) stable diff --git a/gluon/main.py b/gluon/main.py index 94610f41..e60a38ac 100644 --- a/gluon/main.py +++ b/gluon/main.py @@ -91,7 +91,8 @@ from validators import CRYPT from cache import Cache from html import URL, xmlescape from utils import is_valid_ip_address -from rewrite import load, url_in, thread as rwthread, try_rewrite_on_error +from rewrite import load, url_in, thread as rwthread, \ + try_rewrite_on_error, fixup_missing_path_info import newcron __all__ = ['wsgibase', 'save_password', 'appfactory', 'HttpServer'] @@ -382,26 +383,7 @@ def wsgibase(environ, responder): # serve file if static # ################################################## - eget = environ.get - if not eget('PATH_INFO') and eget('REQUEST_URI'): - # for fcgi, get path_info and - # query_string from request_uri - items = environ['REQUEST_URI'].split('?') - environ['PATH_INFO'] = items[0] - if len(items) > 1: - environ['QUERY_STRING'] = items[1] - else: - environ['QUERY_STRING'] = '' - elif not eget('REQUEST_URI'): - if eget('QUERY_STRING'): - environ['REQUEST_URI'] = eget('PATH_INFO') + '?' + eget('QUERY_STRING') - else: - environ['REQUEST_URI'] = eget('PATH_INFO') - if not eget('HTTP_HOST'): - environ['HTTP_HOST'] = \ - eget('SERVER_NAME') + ':' + eget('SERVER_PORT') - - + fixup_missing_path_info(environ) (static_file, environ) = url_in(request, environ) if static_file: @@ -438,6 +420,7 @@ def wsgibase(environ, responder): is_https = env.wsgi_url_scheme \ in ['https', 'HTTPS'] or env.https=='on') request.uuid = request.compute_uuid() # requires client + request.url = environ['PATH_INFO'] # ################################################## # access the requested application @@ -460,9 +443,6 @@ def wsgibase(environ, responder): elif not request.is_local and \ exists(pjoin(request.folder,'DISABLED')): raise HTTP(503, "

Temporarily down for maintenance

") - request.url = URL(r=request, - args=request.args, - extension=request.raw_extension) # ################################################## # build missing folders diff --git a/gluon/rewrite.py b/gluon/rewrite.py index c7a1b211..411a3b15 100644 --- a/gluon/rewrite.py +++ b/gluon/rewrite.py @@ -135,6 +135,27 @@ ROUTER_BASE_KEYS = set( # filter_err: helper for doctest & unittest # regex_filter_out: doctest +def fixup_missing_path_info(environ): + eget = environ.get + path_info = eget('PATH_INFO') + request_uri = eget('REQUEST_URI') + if not path_info and request_uri: + # for fcgi, get path_info and + # query_string from request_uri + items = request_uri.split('?') + path_info = environ['PATH_INFO'] = items[0] + environ['QUERY_STRING'] = items[1] if len(items) > 1 else '' + elif not request_uri: + query_string = eget('QUERY_STRING') + if query_string: + environ['REQUEST_URI'] = '%s?%s' % (path_info,query_string) + else: + environ['REQUEST_URI'] = path_info + if not eget('HTTP_HOST'): + environ['HTTP_HOST'] = \ + '%s:%s' % (eget('SERVER_NAME'),eget('SERVER_PORT')) + + def url_in(request, environ): "parse and rewrite incoming URL" if routers: diff --git a/gluon/tools.py b/gluon/tools.py index ecbc37e2..1492fd62 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -839,6 +839,7 @@ class Auth(object): table_event = None, table_cas = None, showid = False, + use_username = False, login_email_validate = True, login_userfield = None, logout_onlogout = None, @@ -1060,7 +1061,6 @@ class Auth(object): request = current.request session = current.session auth = session.auth - self.use_username = None # None means postpone detection self.user_groups = auth and auth.user_groups or {} if auth and auth.last_visit and auth.last_visit + \ datetime.timedelta(days=0, seconds=auth.expiration) > request.now: @@ -1251,21 +1251,21 @@ class Auth(object): else: login = A(T('Login'), _href=href('login')) register = A(T('Register'), _href=href('register')) - retrieve_username = A(T('Forgot username?'), _href=href('retrieve_username')) - lost_password = A(T('Lost password?'), _href=href('request_reset_password')) + retrieve_username = A( + T('Forgot username?'), _href=href('retrieve_username')) + lost_password = A( + T('Lost password?'), _href=href('request_reset_password')) bar = SPAN(s1, login, s3, _class='auth_navbar') if not 'register' in self.settings.actions_disabled: bar.insert(-1, s2) bar.insert(-1, register) - if self.use_username is None: - # should always be false if auth.define_tables() is called - self.use_username = 'username' in self.table_user().fields - if self.use_username and \ - not 'retrieve_username' in self.settings.actions_disabled: + if self.settings.use_username and not 'retrieve_username' \ + in self.settings.actions_disabled: bar.insert(-1, s2) bar.insert(-1, retrieve_username) - if not 'request_reset_password' in self.settings.actions_disabled: + if not 'request_reset_password' \ + in self.settings.actions_disabled: bar.insert(-1, s2) bar.insert(-1, lost_password) return bar @@ -1357,7 +1357,7 @@ class Auth(object): writable=False,readable=False, label=T('Modified By'))) - def define_tables(self, username=False, signature=None, + def define_tables(self, username=None, signature=None, migrate=True, fake_migrate=False): """ to be called unless tables are defined manually @@ -1375,7 +1375,10 @@ class Auth(object): db = self.db settings = self.settings - self.use_username = username + if username is None: + username = settings.use_username + else: + settings.use_username = username if not self.signature: self.define_signature() if signature==True: From 524a65d0a9d564c44a9ec3db9e57a4f2f0de3ddc Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 2 Sep 2012 15:03:25 -0500 Subject: [PATCH 02/19] routes_in = (regex, value, custom_env) --- VERSION | 2 +- gluon/rewrite.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/VERSION b/VERSION index 852bce5d..8473a5c8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.6 (2012-09-02 14:52:46) stable +Version 2.0.6 (2012-09-02 15:03:21) stable diff --git a/gluon/rewrite.py b/gluon/rewrite.py index 411a3b15..9804f275 100644 --- a/gluon/rewrite.py +++ b/gluon/rewrite.py @@ -308,8 +308,8 @@ def load(routes='routes.py', app=None, data=None, rdict=None): for sym in ('routes_app', 'routes_in', 'routes_out'): if sym in symbols: - for (k, v) in symbols[sym]: - p[sym].append(compile_regex(k, v)) + for items in symbols[sym]: + p[sym].append(compile_regex(*items)) for sym in ('routes_onerror', 'routes_apps_raw', 'error_handler','error_message', 'error_message_ticket', 'default_application','default_controller', 'default_function', @@ -371,7 +371,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None): log_rewrite('URL rewrite is on. configuration in %s' % path) -def compile_regex(k, v): +def compile_regex(k, v, env=None): """ Preprocess and compile the regular expressions in routes_app/in/out The resulting regex will match a pattern of the form: @@ -405,7 +405,7 @@ def compile_regex(k, v): # same for replacement pattern, but with \g for item in regex_at.findall(v): v = v.replace(item, r'\g<%s>' % item[1:]) - return (re.compile(k, re.DOTALL), v) + return (re.compile(k, re.DOTALL), v, env or {}) def load_routers(all_apps): "load-time post-processing of routers" @@ -519,8 +519,9 @@ def regex_uri(e, regexes, tag, default=None): (e.get('REMOTE_ADDR','localhost'), e.get('wsgi.url_scheme', 'http').lower(), host, e.get('REQUEST_METHOD', 'get').lower(), path) - for (regex, value) in regexes: + for (regex, value, custom_env) in regexes: if regex.match(key): + e.update(custom_env) rewritten = regex.sub(value, key) log_rewrite('%s: [%s] [%s] -> %s' % (tag, key, value, rewritten)) return rewritten @@ -708,7 +709,7 @@ def regex_filter_out(url, e=None): e.get('request_method', 'get').lower(), items[0]) else: items[0] = ':http://localhost:get %s' % items[0] - for (regex, value) in thread.routes.routes_out: + for (regex, value, tmp) in thread.routes.routes_out: if regex.match(items[0]): rewritten = '?'.join([regex.sub(value, items[0])] + items[1:]) log_rewrite('routes_out: [%s] -> %s' % (url, rewritten)) From c17c28e42bbf9a55104d058b0cdf9d3330cab366 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 2 Sep 2012 15:09:32 -0500 Subject: [PATCH 03/19] routes_in = [('/welcome','/welcome',dict(web2py_disable_session = True))] --- VERSION | 2 +- applications/welcome/languages/it.py | 2 ++ gluon/main.py | 27 ++++++++++++++++----------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/VERSION b/VERSION index 8473a5c8..40504481 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.6 (2012-09-02 15:03:21) stable +Version 2.0.6 (2012-09-02 15:09:28) stable diff --git a/applications/welcome/languages/it.py b/applications/welcome/languages/it.py index a73486c8..43a60561 100644 --- a/applications/welcome/languages/it.py +++ b/applications/welcome/languages/it.py @@ -95,6 +95,7 @@ 'Layouts': 'Layouts', 'Live Chat': 'Live Chat', 'Logged in': 'Logged in', +'Logged out': 'Logged out', 'login': 'accesso', 'Login': 'Login', 'logout': 'uscita', @@ -172,6 +173,7 @@ 'Update:': 'Aggiorna:', 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).', 'User %(id)s Logged-in': 'User %(id)s Logged-in', +'User %(id)s Logged-out': 'User %(id)s Logged-out', 'User %(id)s Registered': 'User %(id)s Registered', 'User ID': 'ID Utente', 'Verify Password': 'Verify Password', diff --git a/gluon/main.py b/gluon/main.py index e60a38ac..4854811f 100644 --- a/gluon/main.py +++ b/gluon/main.py @@ -149,10 +149,11 @@ def copystream_progress(request, chunk_size= 10**5): and stores progress upload status in cache.ram X-Progress-ID:length and X-Progress-ID:uploaded """ - if not request.env.content_length: + env = request.env + if not env.content_length: return cStringIO.StringIO() - source = request.env.wsgi_input - size = int(request.env.content_length) + source = env.wsgi_input + size = int(env.content_length) dest = tempfile.TemporaryFile() if not 'X-Progress-ID' in request.vars: copystream(source, dest, size, chunk_size) @@ -275,7 +276,8 @@ def environ_aux(environ,request): def parse_get_post_vars(request, environ): # always parse variables in URL for GET, POST, PUT, DELETE, etc. in get_vars - dget = cgi.parse_qsl(request.env.query_string or '', keep_blank_values=1) + env = request.env + dget = cgi.parse_qsl(env.query_string or '', keep_blank_values=1) for (key, value) in dget: if key in request.get_vars: if isinstance(request.get_vars[key], list): @@ -291,7 +293,7 @@ def parse_get_post_vars(request, environ): request.body = body = copystream_progress(request) except IOError: raise HTTP(400,"Bad Request - HTTP body is incomplete") - if (body and request.env.request_method in ('POST', 'PUT', 'BOTH')): + if (body and env.request_method in ('POST', 'PUT', 'BOTH')): dpost = cgi.FieldStorage(fp=body,environ=environ,keep_blank_values=1) # The same detection used by FieldStorage to detect multipart POSTs is_multipart = dpost.type[:10] == 'multipart/' @@ -472,9 +474,9 @@ def wsgibase(environ, responder): # load cookies # ################################################## - if request.env.http_cookie: + if env.http_cookie: try: - request.cookies.load(request.env.http_cookie) + request.cookies.load(env.http_cookie) except Cookie.CookieError, e: pass # invalid cookies @@ -482,7 +484,8 @@ def wsgibase(environ, responder): # try load session or create new session file # ################################################## - session.connect(request, response) + if not env.web2py_disable_session: + session.connect(request, response) # ################################################## # set no-cache headers @@ -519,7 +522,8 @@ def wsgibase(environ, responder): # ################################################## # on success, try store session in database # ################################################## - session._try_store_in_db(request, response) + if not env.web2py_disable_session: + session._try_store_in_db(request, response) # ################################################## # on success, commit database @@ -538,8 +542,9 @@ def wsgibase(environ, responder): # if session not in db try store session on filesystem # this must be done after trying to commit database! # ################################################## - - session._try_store_on_disk(request, response) + + if not env.web2py_disable_session: + session._try_store_on_disk(request, response) # ################################################## # store cookies in headers From d61748466eff27eb1ae3f210b82ee1d2528de444 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 2 Sep 2012 22:30:33 -0500 Subject: [PATCH 04/19] conditional session connect only --- VERSION | 2 +- applications/welcome/languages/it.py | 3 +++ gluon/main.py | 6 ++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index 40504481..bb11df7b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.6 (2012-09-02 15:09:28) stable +Version 2.0.6 (2012-09-02 22:30:28) stable diff --git a/applications/welcome/languages/it.py b/applications/welcome/languages/it.py index 43a60561..90f5d3e1 100644 --- a/applications/welcome/languages/it.py +++ b/applications/welcome/languages/it.py @@ -60,6 +60,7 @@ 'edit profile': 'modifica profilo', 'Edit This App': 'Modifica questa applicazione', 'Email and SMS': 'Email and SMS', +'Email non valida': 'Email non valida', 'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g', 'Errors': 'Errors', 'export as csv file': 'esporta come file CSV', @@ -114,6 +115,7 @@ 'new record inserted': 'nuovo record inserito', 'next 100 rows': 'prossime 100 righe', 'No databases in this application': 'Nessun database presente in questa applicazione', +'Non può essere vuoto': 'Non può essere vuoto', 'not authorized': 'non autorizzato', 'Object or table name': 'Object or table name', 'Online examples': 'Vedere gli esempi', @@ -167,6 +169,7 @@ 'This App': 'This App', 'This is a copy of the scaffolding application': "Questa è una copia dell'applicazione di base (scaffold)", 'Timestamp': 'Ora (timestamp)', +'too short': 'too short', 'Twitter': 'Twitter', 'unable to parse csv file': 'non riesco a decodificare questo file CSV', 'Update': 'Update', diff --git a/gluon/main.py b/gluon/main.py index 4854811f..9d382a34 100644 --- a/gluon/main.py +++ b/gluon/main.py @@ -522,8 +522,7 @@ def wsgibase(environ, responder): # ################################################## # on success, try store session in database # ################################################## - if not env.web2py_disable_session: - session._try_store_in_db(request, response) + session._try_store_in_db(request, response) # ################################################## # on success, commit database @@ -543,8 +542,7 @@ def wsgibase(environ, responder): # this must be done after trying to commit database! # ################################################## - if not env.web2py_disable_session: - session._try_store_on_disk(request, response) + session._try_store_on_disk(request, response) # ################################################## # store cookies in headers From 7b24ce3f41a7cff3e9d6fb1336e157c3ab049253 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 2 Sep 2012 22:34:24 -0500 Subject: [PATCH 05/19] fixed backward compatibility issue in dal with __int__, thanks Dominic --- VERSION | 2 +- gluon/dal.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index bb11df7b..886c6d0e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.6 (2012-09-02 22:30:28) stable +Version 2.0.6 (2012-09-02 22:34:20) stable diff --git a/gluon/dal.py b/gluon/dal.py index 5064fb47..c5330345 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -6460,7 +6460,7 @@ class Row(object): return '' % self.__dict__ def __int__(self): - return dict.__getitem__(self,'id') + return object.__getattribute__(self,'id') def __eq__(self,other): try: From 90c0e0ff7e3e31915b205043a8cd1bd699d012e4 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 3 Sep 2012 08:09:49 -0500 Subject: [PATCH 06/19] CACHED_REGEXES_MAX_SIZE, thanks Anthony --- VERSION | 2 +- gluon/compileapp.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 886c6d0e..9a3437b2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.6 (2012-09-02 22:34:20) stable +Version 2.0.6 (2012-09-03 08:09:45) stable diff --git a/gluon/compileapp.py b/gluon/compileapp.py index 70d5f6c9..6873ca74 100644 --- a/gluon/compileapp.py +++ b/gluon/compileapp.py @@ -92,11 +92,14 @@ _TEST() """ CACHED_REGEXES = {} +CACHED_REGEXES_MAX_SIZE = 1000 def re_compile(regex): try: return CACHED_REGEXES[regex] except KeyError: + if len(CACHED_REGEXES) >= CACHED_REGEXES_MAX_SIZE: + CACHED_REGEXES.clear() compiled_regex = CACHED_REGEXES[regex] = re.compile(regex) return compiled_regex From 72bb9d35130e3e9088085015d10064799a888bea Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 3 Sep 2012 08:12:40 -0500 Subject: [PATCH 07/19] executesql allows fields and/or colnames to be independently specfied, thanks Ahtony and Niphlod --- VERSION | 2 +- gluon/dal.py | 44 +++++++++++++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/VERSION b/VERSION index 9a3437b2..47aa9623 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.6 (2012-09-03 08:09:45) stable +Version 2.0.6 (2012-09-03 08:12:36) stable diff --git a/gluon/dal.py b/gluon/dal.py index c5330345..172a773c 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -7198,21 +7198,34 @@ def index(): [{field1: value1, field2: value2}, {field1: value1b, field2: value2b}] - Added 2012-08-24 "fields" optional argument. If not None, the - results cursor returned by the DB driver will be converted to a - DAL Rows object using the db._adapter.parse() method. This requires - specifying the "fields" argument as a list of DAL Field objects - that match the fields returned from the DB. The Field objects should - be part of one or more Table objects defined on the DAL object. - The "fields" list can include one or more DAL Table objects in addition - to or instead of including Field objects, or it can be just a single - table (not in a list). In that case, the Field objects will be - extracted from the table(s). + Added 2012-08-24 "fields" and "colnames" optional arguments. If either + is provided, the results cursor returned by the DB driver will be + converted to a DAL Rows object using the db._adapter.parse() method. + + The "fields" argument is a list of DAL Field objects that match the + fields returned from the DB. The Field objects should be part of one or + more Table objects defined on the DAL object. The "fields" list can + include one or more DAL Table objects in addition to or instead of + including Field objects, or it can be just a single table (not in a + list). In that case, the Field objects will be extracted from the + table(s). - The field names will be extracted from the Field objects, or optionally, - a list of field names can be provided (in tablename.fieldname format) - via the "colnames" argument. Note, the fields and colnames must be in - the same order as the fields in the results cursor returned from the DB. + Instead of specifying the "fields" argument, the "colnames" argument + can be specified as a list of field names in tablename.fieldname format. + Again, these should represent tables and fields defined on the DAL + object. + + It is also possible to specify both "fields" and the associated + "colnames". In that case, "fields" can also include DAL Expression + objects in addition to Field objects. For Field objects in "fields", + the associated "colnames" must still be in tablename.fieldname format. + For Expression objects in "fields", the associated "colnames" can + be any arbitrary labels. + + Note, the DAL Table objects referred to by "fields" or "colnames" can + be dummy tables and do not have to represent any real tables in the + database. Also, note that the "fields" and "colnames" must be in the + same order as the fields in the results cursor returned from the DB. """ adapter = self._adapter if placeholders: @@ -7234,7 +7247,8 @@ def index(): # easier to work with. row['field_name'] rather than row[0] return [dict(zip(fields,row)) for row in data] data = adapter.cursor.fetchall() - if fields: + if fields or colnames: + fields = [] if fields is None else fields if not isinstance(fields, list): fields = [fields] extracted_fields = [] From 29c513e5a361bb8656a0b10161fe8efc34a02498 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 3 Sep 2012 08:17:05 -0500 Subject: [PATCH 08/19] faster custom_import, thanks Michele --- VERSION | 2 +- gluon/custom_import.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/VERSION b/VERSION index 47aa9623..80f6d0c2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.6 (2012-09-03 08:12:36) stable +Version 2.0.6 (2012-09-03 08:17:01) stable diff --git a/gluon/custom_import.py b/gluon/custom_import.py index afcaff33..78c304ec 100644 --- a/gluon/custom_import.py +++ b/gluon/custom_import.py @@ -66,12 +66,14 @@ class _BaseImporter(object): help the management of this aspect. """ + def __init__(self): + self._STANDARD_PYTHON_IMPORTER = _STANDARD_PYTHON_IMPORTER def __call__(self, name, globals=None, locals=None, fromlist=None, level=-1): """ The import method itself. """ - return _STANDARD_PYTHON_IMPORTER(name, + return self._STANDARD_PYTHON_IMPORTER(name, globals, locals, fromlist, @@ -226,7 +228,8 @@ class _Web2pyImporter(_BaseImporter): """ global DEBUG - super(_Web2pyImporter, self).__init__() + self.super_class = super(_Web2pyImporter, self) + self.super_class.__init__() self.web2py_path = web2py_path self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep) @@ -284,16 +287,16 @@ class _Web2pyImporter(_BaseImporter): globals, locals, fromlist, level) else: # import like "from x import a, b, ..." - return super(_Web2pyImporter, self) \ + return self.super_class \ .__call__(modules_prefix+"."+name, globals, locals, fromlist, level) except ImportError, e: try: - return super(_Web2pyImporter, self).__call__(name, globals, locals, + return self.super_class.__call__(name, globals, locals, fromlist, level) except ImportError, e1: raise e - return super(_Web2pyImporter, self).__call__(name, globals, locals, + return self.super_class.__call__(name, globals, locals, fromlist, level) def __import__dot(self, prefix, name, globals, locals, fromlist, From 73c66c142da90af39c74df1de539fed116dd7a4d Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 3 Sep 2012 08:47:10 -0500 Subject: [PATCH 09/19] fixed error in admin login --- VERSION | 2 +- applications/admin/views/default.mobile/index.html | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 80f6d0c2..4b8679fb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.6 (2012-09-03 08:17:01) stable +Version 2.0.6 (2012-09-03 08:47:06) stable diff --git a/applications/admin/views/default.mobile/index.html b/applications/admin/views/default.mobile/index.html index 1b775d18..90f02961 100644 --- a/applications/admin/views/default.mobile/index.html +++ b/applications/admin/views/default.mobile/index.html @@ -2,7 +2,9 @@

web2py™ {{=T('Web Framework')}}

{{=T('Login to the Administrative Interface')}}

-
+ +{{if request.is_https or request.is_local:}} +
@@ -11,4 +13,6 @@
- +{{else:}} +

{{=T('ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.')}}

+{{pass}} From da7d3c6dbd48d347ce117b4046a343e7a66493d6 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 3 Sep 2012 08:54:45 -0500 Subject: [PATCH 10/19] updated markmin docs --- VERSION | 2 +- applications/examples/static/markmin.html | 123 +++++++++++++++------- 2 files changed, 88 insertions(+), 37 deletions(-) diff --git a/VERSION b/VERSION index 4b8679fb..f4b17644 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.6 (2012-09-03 08:47:06) stable +Version 2.0.6 (2012-09-03 08:54:40) stable diff --git a/applications/examples/static/markmin.html b/applications/examples/static/markmin.html index 00b7ad62..98aa58ca 100644 --- a/applications/examples/static/markmin.html +++ b/applications/examples/static/markmin.html @@ -1,41 +1,92 @@ -

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
->>> print markmin2latex(m)
->>> from markmin2pdf import markmin2pdf # requires pdflatex
->>> print markmin2pdf(m)

Why?

We wanted a markup language with the following requirements:

  • less than 100 lines of functional code
  • easy to read
  • secure
  • support table, ul, ol, code
  • support html5 video and audio elements (html serialization only)
  • can align images and resize them
  • can specify class for tables and code elements
  • can add anchors
  • does not use _ for markup (since it creates odd behavior)
  • automatically links urls
  • fast
  • easy to extend
  • supports latex and pdf including references
  • allows to describe the markup in the markup (this document is generated from markmin syntax)

(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

  • http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2html.py
  • http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2latex.py
  • http://web2py.googlecode.com/hg/gluon/contrib/markmin/markmin2pdf.py

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

Examples

Bold, italic, code and links

SOURCE OUTPUT
# title title
## section section
### subsection subsection
**bold** bold
''italic'' italic
``verbatim`` verbatim
http://google.com http://google.com
[[click me #myanchor]]click me
-

More on links

The format is always [[title link]]. Notice you can nest bold, italic 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]].

Images

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

[[some image http://www.web2py.com/examples/static/web2py_logo.png right 200px]].

Unordered Lists

- Dog
+
+
+
+
+
+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
+print markmin2latex(m)
+from markmin2pdf import markmin2pdf # requires pdflatex
+print markmin2pdf(m)
====================

This is a test block with new features:

This is a blockquote with a list with tables in it:

This is a paragraph before list. You can continue paragraph on the next lines.
This is an ordered list with tables:
  1. Item 1
  2. Item 2
  3. aabbcc
    112233
  4. Item 4
    T1T2t3
    aaabbbccc
    dddfffggg
    12305.0

This this a new paragraph with a table. Table has header, footer, sections, odd and even rows:

Title 1Title 2Title 3
data 1data 22.00
data 3data4(long)23.00
data 533.50
New sectionNew data5.00
data 1data2(long)100.45
data 312.50
data 4data 5.33
data 6data7(long)8.01
data 8514
Total:9 items698,79

Multilevel lists

Now lists can be multilevel:

  1. Ordered item 1 on level 1. You can continue item text on next strings
    1. Ordered item 1 of sublevel 2 with a paragraph (paragraph can start with point after plus or minus characters, e.g. ++. or --.)

    2. This is another item. But with 3 paragraphs, blockquote and sublists:

      This is the second paragraph in the item. You can add paragraphs to an item, using point notation, where first characters in the string are sequence of points with space between them and another string. For example, this paragraph (in sublevel 2) starts with two points:

      .. This is the second paragraph...

      this is a blockquote in a list

      You can use blockquote with headers, paragraphs, tables and lists in it:
      Tables can have or have not header and footer. This table is defined without any header and footer in it:
      redfox0
      bluedolphin1000
      greenleaf10000

      This is yet another paragraph in the item.

      • This is an item of unordered list (sublevel 3)
      • This is the second item of the unordered list (sublevel 3)
            1. This is a single item of ordered list in sublevel 6

          and this is a paragraph in sublevel 4

      • This is a new item with paragraph in sublevel 3.

        1. Start ordered list in sublevel 4 with code block:
          line 1
          +  line 2
          +     line 3
        2. Yet another item with code block:

            line 1
          +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.

    3. item 3 in sublevel 2
    • item 1 in sublevel 2 (new unordered list)
    • item 2 in sublevel 2
    • 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
  2. item 2 in level 1
  3. item 3 in level 1
  • new unordered list (item 1 in level 1)
  • level 2 in level 1
  • level 3 in level 1
  • level 4 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:

  • less than 300 lines of functional code
  • easy to read
  • secure
  • support table, ul, ol, code
  • support html5 video and audio elements (html serialization only)
  • can align images and resize them
  • can specify class for tables and code elements
  • can add anchors
  • does not use _ for markup (since it creates odd behavior)
  • automatically links urls
  • fast
  • easy to extend
  • supports latex and pdf including references
  • allows to describe the markup in the markup (this document is generated from markmin syntax)

(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

  • Dog
  • Cat
  • Mouse

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

Ordered Lists

+ Dog
+- Mouse

is rendered as

  • Dog
  • Cat
  • Mouse

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

Ordered Lists

+ Dog
 + Cat
-+ Mouse

is rendered as

  1. Dog
  2. Cat
  3. Mouse

Tables

Something like this -

---------
-**A** | **B** | **C**
-0 | 0 | X
-0 | X | 0
-X | 0 | 0
------:abc
-is a table and is rendered as -
ABC
00X
0X0
X00
Four or more dashes delimit the table and | separates the columns. -The :abc at the end sets the class for the table and it is optional.

Blockquote

A table with a single cell is rendered as a blockquote:

Hello world
-

Code, <code>, escaping and extra stuff

def test():
-    return "this is Python code"

Optionally a ` inside a ``...`` block can be inserted escaped with !`!. -The :python after the markup is also optional. If present, by default, it is used to set the class of the <code> block. -The behavior can be overridden by passing an argument extra to the render function. For example:

>>> markmin2html("``aaa``:custom",
-       extra=dict(custom=lambda text: 'x'+text+'x'))

generates

'xaaax'

(the ``...``:custom block is rendered by the custom=lambda function passed to render).

Html5 support

Markmin also supports the <video> and <audio> html5 tags using the notation: -

[[title link video]]
-[[title link audio]]

Latex and other extensions

Formulas can be embedded into HTML with $$formula$$. -You can use Google charts to render the formula:

>>> LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" align="center"/>'
->>> markmin2html(text,{'latex':lambda code: LATEX % code.replace('"','"')})

Code with syntax highlighting

This requires a syntax highlighting tool, such as the web2py CODE helper.

>>> extra={'code_cpp':lambda text: CODE(text,language='cpp').xml(),
-           'code_java':lambda text: CODE(text,language='java').xml(),
-           'code_python':lambda text: CODE(text,language='python').xml(),
-           'code_html':lambda text: CODE(text,language='html').xml()}
->>> markmin2html(text,extra=extra)

Code can now be marked up as in this example:

``
++ Mouse

is rendered as

  1. Dog
  2. Cat
  3. Mouse

Multilevel Lists

+ Dogs
+ -- red
+ -- brown
+ -- black
++ Cats
+ -- fluffy
+ -- smooth
+ -- bald
++ Mice
+ -- small
+ -- big
+ -- huge

is rendered as

  1. Dogs
    • red
    • brown
    • black
  2. Cats
    • fluffy
    • smooth
    • bald
  3. Mice
    • small
    • big
    • huge

Tables (with optional header and/or footer)

Something like this

-----------------
+**A**|**B**|**C**
+=================
+  0  |  0  |  X
+  0  |  X  |  0
+  X  |  0  |  0
+=================
+**D**|**F**|**G**
+-----------------:abc[id]
is a table and is rendered as
ABC
00X
0X0
X00
DFG
Four or more dashes delimit the table and | separates the columns. The :abc, :id[abc_1] or :abc[abc_1] at the end sets the class and/or id for the table and it is optional.

Blockquote

A table with a single cell is rendered as a blockquote:

Hello world

Blockquote can contain headers, paragraphs, lists and tables:

-----
+  This is a paragraph in a blockquote
+
+  + item 1
+  + item 2
+  -- item 2.1
+  -- item 2.2
+  + item 3
+
+  ---------
+  0 | 0 | X
+  0 | X | 0
+  X | 0 | 0
+  ---------:tableclass1
+-----

is rendered as:

This is a paragraph in a blockquote
  1. item 1
  2. item 2
    • item 2.1
    • item 2.2
  3. item 3
00X
0X0
X00

Code, <code>, escaping and extra stuff

def test():
+    return "this is Python code"

Optionally a ` inside a ``...`` block can be inserted escaped with !`!.

NOTE: You can escape markmin constructions ('',``,**,~~,[,{,]},$,@) with '\' character: so \`\` can replace !`!`! escape string

The :python after the markup is also optional. If present, by default, it is used to set the class of the <code> block. The behavior can be overridden by passing an argument extra to the render function. For example:

markmin2html("``aaa``:custom",
+             extra=dict(custom=lambda text: 'x'+text+'x'))

generates

'xaaax'

(the ``...``:custom block is rendered by the custom=lambda function passed to render).

Html5 support

Markmin also supports the <video> and <audio> html5 tags using the notation:

[[message link video]]
+[[message link audio]]
+
+[[message [title] link video]]
+[[message [title] link audio]]
where message will be shown in brousers without HTML5 video/audio tags support.

Latex and other extensions

Formulas can be embedded into HTML with $$formula$$. You can use Google charts to render the formula:

LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" />'
+markmin2html(text,{'latex':lambda code: LATEX % code.replace('"','\"')})

Code with syntax highlighting

This requires a syntax highlighting tool, such as the web2py CODE helper.

extra={'code_cpp':lambda text: CODE(text,language='cpp').xml(),
+       'code_java':lambda text: CODE(text,language='java').xml(),
+       'code_python':lambda text: CODE(text,language='python').xml(),
+       'code_html':lambda text: CODE(text,language='html').xml()}
or simple:
extra={'code':lambda text,lang='python': CODE(text,language=lang).xml()}
markmin2html(text,extra=extra)

Code can now be marked up as in this example:

``
 <html><body>example</body></html>
-``:code_html

Citations and References

Citations are treated as internal links in html and proper citations in latex if there is a final section called "References". Items like

- [[key]] value

in the References will be translated into Latex

\bibitem{key} value

Here is an example of usage:

As shown in Ref.``mdipierro``:cite
+``:code_html
OR
``
+<html><body>example</body></html>
+``:code[html]

Citations and References

Citations are treated as internal links in html and proper citations in latex if there is a final section called "References". Items like

- [[key]] value

in the References will be translated into Latex

\bibitem{key} value

Here is an example of usage:

As shown in Ref.``mdipierro``:cite
 
 ## 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.

+ + From 65fec492f0a879b29b2bde75f04d6c18a524b40d Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 3 Sep 2012 08:55:54 -0500 Subject: [PATCH 11/19] fixed input-xlarge class issue, thanks Anthony --- VERSION | 2 +- gluon/sqlhtml.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index f4b17644..db2abfd7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.6 (2012-09-03 08:54:40) stable +Version 2.0.6 (2012-09-03 08:55:49) stable diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index 959d40b3..b94f324b 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -702,17 +702,17 @@ def formstyle_bootstrap(form, fields): _submit = False if isinstance(controls, INPUT): - controls['_class'] = 'input-xlarge' + controls.add_class('input-xlarge') if controls['_type'] == 'submit': # flag submit button _submit = True controls['_class'] = 'btn btn-primary' if isinstance(controls, SELECT): - controls['_class'] = 'input-xlarge' + controls.add_class('input-xlarge') if isinstance(controls, TEXTAREA): - controls['_class'] = 'input-xlarge' + controls.add_class('input-xlarge') if isinstance(label, LABEL): label['_class'] = 'control-label' From 07a97d62b8880b583425c8670f2e409b5ba32cc5 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 3 Sep 2012 10:18:15 -0500 Subject: [PATCH 12/19] minor changes in web2py.css --- VERSION | 2 +- applications/welcome/static/css/web2py.css | 11 ++--------- applications/welcome/views/layout.html | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/VERSION b/VERSION index db2abfd7..2188ad7d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.6 (2012-09-03 08:55:49) stable +Version 2.0.6 (2012-09-03 10:18:10) stable diff --git a/applications/welcome/static/css/web2py.css b/applications/welcome/static/css/web2py.css index 54b1cbce..be3aebf6 100644 --- a/applications/welcome/static/css/web2py.css +++ b/applications/welcome/static/css/web2py.css @@ -15,9 +15,9 @@ 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.5em 0} +p,table,ol,ul {padding:0; margin: 0.75em 0} p {text-align:justify} -ol, ul {list-style-position:inside} +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} @@ -39,13 +39,6 @@ input[type=text],input[type=password],select{width:300px; margin-right:5px} /* Sticky footer begin */ -.wrapper { - min-height:100%; - height:auto !important; - height:100%; - margin:0 auto -8em; /* set last value to footer height plus footer vertical padding */ -} - .main { padding:20px 0 50px 0; } diff --git a/applications/welcome/views/layout.html b/applications/welcome/views/layout.html index addb3682..35532710 100644 --- a/applications/welcome/views/layout.html +++ b/applications/welcome/views/layout.html @@ -129,7 +129,7 @@ -