From c02707a65c9a3f9afd4e9f979162970f793232c3 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 08:22:35 -0500 Subject: [PATCH 01/68] support for HEAD requests --- VERSION | 2 +- gluon/http.py | 6 ++++-- gluon/main.py | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index e88b45a9..8009008d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 07:27:23) rc4 +Version 2.00.1 (2012-08-29 08:22:30) rc4 diff --git a/gluon/http.py b/gluon/http.py index 548ac6d9..5963603a 100644 --- a/gluon/http.py +++ b/gluon/http.py @@ -76,7 +76,7 @@ class HTTP(BaseException): self.headers['Set-Cookie'] = [ str(cookie)[11:] for cookie in cookies.values()] - def to(self, responder): + def to(self, responder, env={}): status = self.status headers = self.headers if status in defined_status: @@ -100,7 +100,9 @@ class HTTP(BaseException): else: rheaders.append((k, str(v))) responder(status, rheaders) - if isinstance(body,str): + if env.get('request_method','')=='HEAD': + return [''] + elif isinstance(body,str): return [body] elif hasattr(body, '__iter__'): return body diff --git a/gluon/main.py b/gluon/main.py index efa30df7..d5e6d25c 100644 --- a/gluon/main.py +++ b/gluon/main.py @@ -526,7 +526,8 @@ def wsgibase(environ, responder): except HTTP, http_response: if static_file: - return http_response.to(responder) + return http_response.to(responder,env=env) + if request.body: request.body.close() @@ -636,7 +637,7 @@ def wsgibase(environ, responder): return wsgibase(new_environ,responder) if global_settings.web2py_crontype == 'soft': newcron.softcron(global_settings.applications_parent).start() - return http_response.to(responder) + return http_response.to(responder,env=env) def save_password(password, port): From 6f6276a912bf924b680af548c03afe7278c5a4b1 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 10:05:51 -0500 Subject: [PATCH 02/68] fixed to(env={}), thanks Anthony --- VERSION | 2 +- gluon/http.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 8009008d..de0cdbc9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 08:22:30) rc4 +Version 2.00.1 (2012-08-29 10:05:45) rc4 diff --git a/gluon/http.py b/gluon/http.py index 5963603a..69356b85 100644 --- a/gluon/http.py +++ b/gluon/http.py @@ -76,7 +76,8 @@ class HTTP(BaseException): self.headers['Set-Cookie'] = [ str(cookie)[11:] for cookie in cookies.values()] - def to(self, responder, env={}): + def to(self, responder, env=None): + env = env or {} status = self.status headers = self.headers if status in defined_status: From b232a2ddbaa2260e82447300571a26ebfeb7a82f Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 10:12:11 -0500 Subject: [PATCH 03/68] catch missing winservice --- VERSION | 2 +- gluon/widget.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index de0cdbc9..953d2253 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 10:05:45) rc4 +Version 2.00.1 (2012-08-29 10:12:06) rc4 diff --git a/gluon/widget.py b/gluon/widget.py index 13114466..3b8055f5 100644 --- a/gluon/widget.py +++ b/gluon/widget.py @@ -33,8 +33,9 @@ try: import Tkinter, tkMessageBox import contrib.taskbar_widget from winservice import web2py_windows_service_handler + have_winservice = True except: - pass + have_winservice = False try: @@ -1080,8 +1081,12 @@ def start(cron=True): # ## if -W install/start/stop web2py as service if options.winservice: if os.name == 'nt': - web2py_windows_service_handler(['', options.winservice], - options.config) + if have_winservice: + web2py_windows_service_handler(['', options.winservice], + options.config) + else: + print 'Error: Missing python module winservice' + sys.exit(1) else: print 'Error: Windows services not supported on this platform' sys.exit(1) From b244f9000a5c8f89034a3f749db1587bf570f006 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 10:28:04 -0500 Subject: [PATCH 04/68] fixed error with referenced_by, thanks Marin --- VERSION | 2 +- applications/admin/languages/default.py | 1 + gluon/sqlhtml.py | 15 +++++++-------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/VERSION b/VERSION index 953d2253..b25e0bdd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 10:12:06) rc4 +Version 2.00.1 (2012-08-29 10:27:58) rc4 diff --git a/applications/admin/languages/default.py b/applications/admin/languages/default.py index a9bd0be9..5e7b0a7d 100644 --- a/applications/admin/languages/default.py +++ b/applications/admin/languages/default.py @@ -78,6 +78,7 @@ 'New simple application': 'New simple application', 'Overwrite installed app': 'Overwrite installed app', 'Pack all': 'Pack all', +'Peeking at file': 'Peeking at file', 'Plugins': 'Plugins', 'plugins': 'plugins', 'Plural-Forms:': 'Plural-Forms:', diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index 20bfef25..32979bcc 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -1042,20 +1042,19 @@ class SQLFORM(FORM): # build a link if record and linkto: db = linkto.split('/')[-1] - for (rtable, rfield) in table._referenced_by: + for rfld in table._referenced_by: if keyed: - rfld = table._db[rtable][rfield] query = urllib.quote('%s.%s==%s' % (db,rfld,record[rfld.type[10:].split('.')[1]])) else: - query = urllib.quote('%s.%s==%s' % (db,table._db[rtable][rfield],record[self.id_field_name])) - lname = olname = '%s.%s' % (rtable, rfield) + query = urllib.quote('%s.%s==%s' % (db,rfld,record[self.id_field_name])) + lname = olname = '%s.%s' % (rfld.tablename, rfld.name) if ofields and not olname in ofields: continue if labels and lname in labels: lname = labels[lname] widget = A(lname, _class='reference', - _href='%s/%s?query=%s' % (linkto, rtable, query)) + _href='%s/%s?query=%s' % (linkto, rfld.tablename, query)) xfields.append((olname.replace('.', '__')+SQLFORM.ID_ROW_SUFFIX, '',widget,col3.get(olname,''))) self.custom.linkto[olname.replace('.', '__')] = widget @@ -2218,9 +2217,9 @@ class SQLFORM(FORM): del kwargs[key] check = {} id_field_name = table._id.name - for tablename,fieldname in table._referenced_by: - if db[tablename][fieldname].readable: - check[tablename] = check.get(tablename,[])+[fieldname] + for field in table._referenced_by: + if field.readable: + check[field.tablename] = check.get(field.tablename,[])+[field.name] for tablename in sorted(check): linked_fieldnames = check[tablename] tb = db[tablename] From 789bacce106898dd2ced5355e9a941a9744ba3af Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 15:09:08 -0500 Subject: [PATCH 05/68] fixed some problems with admin no GAE, still readonly --- VERSION | 2 +- applications/admin/controllers/default.py | 12 +++++++----- applications/admin/models/access.py | 5 ++++- gluon/cfs.py | 6 ++++-- gluon/dal.py | 2 +- gluon/languages.py | 5 ++++- 6 files changed, 21 insertions(+), 11 deletions(-) diff --git a/VERSION b/VERSION index b25e0bdd..c09cf1c7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 10:27:58) rc4 +Version 2.00.1 (2012-08-29 15:09:03) rc4 diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 1fb8cf9e..ccc36121 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -51,12 +51,12 @@ def log_progress(app,mode='EDIT',filename=None,progress=0): progress_file = os.path.join(apath(app, r=request), 'progress.log') now = str(request.now)[:19] if not os.path.exists(progress_file): - open(progress_file,'w').write('[%s] START\n' % now) + safe_open(progress_file,'w').write('[%s] START\n' % now) if filename: - open(progress_file,'a').write('[%s] %s %s: %s\n' % (now,mode,filename,progress)) + safe_open(progress_file,'a').write('[%s] %s %s: %s\n' % (now,mode,filename,progress)) def safe_open(a,b): - if DEMO_MODE and 'w' in b: + if DEMO_MODE and ('w' in b or 'a' in b): class tmp: def write(self,data): pass return tmp() @@ -455,11 +455,13 @@ def delete(): def enable(): app = get_app() filename = os.path.join(apath(app, r=request),'DISABLED') - if os.path.exists(filename): + if is_gae: + return SPAN(T('Not supported'),_style='color:yellow') + elif os.path.exists(filename): os.unlink(filename) return SPAN(T('Disable'),_style='color:green') else: - open(filename,'wb').write(time.ctime()) + safe_open(filename,'wb').write(time.ctime()) return SPAN(T('Enable'),_style='color:red') def peek(): diff --git a/applications/admin/models/access.py b/applications/admin/models/access.py index 1669c526..22d9e03c 100644 --- a/applications/admin/models/access.py +++ b/applications/admin/models/access.py @@ -12,6 +12,9 @@ if request.env.web2py_runtime_gae: session_db = DAL('gae') session.connect(request, response, db=session_db) hosts = (http_host, ) + is_gae = True +else: + is_gae = False if request.env.http_x_forwarded_for or request.is_https: session.secure() @@ -27,7 +30,7 @@ try: raise HTTP(200, T('admin disabled because no admin password')) except IOError: import gluon.fileutils - if request.env.web2py_runtime_gae: + if is_gae: if gluon.fileutils.check_credentials(request): session.authorized = True session.last_time = time.time() diff --git a/gluon/cfs.py b/gluon/cfs.py index b3669f4b..f0c20aaf 100644 --- a/gluon/cfs.py +++ b/gluon/cfs.py @@ -14,6 +14,7 @@ FOR INTERNAL USE ONLY import os import thread +import logging from fileutils import read_file cfs = {} # for speed-up @@ -34,15 +35,16 @@ def getcfs(key, filename, filter=None): This is used on Google App Engine since pyc files cannot be saved. """ try: + logging.info(filename) t = os.stat(filename).st_mtime except OSError: - return filter() + return filter() if callable(filter) else '' cfs_lock.acquire() item = cfs.get(key, None) cfs_lock.release() if item and item[0] == t: return item[1] - if not filter: + if not callable(filter): data = read_file(filename) else: data = filter() diff --git a/gluon/dal.py b/gluon/dal.py index 5f9b5b0e..912fc2ad 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -4329,9 +4329,9 @@ class GoogleDatastoreAdapter(NoSQLAdapter): def select_raw(self,query,fields=None,attributes=None): db = self.db - args_get = attributes.get fields = fields or [] attributes = attributes or {} + args_get = attributes.get new_fields = [] for item in fields: if isinstance(item,SQLALL): diff --git a/gluon/languages.py b/gluon/languages.py index f2d78c52..4b4f6f1a 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -429,7 +429,10 @@ class translator(object): self.request = request self.folder = request.folder self.langpath = ospath.join(self.folder,'languages') - self.filenames = set(os.listdir(self.langpath)) + try: + self.filenames = set(os.listdir(self.langpath)) + except: + self.filenames = set() self.http_accept_language = request.env.http_accept_language # self.cache # filled in self.force() # self.accepted_language = None # filled in self.force() From b3033ddf52d0c0c5730c00ce98e7a89861e07b9f Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 15:12:46 -0500 Subject: [PATCH 06/68] removed code comitted accidentally --- VERSION | 2 +- gluon/cfs.py | 1 - gluon/languages.py | 6 +----- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/VERSION b/VERSION index c09cf1c7..b05bac65 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 15:09:03) rc4 +Version 2.00.1 (2012-08-29 15:12:43) rc4 diff --git a/gluon/cfs.py b/gluon/cfs.py index f0c20aaf..d40737b3 100644 --- a/gluon/cfs.py +++ b/gluon/cfs.py @@ -35,7 +35,6 @@ def getcfs(key, filename, filter=None): This is used on Google App Engine since pyc files cannot be saved. """ try: - logging.info(filename) t = os.stat(filename).st_mtime except OSError: return filter() if callable(filter) else '' diff --git a/gluon/languages.py b/gluon/languages.py index 4b4f6f1a..c0a13df0 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -429,10 +429,7 @@ class translator(object): self.request = request self.folder = request.folder self.langpath = ospath.join(self.folder,'languages') - try: - self.filenames = set(os.listdir(self.langpath)) - except: - self.filenames = set() + self.filenames = set(os.listdir(self.langpath)) self.http_accept_language = request.env.http_accept_language # self.cache # filled in self.force() # self.accepted_language = None # filled in self.force() @@ -445,7 +442,6 @@ class translator(object): # self.plural_file = None # filled in self.force() # self.plural_dict = None # filled in self.force() # self.plural_status = None # filled in self.force() - self.requested_languages = \ self.force(self.http_accept_language) self.lazy = True From a9b780334f951a9a4a2894841006dabd01087aef Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 15:13:10 -0500 Subject: [PATCH 07/68] 2.0.1 rc5 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index b05bac65..c4320b04 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 15:12:43) rc4 +Version 2.00.1 (2012-08-29 15:13:07) rc4 From ac100b74895dd21b502fd081847e72af3d308a60 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 15:42:51 -0500 Subject: [PATCH 08/68] fixed problem with iterator in template.py --- VERSION | 2 +- gluon/template.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index c4320b04..daf05c16 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 15:13:07) rc4 +Version 2.00.1 (2012-08-29 15:42:48) rc4 diff --git a/gluon/template.py b/gluon/template.py index a8481338..3d6442c0 100644 --- a/gluon/template.py +++ b/gluon/template.py @@ -177,7 +177,8 @@ class Content(BlockNode): if isinstance(other, (list, tuple)): # Must reverse so the order stays the same. other.reverse() - (self._insert(item, index) for item in other) + for item in other: + self._insert(item, index) else: self._insert(other, index) From 36e8c95ca519643bc6ea428a34b488877ca6465d Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 16:05:41 -0500 Subject: [PATCH 09/68] fixed problem with computed fields --- VERSION | 2 +- gluon/dal.py | 12 ++++++++---- gluon/tests/test_dal.py | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index daf05c16..7fd3f52c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 15:42:48) rc4 +Version 2.00.1 (2012-08-29 16:05:38) rc4 diff --git a/gluon/dal.py b/gluon/dal.py index 912fc2ad..22eed09b 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -7673,21 +7673,25 @@ class Table(object): to_compute.append((name,ofield)) # if field is required, check its default value elif not update and not ofield.default is None: - new_fields[name] = (ofield,ofield.default) + value = ofield.default + fields[name] = value + new_fields[name] = (ofield,value) # if this is an update, user the update field instead elif update and not ofield.update is None: - new_fields[name] = (ofield,ofield.update) + value = ofield.update + fields[name] = value + new_fields[name] = (ofield,value) # if the field is still not there but it should, error elif not update and ofield.required: raise RuntimeError, \ 'Table: missing required field: %s' % name # now deal with fields that are supposed to be computed if to_compute: - dummyrow = Row(new_fields) + row = Row(fields) for name,ofield in to_compute: # try compute it try: - new_fields[name] = (ofield,ofield.compute(dummyrow)) + new_fields[name] = (ofield,ofield.compute(row)) except (KeyError, AttributeError): # error sinlently unless field is required! if ofield.required: diff --git a/gluon/tests/test_dal.py b/gluon/tests/test_dal.py index 79e35c26..b60fbe16 100644 --- a/gluon/tests/test_dal.py +++ b/gluon/tests/test_dal.py @@ -515,6 +515,20 @@ class TestVirtualFields(unittest.TestCase): db.t.drop() db.commit() +class TestComputedFields(unittest.TestCase): + + def testRun(self): + db = DAL('sqlite:memory:') + db.define_table('t', + Field('a'), + Field('b',default='x'), + Field('c',compute=lambda r: r.a+r.b)) + db.commit() + id = db.t.insert(a="z") + self.assertEqual(db.t[id].c,'zx') + db.t.drop() + db.commit() + class TestImportExportFields(unittest.TestCase): def testRun(self): From ae86f4b9eb2613a6b7b4d19bb4a8643b266942fd Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 17:40:03 -0500 Subject: [PATCH 10/68] webclient.py and test_web.py --- VERSION | 2 +- gluon/contrib/webclient.py | 92 ++++++++++++++++++++++++++++++++++++++ gluon/tests/test.sh | 6 ++- gluon/tests/test_web.py | 41 +++++++++++++++++ 4 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 gluon/contrib/webclient.py create mode 100644 gluon/tests/test_web.py diff --git a/VERSION b/VERSION index 7fd3f52c..c53c8981 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 16:05:38) rc4 +Version 2.00.1 (2012-08-29 17:40:00) rc4 diff --git a/gluon/contrib/webclient.py b/gluon/contrib/webclient.py new file mode 100644 index 00000000..3101e28a --- /dev/null +++ b/gluon/contrib/webclient.py @@ -0,0 +1,92 @@ +""" +Developed by Massimo Di Pierro +Released under the web2py license (LGPL) + +It an interface on top of urllib2 that allows authentication and understand +web2py cookies and web2py forms. An example of usage is at the bottom. +""" + +import re +import urllib +import urllib2 + +class WebClient(object): + regex = re.compile('\\') + + def __init__(self,app=''): + self.app = app + self.cookies = {} + + def get(self,url,cookies=None,headers=None,auth=None): + return self.post(url,data=None,cookies=cookies,headers=headers) + + def post(self,url,data=None,cookies=None,headers=None,auth=None): + self.url = self.app+url + if cookies is None: cookies = self.cookies + if auth: + auth_handler = urllib2.HTTPBasicAuthHandler() + auth_handler.add_password(**auth) + opener = urllib2.build_opener(auth_handler) + else: + opener = urllib2.build_opener() + headers_list = [] + for key,value in (headers or {}).iteritems(): + if isinstance(value,(list,tuple)): + for v in value: headers_list.append((key,v)) + else: + headers_list.append((key,value)) + for key,value in (cookies or {}).iteritems(): + headers_list.append(('Cookie','%s=%s' % (key,value))) + for key,value in headers_list: + opener.addheaders.append((key,str(value))) + if data is not None: + # if there is only one form, set _formname automatically + if not '_formname' in data and len(self.forms)==1: + data['_formname'] = self.forms.keys()[0] + # if there is no formkey but it is known, set it + if '_formname' in data and not '_formkey' in data and \ + data['_formname'] in self.forms: + data['_formkey'] = self.forms[data['_formname']] + data = urllib.urlencode(data) + self.request = opener.open(self.url,data) + else: + self.request = opener.open(self.url) + self.status = self.request.getcode() + self.text = self.request.read() + self.headers = dict(self.request.headers) + self.cookies = dict(item[:item.find(';')].split('=') for item in \ + self.headers.get('set-cookie','').split(',')) + self.forms = {} + for match in WebClient.regex.finditer(self.text): + self.forms[match.group('formname')] = match.group('formkey') + +def test_web2py_registration_and_login(): + session = WebClient('http://127.0.0.1:8000/welcome/default/') + session.get('user/register') + session_id_welcome = session.cookies['session_id_welcome'] + data = dict(first_name = 'Homer', + last_name = 'Simpson', + email = 'homer@web2py.com', + password = 'test', + password_two = 'test', + _formname = 'register') + session.post('user/register',data = data) + + session.get('user/login') + data = dict(email='homer@web2py.com', + password='test', + _formname = 'login') + session.post('user/login',data = data) + + session.get('index') + + # check registration and login were successful + assert 'Welcome Homer' in session.text + + # check we are always in the same session + assert session_id_welcome == session.cookies['session_id_welcome'] + + +if __name__ == '__main__': + test_web2py_registration_and_login() + diff --git a/gluon/tests/test.sh b/gluon/tests/test.sh index a32b17c3..1e25f800 100755 --- a/gluon/tests/test.sh +++ b/gluon/tests/test.sh @@ -28,8 +28,10 @@ else elif [ "$1" = "doctest" ]; then # this has to run in gluon's parent; needs work # - # the problem is that doctests run this way have a very different environment, - # apparently due to imports that don't happen in the normal course of running + # the problem is that doctests run this way + # have a very different environment, + # apparently due to imports that don't happen + # in the normal course of running # doctest via __main__. # echo doctest not supported >&2 diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py new file mode 100644 index 00000000..94a2b31d --- /dev/null +++ b/gluon/tests/test_web.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Unit tests for running web2py +""" +import sys +import os +if os.path.isdir('gluon'): + sys.path.append(os.path.realpath('gluon')) +else: + sys.path.append(os.path.realpath('../')) + +import unittest +from gluon.contrib.webclient import WebClient + +class TestWeb(unittest.TestCase): + def testWebClient(self): + session = WebClient('http://127.0.0.1:8000/welcome/default/') + session.get('user/register') + session_id_welcome = session.cookies['session_id_welcome'] + data = dict(first_name = 'Homer', + last_name = 'Simpson', + email = 'homer@web2py.com', + password = 'test', + password_two = 'test', + _formname = 'register') + session.post('user/register',data = data) + + session.get('user/login') + data = dict(email='homer@web2py.com', + password='test', + _formname = 'login') + session.post('user/login',data = data) + + session.get('index') + # check registration and login were successful + self.assertTrue('Welcome Homer' in session.text) + # check we are always in the same session + self.assertEqual(session_id_welcome, + session.cookies['session_id_welcome']) + self.assertEqual('a','b') From ee6c0fca070dbf68af464e8cc5a4aa4e6d360ecf Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 17:44:07 -0500 Subject: [PATCH 11/68] updated CHANGELOG --- CHANGELOG | 1 + VERSION | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index bd6fb9cd..c0ece9e9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -80,6 +80,7 @@ ### Other Improvements +- gluon/contrib/webclient.py makes it easy to create functional tests for app - DIV(..).elements(...replace=...), thanks Anthony - new layout based on Twitter Bootstrap - New generic views: generic.ics (Mac Mail Calendar) and generic.map (Google Maps) diff --git a/VERSION b/VERSION index c53c8981..fa29024d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 17:40:00) rc4 +Version 2.00.1 (2012-08-29 17:44:04) rc4 From 8574f04522252d52b5a99e05aa2004c978bc08e2 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 17:59:44 -0500 Subject: [PATCH 12/68] updated gluon/tests/__init__.py, thanks Jonathan --- VERSION | 2 +- gluon/tests/__init__.py | 2 ++ gluon/tests/test_web.py | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index fa29024d..b9c4af54 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 17:44:04) rc4 +Version 2.00.1 (2012-08-29 17:59:41) rc4 diff --git a/gluon/tests/__init__.py b/gluon/tests/__init__.py index 63f85d7c..5fd6813a 100644 --- a/gluon/tests/__init__.py +++ b/gluon/tests/__init__.py @@ -8,3 +8,5 @@ from test_routes import * from test_storage import * from test_template import * from test_utils import * +from test_contribs import * +# from test_web import * diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py index 94a2b31d..d85943ff 100644 --- a/gluon/tests/test_web.py +++ b/gluon/tests/test_web.py @@ -38,4 +38,3 @@ class TestWeb(unittest.TestCase): # check we are always in the same session self.assertEqual(session_id_welcome, session.cookies['session_id_welcome']) - self.assertEqual('a','b') From 1abc8e7f13861732b84779df9ba510af6bc4996a Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 18:01:27 -0500 Subject: [PATCH 13/68] more test improvements, thanks Jonathan --- VERSION | 2 +- gluon/tests/test_web.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index b9c4af54..051f0d94 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 17:59:41) rc4 +Version 2.00.1 (2012-08-29 18:01:24) rc4 diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py index d85943ff..b2781ac8 100644 --- a/gluon/tests/test_web.py +++ b/gluon/tests/test_web.py @@ -38,3 +38,6 @@ class TestWeb(unittest.TestCase): # check we are always in the same session self.assertEqual(session_id_welcome, session.cookies['session_id_welcome']) + +if __name__ == '__main__': + unittest.main() From cfcc72de6fe72d812411cbc102badc22c73a14dc Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 18:14:24 -0500 Subject: [PATCH 14/68] simplied handling of postbacks --- VERSION | 2 +- gluon/contrib/webclient.py | 7 ++++--- gluon/tests/test_web.py | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/VERSION b/VERSION index 051f0d94..64166664 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 18:01:24) rc4 +Version 2.00.1 (2012-08-29 18:14:21) rc4 diff --git a/gluon/contrib/webclient.py b/gluon/contrib/webclient.py index 3101e28a..9f59bec4 100644 --- a/gluon/contrib/webclient.py +++ b/gluon/contrib/webclient.py @@ -21,6 +21,8 @@ class WebClient(object): return self.post(url,data=None,cookies=cookies,headers=headers) def post(self,url,data=None,cookies=None,headers=None,auth=None): + if data and '_formname' in data: + self.get(url,cookies=None,headers=None,auth=None) self.url = self.app+url if cookies is None: cookies = self.cookies if auth: @@ -62,8 +64,9 @@ class WebClient(object): def test_web2py_registration_and_login(): session = WebClient('http://127.0.0.1:8000/welcome/default/') - session.get('user/register') + session.get('index') session_id_welcome = session.cookies['session_id_welcome'] + data = dict(first_name = 'Homer', last_name = 'Simpson', email = 'homer@web2py.com', @@ -72,7 +75,6 @@ def test_web2py_registration_and_login(): _formname = 'register') session.post('user/register',data = data) - session.get('user/login') data = dict(email='homer@web2py.com', password='test', _formname = 'login') @@ -86,7 +88,6 @@ def test_web2py_registration_and_login(): # check we are always in the same session assert session_id_welcome == session.cookies['session_id_welcome'] - if __name__ == '__main__': test_web2py_registration_and_login() diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py index b2781ac8..82b1e01e 100644 --- a/gluon/tests/test_web.py +++ b/gluon/tests/test_web.py @@ -14,10 +14,11 @@ import unittest from gluon.contrib.webclient import WebClient class TestWeb(unittest.TestCase): - def testWebClient(self): + def testWebClient(self): session = WebClient('http://127.0.0.1:8000/welcome/default/') - session.get('user/register') + session.get('index') session_id_welcome = session.cookies['session_id_welcome'] + data = dict(first_name = 'Homer', last_name = 'Simpson', email = 'homer@web2py.com', @@ -25,8 +26,8 @@ class TestWeb(unittest.TestCase): password_two = 'test', _formname = 'register') session.post('user/register',data = data) + - session.get('user/login') data = dict(email='homer@web2py.com', password='test', _formname = 'login') From 9feff0879023d72f59fa0f3f430fb787c21fdd64 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 18:22:07 -0500 Subject: [PATCH 15/68] added timing and history to webclient --- VERSION | 2 +- gluon/contrib/webclient.py | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 64166664..b388c989 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 18:14:21) rc4 +Version 2.00.1 (2012-08-29 18:22:03) rc4 diff --git a/gluon/contrib/webclient.py b/gluon/contrib/webclient.py index 9f59bec4..2cec5321 100644 --- a/gluon/contrib/webclient.py +++ b/gluon/contrib/webclient.py @@ -7,13 +7,16 @@ web2py cookies and web2py forms. An example of usage is at the bottom. """ import re +import time import urllib import urllib2 + class WebClient(object): regex = re.compile('\\') def __init__(self,app=''): + self.history = [] self.app = app self.cookies = {} @@ -21,9 +24,10 @@ class WebClient(object): return self.post(url,data=None,cookies=cookies,headers=headers) def post(self,url,data=None,cookies=None,headers=None,auth=None): - if data and '_formname' in data: - self.get(url,cookies=None,headers=None,auth=None) self.url = self.app+url + if data and '_formname' in data and self.history and \ + self.history[-1][1]!=self.url: + self.get(url,cookies=None,headers=None,auth=None) if cookies is None: cookies = self.cookies if auth: auth_handler = urllib2.HTTPBasicAuthHandler() @@ -42,6 +46,7 @@ class WebClient(object): for key,value in headers_list: opener.addheaders.append((key,str(value))) if data is not None: + self.method = 'POST' # if there is only one form, set _formname automatically if not '_formname' in data and len(self.forms)==1: data['_formname'] = self.forms.keys()[0] @@ -50,9 +55,14 @@ class WebClient(object): data['_formname'] in self.forms: data['_formkey'] = self.forms[data['_formname']] data = urllib.urlencode(data) + t0 = time.time() self.request = opener.open(self.url,data) + self.time = time.time()-t0 else: + self.method = 'GET' + t0 = time.time() self.request = opener.open(self.url) + self.time = time.time()-t0 self.status = self.request.getcode() self.text = self.request.read() self.headers = dict(self.request.headers) @@ -61,6 +71,7 @@ class WebClient(object): self.forms = {} for match in WebClient.regex.finditer(self.text): self.forms[match.group('formname')] = match.group('formkey') + self.history.append((self.method,self.url,self.status,self.time)) def test_web2py_registration_and_login(): session = WebClient('http://127.0.0.1:8000/welcome/default/') @@ -88,6 +99,9 @@ def test_web2py_registration_and_login(): # check we are always in the same session assert session_id_welcome == session.cookies['session_id_welcome'] + for method, url, status, t in session.history: + print method, url, status, t + if __name__ == '__main__': test_web2py_registration_and_login() From 21dea6ed32af7b0622779d18c21e06d86a738c9e Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 18:27:30 -0500 Subject: [PATCH 16/68] more comments in webclient.py --- VERSION | 2 +- gluon/contrib/webclient.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index b388c989..25910d92 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 18:22:03) rc4 +Version 2.00.1 (2012-08-29 18:27:27) rc4 diff --git a/gluon/contrib/webclient.py b/gluon/contrib/webclient.py index 2cec5321..50224ab1 100644 --- a/gluon/contrib/webclient.py +++ b/gluon/contrib/webclient.py @@ -15,7 +15,8 @@ import urllib2 class WebClient(object): regex = re.compile('\\') - def __init__(self,app=''): + def __init__(self,app='', postbacks=True): + self.postbacks = postbacks self.history = [] self.app = app self.cookies = {} @@ -25,8 +26,10 @@ class WebClient(object): def post(self,url,data=None,cookies=None,headers=None,auth=None): self.url = self.app+url - if data and '_formname' in data and self.history and \ - self.history[-1][1]!=self.url: + if data and '_formname' in data and self.postbacks and \ + self.history and self.history[-1][1]!=self.url: + # to bypass the web2py CSRF need to get formkey + # before submitting the form self.get(url,cookies=None,headers=None,auth=None) if cookies is None: cookies = self.cookies if auth: @@ -35,14 +38,17 @@ class WebClient(object): opener = urllib2.build_opener(auth_handler) else: opener = urllib2.build_opener() + # copy headers from dict to list of key,value headers_list = [] for key,value in (headers or {}).iteritems(): if isinstance(value,(list,tuple)): for v in value: headers_list.append((key,v)) else: headers_list.append((key,value)) + # move cookies to headers for key,value in (cookies or {}).iteritems(): headers_list.append(('Cookie','%s=%s' % (key,value))) + # add headers to request for key,value in headers_list: opener.addheaders.append((key,str(value))) if data is not None: @@ -66,11 +72,14 @@ class WebClient(object): self.status = self.request.getcode() self.text = self.request.read() self.headers = dict(self.request.headers) + # parse headers into cookies self.cookies = dict(item[:item.find(';')].split('=') for item in \ self.headers.get('set-cookie','').split(',')) self.forms = {} + # find all forms and formkeys for match in WebClient.regex.finditer(self.text): self.forms[match.group('formname')] = match.group('formkey') + # log this request self.history.append((self.method,self.url,self.status,self.time)) def test_web2py_registration_and_login(): From 4fa1d8b0c7f9a7d560e7fbcb701e6919d848dd0d Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 18:31:23 -0500 Subject: [PATCH 17/68] changed vars in example and test for webclient --- VERSION | 2 +- gluon/contrib/webclient.py | 18 +++++++++--------- gluon/tests/test_web.py | 19 +++++++++++-------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/VERSION b/VERSION index 25910d92..cd153d7c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 18:27:27) rc4 +Version 2.00.1 (2012-08-29 18:31:20) rc4 diff --git a/gluon/contrib/webclient.py b/gluon/contrib/webclient.py index 50224ab1..c742669c 100644 --- a/gluon/contrib/webclient.py +++ b/gluon/contrib/webclient.py @@ -83,9 +83,9 @@ class WebClient(object): self.history.append((self.method,self.url,self.status,self.time)) def test_web2py_registration_and_login(): - session = WebClient('http://127.0.0.1:8000/welcome/default/') - session.get('index') - session_id_welcome = session.cookies['session_id_welcome'] + client = WebClient('http://127.0.0.1:8000/welcome/default/') + client.get('index') + session_id_welcome = client.cookies['session_id_welcome'] data = dict(first_name = 'Homer', last_name = 'Simpson', @@ -93,22 +93,22 @@ def test_web2py_registration_and_login(): password = 'test', password_two = 'test', _formname = 'register') - session.post('user/register',data = data) + client.post('user/register',data = data) data = dict(email='homer@web2py.com', password='test', _formname = 'login') - session.post('user/login',data = data) + client.post('user/login',data = data) - session.get('index') + client.get('index') # check registration and login were successful - assert 'Welcome Homer' in session.text + assert 'Welcome Homer' in client.text # check we are always in the same session - assert session_id_welcome == session.cookies['session_id_welcome'] + assert session_id_welcome == client.cookies['session_id_welcome'] - for method, url, status, t in session.history: + for method, url, status, t in client.history: print method, url, status, t if __name__ == '__main__': diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py index 82b1e01e..65b0a5b5 100644 --- a/gluon/tests/test_web.py +++ b/gluon/tests/test_web.py @@ -15,9 +15,10 @@ from gluon.contrib.webclient import WebClient class TestWeb(unittest.TestCase): def testWebClient(self): - session = WebClient('http://127.0.0.1:8000/welcome/default/') - session.get('index') - session_id_welcome = session.cookies['session_id_welcome'] + client = WebClient('http://127.0.0.1:8000/welcome/default/') + + client.get('index') + session_id_welcome = client.cookies['session_id_welcome'] data = dict(first_name = 'Homer', last_name = 'Simpson', @@ -25,20 +26,22 @@ class TestWeb(unittest.TestCase): password = 'test', password_two = 'test', _formname = 'register') - session.post('user/register',data = data) + client.post('user/register',data = data) data = dict(email='homer@web2py.com', password='test', _formname = 'login') - session.post('user/login',data = data) + client.post('user/login',data = data) - session.get('index') + client.get('index') + # check registration and login were successful - self.assertTrue('Welcome Homer' in session.text) + self.assertTrue('Welcome Homer' in client.text) + # check we are always in the same session self.assertEqual(session_id_welcome, - session.cookies['session_id_welcome']) + client.cookies['session_id_welcome']) if __name__ == '__main__': unittest.main() From 797ecd2e134c827247705825b2980290ebf80d6d Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 21:57:09 -0500 Subject: [PATCH 18/68] better webclient.py --- VERSION | 2 +- gluon/contrib/webclient.py | 172 +++++++++++++++++++++++++--------- gluon/tests/test_cache.py | 2 + gluon/tests/test_contribs.py | 7 +- gluon/tests/test_dal.py | 11 ++- gluon/tests/test_html.py | 3 +- gluon/tests/test_is_url.py | 1 + gluon/tests/test_languages.py | 17 ++-- gluon/tests/test_router.py | 119 +++++++++++------------ gluon/tests/test_routes.py | 139 +++++++++++++-------------- gluon/tests/test_storage.py | 23 ++--- gluon/tests/test_template.py | 1 + gluon/tests/test_utils.py | 1 + gluon/tests/test_web.py | 17 ++-- 14 files changed, 307 insertions(+), 208 deletions(-) diff --git a/VERSION b/VERSION index cd153d7c..e4f3de1b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 18:31:20) rc4 +Version 2.00.1 (2012-08-29 21:57:05) rc4 diff --git a/gluon/contrib/webclient.py b/gluon/contrib/webclient.py index c742669c..4bcfe7c8 100644 --- a/gluon/contrib/webclient.py +++ b/gluon/contrib/webclient.py @@ -2,8 +2,18 @@ Developed by Massimo Di Pierro Released under the web2py license (LGPL) -It an interface on top of urllib2 that allows authentication and understand -web2py cookies and web2py forms. An example of usage is at the bottom. +It an interface on top of urllib2 which simplifies scripting of http requests +mostly for testing purposes + +- customizable +- supports basic auth +- supports cookies +- supports session cookies (tested with web2py sessions) +- detects broken session +- detects web2py form postbacks and handles formname and formkey +- detects web2py tickets + +Some examples at the bottom. """ import re @@ -11,82 +21,153 @@ import time import urllib import urllib2 +DEFAULT_HEADERS = { + 'user-agent': 'Mozilla/4.0', # some servers are picky + 'accept-language': 'en', + } + +FORM_REGEX = re.compile('(\)?\') + +SESSION_REGEX = 'session_id_(?P.+)' class WebClient(object): - regex = re.compile('\\') - def __init__(self,app='', postbacks=True): + def __init__(self, + app = '', + postbacks = True, + default_headers = DEFAULT_HEADERS, + session_regex = SESSION_REGEX): + self.app = app self.postbacks = postbacks self.history = [] - self.app = app self.cookies = {} - + self.default_headers = default_headers + self.sessions = {} + self.session_regex = session_regex and re.compile(session_regex) + def get(self,url,cookies=None,headers=None,auth=None): return self.post(url,data=None,cookies=cookies,headers=headers) - + def post(self,url,data=None,cookies=None,headers=None,auth=None): self.url = self.app+url + + # if this POST form requires a postback do it if data and '_formname' in data and self.postbacks and \ - self.history and self.history[-1][1]!=self.url: + self.history and self.history[-1][1]!=self.url: # to bypass the web2py CSRF need to get formkey # before submitting the form - self.get(url,cookies=None,headers=None,auth=None) - if cookies is None: cookies = self.cookies + self.get(url,cookies=cookies,headers=headers,auth=auth) + + # unless cookies are specified, recycle cookies + if cookies is None: + cookies = self.cookies + cookies = cookies or {} + headers = headers or {} + + # if required do basic auth if auth: auth_handler = urllib2.HTTPBasicAuthHandler() auth_handler.add_password(**auth) opener = urllib2.build_opener(auth_handler) else: opener = urllib2.build_opener() + # copy headers from dict to list of key,value headers_list = [] - for key,value in (headers or {}).iteritems(): + for key,value in self.default_headers.iteritems(): + if not key in headers: + headers[key] = value + for key,value in headers.iteritems(): if isinstance(value,(list,tuple)): for v in value: headers_list.append((key,v)) else: headers_list.append((key,value)) + # move cookies to headers - for key,value in (cookies or {}).iteritems(): + for key,value in cookies.iteritems(): headers_list.append(('Cookie','%s=%s' % (key,value))) + # add headers to request for key,value in headers_list: opener.addheaders.append((key,str(value))) - if data is not None: - self.method = 'POST' - # if there is only one form, set _formname automatically - if not '_formname' in data and len(self.forms)==1: - data['_formname'] = self.forms.keys()[0] - # if there is no formkey but it is known, set it - if '_formname' in data and not '_formkey' in data and \ - data['_formname'] in self.forms: - data['_formkey'] = self.forms[data['_formname']] - data = urllib.urlencode(data) - t0 = time.time() - self.request = opener.open(self.url,data) + + # assume everything is ok and make http request + error = None + try: + if data is not None: + self.method = 'POST' + + # if there is only one form, set _formname automatically + if not '_formname' in data and len(self.forms)==1: + data['_formname'] = self.forms.keys()[0] + + # if there is no formkey but it is known, set it + if '_formname' in data and not '_formkey' in data and \ + data['_formname'] in self.forms: + data['_formkey'] = self.forms[data['_formname']] + + # time the POST request + data = urllib.urlencode(data) + t0 = time.time() + self.response = opener.open(self.url,data) + self.time = time.time()-t0 + else: + self.method = 'GET' + + # time the GET request + t0 = time.time() + self.response = opener.open(self.url) + self.time = time.time()-t0 + except urllib2.HTTPError, error: + # catch HTTP errors self.time = time.time()-t0 - else: - self.method = 'GET' - t0 = time.time() - self.request = opener.open(self.url) - self.time = time.time()-t0 - self.status = self.request.getcode() - self.text = self.request.read() - self.headers = dict(self.request.headers) + self.response = error + + self.status = self.response.getcode() + self.text = self.response.read() + self.headers = dict(self.response.headers) + + # treat web2py tickets as special types of errors + if error is not None: + if 'web2py_error' in self.headers: + raise RuntimeError, self.headers['web2py_error'] + else: + raise error + # parse headers into cookies - self.cookies = dict(item[:item.find(';')].split('=') for item in \ - self.headers.get('set-cookie','').split(',')) + if 'set-cookie' in self.headers: + self.cookies = dict( + item[:item.find(';')].split('=') for item in \ + self.headers['set-cookie'].split(',')) + else: + self.cookies = {} + + # check is a new session id has been issued, symptom of broken session + if self.session_regex is not None: + for cookie, value in self.cookies.iteritems(): + match = self.session_regex.match(cookie) + if match: + name = match.group('name') + if name in self.sessions and self.sessions[name]!=value: + raise RuntimeError, 'Broken sessions %s' % name + self.sessions[name] = value + + # find all forms and formkeys in page self.forms = {} - # find all forms and formkeys - for match in WebClient.regex.finditer(self.text): + for match in FORM_REGEX.finditer(self.text): self.forms[match.group('formname')] = match.group('formkey') + # log this request self.history.append((self.method,self.url,self.status,self.time)) def test_web2py_registration_and_login(): + # from gluon.contrib.webclient import WebClient + # start a web2py instance for testing + client = WebClient('http://127.0.0.1:8000/welcome/default/') client.get('index') - session_id_welcome = client.cookies['session_id_welcome'] + # register data = dict(first_name = 'Homer', last_name = 'Simpson', email = 'homer@web2py.com', @@ -95,22 +176,29 @@ def test_web2py_registration_and_login(): _formname = 'register') client.post('user/register',data = data) + # logout + client.get('user/logout') + + # login data = dict(email='homer@web2py.com', password='test', _formname = 'login') client.post('user/login',data = data) - - client.get('index') # check registration and login were successful + client.get('user/profile') assert 'Welcome Homer' in client.text - # check we are always in the same session - assert session_id_welcome == client.cookies['session_id_welcome'] - + # print some variables + print '\nsessions:\n',client.sessions + print '\nheaders:\n',client.headers + print '\ncookies:\n',client.cookies + print '\nforms:\n',client.forms + print for method, url, status, t in client.history: print method, url, status, t if __name__ == '__main__': test_web2py_registration_and_login() + diff --git a/gluon/tests/test_cache.py b/gluon/tests/test_cache.py index 15f29b6d..ec9b94d4 100644 --- a/gluon/tests/test_cache.py +++ b/gluon/tests/test_cache.py @@ -70,3 +70,5 @@ if __name__ == '__main__': setUpModule() # pre-python-2.7 unittest.main() tearDownModule() + + diff --git a/gluon/tests/test_contribs.py b/gluon/tests/test_contribs.py index 6af457ad..fbb4595f 100644 --- a/gluon/tests/test_contribs.py +++ b/gluon/tests/test_contribs.py @@ -14,7 +14,7 @@ else: from utils import md5_hash import contrib.fpdf as fpdf import contrib.pyfpdf as pyfpdf - + class TestContribs(unittest.TestCase): """ Tests the contrib package """ @@ -28,14 +28,15 @@ class TestContribs(unittest.TestCase): pdf = fpdf.FPDF() pdf.add_page() pdf.compress = False - pdf.set_font('Arial', '',14) + pdf.set_font('Arial', '',14) pdf.ln(10) pdf.write(5, 'hello world') pdf_out = pdf.output('', 'S') - + self.assertTrue(fpdf.FPDF_VERSION in pdf_out, 'version string') self.assertTrue('hello world' in pdf_out, 'sample message') if __name__ == '__main__': unittest.main() + diff --git a/gluon/tests/test_dal.py b/gluon/tests/test_dal.py index b60fbe16..419d78d2 100644 --- a/gluon/tests/test_dal.py +++ b/gluon/tests/test_dal.py @@ -176,9 +176,9 @@ class TestTable(unittest.TestCase): self.assertRaises(SyntaxError, Table, None, 'test', None) persons = Table(None, 'persons', - Field('firstname','string'), + Field('firstname','string'), Field('lastname', 'string')) - + # Does it have the correct fields? self.assert_(set(persons.fields).issuperset(set(['firstname', @@ -519,7 +519,7 @@ class TestComputedFields(unittest.TestCase): def testRun(self): db = DAL('sqlite:memory:') - db.define_table('t', + db.define_table('t', Field('a'), Field('b',default='x'), Field('c',compute=lambda r: r.a+r.b)) @@ -547,7 +547,7 @@ class TestImportExportFields(unittest.TestCase): db(db.pet).delete() db(db.person).delete() stream = cStringIO.StringIO(stream.getvalue()) - db.import_from_csv_file(stream) + db.import_from_csv_file(stream) assert db(db.person.id==db.pet.friend)(db.person.name==db.pet.name).count()==10 db.pet.drop() db.person.drop() @@ -569,7 +569,7 @@ class TestImportExportUuidFields(unittest.TestCase): stream = cStringIO.StringIO() db.export_to_csv_file(stream) stream = cStringIO.StringIO(stream.getvalue()) - db.import_from_csv_file(stream) + db.import_from_csv_file(stream) assert db(db.person).count()==10 assert db(db.person.id==db.pet.friend)(db.person.name==db.pet.name).count()==20 db.pet.drop() @@ -579,3 +579,4 @@ class TestImportExportUuidFields(unittest.TestCase): if __name__ == '__main__': unittest.main() tearDownModule() + diff --git a/gluon/tests/test_html.py b/gluon/tests/test_html.py index 752ed921..ff906b56 100644 --- a/gluon/tests/test_html.py +++ b/gluon/tests/test_html.py @@ -27,7 +27,7 @@ class TestBareHelpers(unittest.TestCase): def testHR(self): self.assertEqual(HR(_a='1', _b='2').xml(), '
') - + def testIMG(self): self.assertEqual(IMG(_a='1', _b='2').xml(), '') @@ -209,3 +209,4 @@ class TestBareHelpers(unittest.TestCase): if __name__ == '__main__': unittest.main() + diff --git a/gluon/tests/test_is_url.py b/gluon/tests/test_is_url.py index 60ffdd45..7a17496c 100644 --- a/gluon/tests/test_is_url.py +++ b/gluon/tests/test_is_url.py @@ -642,3 +642,4 @@ class TestUnicode(unittest.TestCase): if __name__ == '__main__': unittest.main() + diff --git a/gluon/tests/test_languages.py b/gluon/tests/test_languages.py index bcff8de4..070fb874 100644 --- a/gluon/tests/test_languages.py +++ b/gluon/tests/test_languages.py @@ -32,7 +32,7 @@ try: return True class TestLanguagesParallel(unittest.TestCase): - + def setUp(self): self.filename = tempfile.mktemp() contents = dict() @@ -46,7 +46,7 @@ try: os.remove(self.filename) except: pass - + def test_reads_and_writes(self): readwriters = 10 pool = multiprocessing.Pool(processes = readwriters) @@ -56,17 +56,17 @@ try: class TestTranslations(unittest.TestCase): - - def setUp(self): + + def setUp(self): self.request = Storage() self.request.folder = 'applications/welcome' self.request.env = Storage() self.request.env.http_accept_language = 'en' - - + + def tearDown(self): pass - + def test_plain(self): T = languages.translator(self.request) self.assertEqual(str(T('Hello World')), @@ -89,6 +89,7 @@ try: except ImportError: logging.warning("Skipped test case, no multiprocessing module.") - + if __name__ == '__main__': unittest.main() + diff --git a/gluon/tests/test_router.py b/gluon/tests/test_router.py index 5cf7b1aa..f554f420 100644 --- a/gluon/tests/test_router.py +++ b/gluon/tests/test_router.py @@ -54,7 +54,7 @@ def setUpModule(): routes = open(abspath('applications', 'examples', 'routes.py'), 'w') routes.write("routers=dict(examples=dict(default_function='exdef'))") routes.close() - + # create language files for examples app for lang in ('en', 'it'): os.mkdir(abspath('applications', 'examples', 'static', lang)) @@ -92,11 +92,11 @@ class TestRouter(unittest.TestCase): self.assertRaises(SyntaxError, load, rdict=dict(BASE=dict(), app=dict(default_application="name"))) try: # 2.7+ only - self.assertRaisesRegexp(SyntaxError, "invalid syntax", + self.assertRaisesRegexp(SyntaxError, "invalid syntax", load, data='x:y') - self.assertRaisesRegexp(SyntaxError, "unknown key", + self.assertRaisesRegexp(SyntaxError, "unknown key", load, rdict=dict(BASE=dict(badkey="value"))) - self.assertRaisesRegexp(SyntaxError, "BASE-only key", + self.assertRaisesRegexp(SyntaxError, "BASE-only key", load, rdict=dict(BASE=dict(), app=dict(default_application="name"))) except AttributeError: pass @@ -133,9 +133,9 @@ class TestRouter(unittest.TestCase): self.assertEqual(filter_url('http://domain.com/admin/default/abc', out=True), '/admin/abc') def test_router_specific(self): - """ - Test app-specific routes.py - + """ + Test app-specific routes.py + Note that make_apptree above created applications/examples/routes.py with a default_function. """ load(rdict=dict()) @@ -164,7 +164,7 @@ class TestRouter(unittest.TestCase): self.assertEqual(filter_url('http://domain.com/welcome/default/index/arg1', out=True), '/index/arg1') self.assertEqual(filter_url('http://domain.com/welcome/default/abc', out=True), '/abc') self.assertEqual(filter_url('http://domain.com/welcome/default/admin', out=True), '/default/admin') - self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True), + self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True), '/welcome/static/abc') self.assertEqual(filter_url('http://domain.com/welcome/appadmin/index', out=True), '/appadmin') self.assertEqual(filter_url('http://domain.com/welcome/appadmin/abc', out=True), '/appadmin/abc') @@ -183,7 +183,7 @@ class TestRouter(unittest.TestCase): self.assertEqual(filter_url('http://domain.com/welcome/default/index', out=True), '/default') self.assertEqual(filter_url('http://domain.com/welcome/default/index/arg1', out=True), '/default/index/arg1') self.assertEqual(filter_url('http://domain.com/welcome/default/abc', out=True), '/default/abc') - self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True), + self.assertEqual(filter_url('http://domain.com/welcome/static/abc', out=True), '/welcome/static/abc') self.assertEqual(filter_url('http://domain.com/welcome/appadmin/index', out=True), '/appadmin') self.assertEqual(filter_url('http://domain.com/welcome/appadmin/abc', out=True), '/appadmin/abc') @@ -386,7 +386,7 @@ class TestRouter(unittest.TestCase): self.assertEqual(filter_url('http://domain2.com/f2'), '/welcome/default/f2') self.assertEqual(filter_url('http://domain2.com/other/f3'), '/welcome/other/f3') - + def test_router_domains(self): ''' Test URLs that map domains @@ -429,7 +429,7 @@ class TestRouter(unittest.TestCase): self.assertEqual(filter_url('http://domain1.com/abc.css'), '/app1/c1/abc.css') self.assertEqual(filter_url('http://domain1.com/index/abc'), "/app1/c1/index ['abc']") self.assertEqual(filter_url('http://domain2.com/app1'), "/app2a/c2a/app1") - + self.assertEqual(filter_url('https://domain1.com/app1/ctr/fcn', domain=('app1',None), out=True), "/ctr/fcn") self.assertEqual(filter_url('https://www.domain1.com/app1/ctr/fcn', domain=('app1',None), out=True), "/ctr/fcn") @@ -534,7 +534,7 @@ class TestRouter(unittest.TestCase): self.assertEqual(filter_url('https://domain.com/init/ctr/index', out=True), "/ctr") self.assertEqual(filter_url('http://domain.com/init/default/fcn?query', out=True), "/fcn?query") self.assertEqual(filter_url('http://domain.com/init/default/fcn#anchor', out=True), "/fcn#anchor") - self.assertEqual(filter_url('http://domain.com/init/default/fcn?query#anchor', out=True), + self.assertEqual(filter_url('http://domain.com/init/default/fcn?query#anchor', out=True), "/fcn?query#anchor") router_out['BASE']['map_static'] = True @@ -576,11 +576,11 @@ class TestRouter(unittest.TestCase): ), ) load(rdict=router_functions) - + # outbound self.assertEqual(str(URL(a='init', c='default', f='f', args=['arg1'])), "/init/f/arg1") self.assertEqual(str(URL(a='init', c='default', f='index', args=['arg1'])), "/init/index/arg1") - + self.assertEqual(str(URL(a='app', c='default', f='index', args=['arg1'])), "/arg1") self.assertEqual(str(URL(a='app', c='default', f='user', args=['arg1'])), "/user/arg1") self.assertEqual(str(URL(a='app', c='default', f='user', args=['index'])), "/user/index") @@ -631,7 +631,7 @@ class TestRouter(unittest.TestCase): ) load(rdict=router_functions) - + # outbound self.assertEqual(str(URL(a='init', c='default', f='index', args=['arg1'])), "/arg1") self.assertEqual(str(URL(a='init', c='default', f='user', args=['arg1'])), "/user/arg1") @@ -673,18 +673,18 @@ class TestRouter(unittest.TestCase): ) load(rdict=router_hyphen) self.assertEqual(filter_url('http://domain.com/init/default/fcn_1', out=True), "/fcn_1") - self.assertEqual(filter_url('http://domain.com/static/filename-with_underscore'), + self.assertEqual(filter_url('http://domain.com/static/filename-with_underscore'), "%s/applications/init/static/filename-with_underscore" % root) - self.assertEqual(filter_url('http://domain.com/init/static/filename-with_underscore', out=True), + self.assertEqual(filter_url('http://domain.com/init/static/filename-with_underscore', out=True), "/init/static/filename-with_underscore") - self.assertEqual(filter_url('http://domain.com/app2/fcn_1'), + self.assertEqual(filter_url('http://domain.com/app2/fcn_1'), "/app2/default/fcn_1") - self.assertEqual(filter_url('http://domain.com/app2/ctr/fcn_1', domain=('app2',None), out=True), + self.assertEqual(filter_url('http://domain.com/app2/ctr/fcn_1', domain=('app2',None), out=True), "/ctr/fcn_1") - self.assertEqual(filter_url('http://domain.com/app2/static/filename-with_underscore', domain=('app2',None), out=True), + self.assertEqual(filter_url('http://domain.com/app2/static/filename-with_underscore', domain=('app2',None), out=True), "/app2/static/filename-with_underscore") - self.assertEqual(filter_url('http://domain.com/app2/static/filename-with_underscore'), + self.assertEqual(filter_url('http://domain.com/app2/static/filename-with_underscore'), "%s/applications/app2/static/filename-with_underscore" % root) self.assertEqual(str(URL(a='init', c='default', f='a_b')), "/a_b") @@ -731,7 +731,7 @@ class TestRouter(unittest.TestCase): self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it', out=True), "/admin/it/static/file") self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it-it', out=True), "/admin/it-it/static/file") self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='it', out=True), "/welcome/ctr/fcn") - self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn") router_lang['admin']['map_static'] = True load(rdict=router_lang) @@ -742,7 +742,7 @@ class TestRouter(unittest.TestCase): self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it', out=True), "/it/static/file") self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it-it', out=True), "/it-it/static/file") self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='it', out=True), "/welcome/ctr/fcn") - self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn") router_lang['admin']['map_static'] = False router_lang['examples']['map_static'] = False @@ -754,7 +754,7 @@ class TestRouter(unittest.TestCase): self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it', out=True), "/admin/static/it/file") self.assertEqual(filter_url('https://domain.com/admin/static/file', lang='it-it', out=True), "/admin/static/it-it/file") self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='it', out=True), "/welcome/ctr/fcn") - self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn") + self.assertEqual(filter_url('https://domain.com/welcome/ctr/fcn', lang='es', out=True), "/welcome/ctr/fcn") self.assertEqual(filter_url('http://domain.com/static/file'), "%s/applications/admin/static/file" % root) self.assertEqual(filter_url('http://domain.com/en/static/file'), "%s/applications/admin/static/file" % root) self.assertEqual(filter_url('http://domain.com/examples/en/static/file'), "%s/applications/examples/static/en/file" % root) @@ -865,21 +865,21 @@ class TestRouter(unittest.TestCase): Test URL args parsing/generation ''' load(rdict=dict()) - self.assertEqual(filter_url('http://domain.com/init/default/f/arg1'), + self.assertEqual(filter_url('http://domain.com/init/default/f/arg1'), "/init/default/f ['arg1']") - self.assertEqual(filter_url('http://domain.com/init/default/f/arg1/'), + self.assertEqual(filter_url('http://domain.com/init/default/f/arg1/'), "/init/default/f ['arg1']") - self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//'), + self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//'), "/init/default/f ['arg1', '']") - self.assertEqual(filter_url('http://domain.com/init/default/f//arg1'), + self.assertEqual(filter_url('http://domain.com/init/default/f//arg1'), "/init/default/f ['', 'arg1']") - self.assertEqual(filter_url('http://domain.com/init/default/f/arg1/arg2'), + self.assertEqual(filter_url('http://domain.com/init/default/f/arg1/arg2'), "/init/default/f ['arg1', 'arg2']") - self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//arg2'), + self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//arg2'), "/init/default/f ['arg1', '', 'arg2']") - self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//arg3/'), + self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//arg3/'), "/init/default/f ['arg1', '', 'arg3']") - self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//arg3//'), + self.assertEqual(filter_url('http://domain.com/init/default/f/arg1//arg3//'), "/init/default/f ['arg1', '', 'arg3', '']") self.assertEqual(filter_url('http://domain.com/init/default/f', out=True), "/f") @@ -903,12 +903,12 @@ class TestRouter(unittest.TestCase): load(rdict=dict()) self.assertEqual(str(URL(a='a', c='c', f='f', anchor='anchor')), "/a/c/f#anchor") args = ['a1', 'a2'] - self.assertEqual(str(URL(a='a', c='c', f='f', args=args, anchor='anchor')), + self.assertEqual(str(URL(a='a', c='c', f='f', args=args, anchor='anchor')), "/a/c/f/a1/a2#anchor") vars = dict(v1=1, v2=2) - self.assertEqual(str(URL(a='a', c='c', f='f', vars=vars, anchor='anchor')), + self.assertEqual(str(URL(a='a', c='c', f='f', vars=vars, anchor='anchor')), "/a/c/f?v1=1&v2=2#anchor") - self.assertEqual(str(URL(a='a', c='c', f='f', args=args, vars=vars, anchor='anchor')), + self.assertEqual(str(URL(a='a', c='c', f='f', args=args, vars=vars, anchor='anchor')), "/a/c/f/a1/a2?v1=1&v2=2#anchor") self.assertEqual(str(URL(a='init', c='default', f='index')), "/") @@ -938,11 +938,11 @@ class TestRouter(unittest.TestCase): ), ) load(rdict=router_path_prefix) - self.assertEqual(str(URL(a='a1', c='c1a', f='f')), + self.assertEqual(str(URL(a='a1', c='c1a', f='f')), "/path/to/apps/c1a/f") - self.assertEqual(str(URL(a='a2', c='c', f='f')), + self.assertEqual(str(URL(a='a2', c='c', f='f')), "/path/to/apps/a2/c/f") - self.assertEqual(str(URL(a='a2', c='c2', f='f')), + self.assertEqual(str(URL(a='a2', c='c2', f='f')), "/path/to/apps/a2/c2/f") self.assertEqual(filter_url('http://domain.com/a1/'), "/a1/default/index") self.assertEqual(filter_url('http://domain.com/path/to/apps/a1/'), "/a1/default/index") @@ -958,35 +958,35 @@ class TestRouter(unittest.TestCase): r.env.http_host = 'domain.com' r.env.wsgi_url_scheme = 'httpx' # distinguish incoming scheme self.assertEqual(str(URL(r=r, a='a', c='c', f='f')), "/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host=True)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host=True)), "httpx://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com')), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com')), "httpx://host.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True)), "httpx://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False)), "/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https')), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https')), "https://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss')), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss')), "wss://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host=True)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host=True)), "httpx://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https', host=True)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https', host=True)), "https://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host=True)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host=True)), "httpx://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host='host.com')), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host='host.com')), "httpx://host.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host='host.com')), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host='host.com')), "httpx://host.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', port=1234)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', port=1234)), "httpx://domain.com:1234/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, port=1234)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, port=1234)), "httpx://domain.com:1234/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com', port=1234)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com', port=1234)), "httpx://host.com:1234/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss', host='host.com', port=1234)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss', host='host.com', port=1234)), "wss://host.com:1234/a/c/f") def test_request_uri(self): @@ -995,15 +995,15 @@ class TestRouter(unittest.TestCase): ''' load(rdict=dict()) - self.assertEqual(filter_url('http://domain.com/abc', env=True).request_uri, + self.assertEqual(filter_url('http://domain.com/abc', env=True).request_uri, '/init/default/abc') - self.assertEqual(filter_url('http://domain.com/abc?def', env=True).request_uri, + self.assertEqual(filter_url('http://domain.com/abc?def', env=True).request_uri, '/init/default/abc?def') - self.assertEqual(filter_url('http://domain.com/index/abc', env=True).request_uri, + self.assertEqual(filter_url('http://domain.com/index/abc', env=True).request_uri, "/init/default/index/abc") - self.assertEqual(filter_url('http://domain.com/abc/def', env=True).request_uri, + self.assertEqual(filter_url('http://domain.com/abc/def', env=True).request_uri, "/init/default/abc/def") - self.assertEqual(filter_url('http://domain.com/index/a%20bc', env=True).request_uri, + self.assertEqual(filter_url('http://domain.com/index/a%20bc', env=True).request_uri, "/init/default/index/a%20bc") def test_request_collide(self): @@ -1022,7 +1022,7 @@ class TestRouter(unittest.TestCase): ), ) load(rdict=router_collide) - + # basic inbound self.assertEqual(filter_url('http://ex.domain.com'), '/examples/default/exdef') self.assertEqual(filter_url('http://ad.domain.com'), '/admin/default/index') @@ -1053,3 +1053,4 @@ if __name__ == '__main__': setUpModule() # pre-2.7 unittest.main() tearDownModule() + diff --git a/gluon/tests/test_routes.py b/gluon/tests/test_routes.py index c3b171d6..abda14dc 100644 --- a/gluon/tests/test_routes.py +++ b/gluon/tests/test_routes.py @@ -112,9 +112,9 @@ routes_in = ( self.assertEqual(filter_url('http://localhost:8000/service/person/create?var1=val1'), "/app/default/call ['json', 'create'] ?model=person&var1=val1") def test_routes_specific(self): - """ - Test app-specific routes.py - + """ + Test app-specific routes.py + Note that make_apptree above created applications/examples/routes.py with a default_function. """ data = r''' @@ -190,59 +190,59 @@ default_application = 'defapp' Test URL args parsing/generation ''' data = r'''routes_in = [ - ('/robots.txt', '/welcome/static/robots.txt'), - ('/favicon.ico', '/welcome/static/favicon.ico'), - ('/admin$anything', '/admin$anything'), - ('.*:https?://(.*\\.)?domain1.com:$method /', '/app1/default'), - ('.*:https?://(.*\\.)?domain1.com:$method /static/$anything', '/app1/static/$anything'), - ('.*:https?://(.*\\.)?domain1.com:$method /appadmin/$anything', '/app1/appadmin/$anything'), - ('.*:https?://(.*\\.)?domain1.com:$method /$anything', '/app1/default/$anything'), - ('.*:https?://(.*\\.)?domain2.com:$method /', '/app2/default'), - ('.*:https?://(.*\\.)?domain2.com:$method /static/$anything', '/app2/static/$anything'), - ('.*:https?://(.*\\.)?domain2.com:$method /appadmin/$anything', '/app2/appadmin/$anything'), - ('.*:https?://(.*\\.)?domain2.com:$method /$anything', '/app2/default/$anything'), - ('.*:https?://(.*\\.)?domain3.com:$method /', '/app3/defcon3'), - ('.*:https?://(.*\\.)?domain3.com:$method /static/$anything', '/app3/static/$anything'), - ('.*:https?://(.*\\.)?domain3.com:$method /appadmin/$anything', '/app3/appadmin/$anything'), + ('/robots.txt', '/welcome/static/robots.txt'), + ('/favicon.ico', '/welcome/static/favicon.ico'), + ('/admin$anything', '/admin$anything'), + ('.*:https?://(.*\\.)?domain1.com:$method /', '/app1/default'), + ('.*:https?://(.*\\.)?domain1.com:$method /static/$anything', '/app1/static/$anything'), + ('.*:https?://(.*\\.)?domain1.com:$method /appadmin/$anything', '/app1/appadmin/$anything'), + ('.*:https?://(.*\\.)?domain1.com:$method /$anything', '/app1/default/$anything'), + ('.*:https?://(.*\\.)?domain2.com:$method /', '/app2/default'), + ('.*:https?://(.*\\.)?domain2.com:$method /static/$anything', '/app2/static/$anything'), + ('.*:https?://(.*\\.)?domain2.com:$method /appadmin/$anything', '/app2/appadmin/$anything'), + ('.*:https?://(.*\\.)?domain2.com:$method /$anything', '/app2/default/$anything'), + ('.*:https?://(.*\\.)?domain3.com:$method /', '/app3/defcon3'), + ('.*:https?://(.*\\.)?domain3.com:$method /static/$anything', '/app3/static/$anything'), + ('.*:https?://(.*\\.)?domain3.com:$method /appadmin/$anything', '/app3/appadmin/$anything'), ('.*:https?://(.*\\.)?domain3.com:$method /$anything', '/app3/defcon3/$anything'), - ('/', '/welcome/default'), - ('/welcome/default/$anything', '/welcome/default/$anything'), - ('/welcome/$anything', '/welcome/default/$anything'), - ('/static/$anything', '/welcome/static/$anything'), - ('/appadmin/$anything', '/welcome/appadmin/$anything'), - ('/$anything', '/welcome/default/$anything'), + ('/', '/welcome/default'), + ('/welcome/default/$anything', '/welcome/default/$anything'), + ('/welcome/$anything', '/welcome/default/$anything'), + ('/static/$anything', '/welcome/static/$anything'), + ('/appadmin/$anything', '/welcome/appadmin/$anything'), + ('/$anything', '/welcome/default/$anything'), ] routes_out = [ - ('/welcome/static/$anything', '/static/$anything'), - ('/welcome/appadmin/$anything', '/appadmin/$anything'), - ('/welcome/default/$anything', '/$anything'), - ('/app1/static/$anything', '/static/$anything'), - ('/app1/appadmin/$anything', '/appadmin/$anything'), - ('/app1/default/$anything', '/$anything'), - ('/app2/static/$anything', '/static/$anything'), - ('/app2/appadmin/$anything', '/appadmin/$anything'), - ('/app2/default/$anything', '/$anything'), - ('/app3/static/$anything', '/static/$anything'), - ('/app3/appadmin/$anything', '/appadmin/$anything'), + ('/welcome/static/$anything', '/static/$anything'), + ('/welcome/appadmin/$anything', '/appadmin/$anything'), + ('/welcome/default/$anything', '/$anything'), + ('/app1/static/$anything', '/static/$anything'), + ('/app1/appadmin/$anything', '/appadmin/$anything'), + ('/app1/default/$anything', '/$anything'), + ('/app2/static/$anything', '/static/$anything'), + ('/app2/appadmin/$anything', '/appadmin/$anything'), + ('/app2/default/$anything', '/$anything'), + ('/app3/static/$anything', '/static/$anything'), + ('/app3/appadmin/$anything', '/appadmin/$anything'), ('/app3/defcon3/$anything', '/$anything') ] ''' load(data=data) - self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1'), + self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1'), "/welcome/default/f ['arg1']") - self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1/'), + self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1/'), "/welcome/default/f ['arg1']") - self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//'), + self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//'), "/welcome/default/f ['arg1', '']") - self.assertEqual(filter_url('http://domain.com/welcome/default/f//arg1'), + self.assertEqual(filter_url('http://domain.com/welcome/default/f//arg1'), "/welcome/default/f ['', 'arg1']") - self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1/arg2'), + self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1/arg2'), "/welcome/default/f ['arg1', 'arg2']") - self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg2'), + self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg2'), "/welcome/default/f ['arg1', '', 'arg2']") - self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg3/'), + self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg3/'), "/welcome/default/f ['arg1', '', 'arg3']") - self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg3//'), + self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg3//'), "/welcome/default/f ['arg1', '', 'arg3', '']") self.assertEqual(filter_url('http://domain.com/welcome/default/f', out=True), "/f") @@ -263,16 +263,16 @@ routes_out = [ load(data='') self.assertEqual(str(URL(a='a', c='c', f='f', anchor='anchor')), "/a/c/f#anchor") args = ['a1', 'a2'] - self.assertEqual(str(URL(a='a', c='c', f='f', args=args, anchor='anchor')), + self.assertEqual(str(URL(a='a', c='c', f='f', args=args, anchor='anchor')), "/a/c/f/a1/a2#anchor") vars = dict(v1=1, v2=2) - self.assertEqual(str(URL(a='a', c='c', f='f', vars=vars, anchor='anchor')), + self.assertEqual(str(URL(a='a', c='c', f='f', vars=vars, anchor='anchor')), "/a/c/f?v1=1&v2=2#anchor") - self.assertEqual(str(URL(a='a', c='c', f='f', args=args, vars=vars, anchor='anchor')), + self.assertEqual(str(URL(a='a', c='c', f='f', args=args, vars=vars, anchor='anchor')), "/a/c/f/a1/a2?v1=1&v2=2#anchor") data = r'''routes_out = [ - ('/init/default/index', '/'), + ('/init/default/index', '/'), ]''' load(data=data) self.assertEqual(str(URL(a='init', c='default', f='index')), @@ -281,7 +281,7 @@ routes_out = [ "/init/default/index#anchor") data = r'''routes_out = [ - (r'/init/default/index(?P(#.*)?)', r'/\g'), + (r'/init/default/index(?P(#.*)?)', r'/\g'), ]''' load(data=data) self.assertEqual(str(URL(a='init', c='default', f='index')), @@ -290,7 +290,7 @@ routes_out = [ "/#anchor") data = r'''routes_out = [ - (r'/init/default/index(?P([?#].*)?)', r'/\g'), + (r'/init/default/index(?P([?#].*)?)', r'/\g'), ]''' load(data=data) self.assertEqual(str(URL(a='init', c='default', f='index')), @@ -313,35 +313,35 @@ routes_out = [ r.env.http_host = 'domain.com' r.env.wsgi_url_scheme = 'httpx' # distinguish incoming scheme self.assertEqual(str(URL(r=r, a='a', c='c', f='f')), "/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host=True)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host=True)), "httpx://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com')), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com')), "httpx://host.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True)), "httpx://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False)), "/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https')), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https')), "https://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss')), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss')), "wss://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host=True)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host=True)), "httpx://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https', host=True)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https', host=True)), "https://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host=True)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host=True)), "httpx://domain.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host='host.com')), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host='host.com')), "httpx://host.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host='host.com')), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host='host.com')), "httpx://host.com/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', port=1234)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', port=1234)), "httpx://domain.com:1234/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, port=1234)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, port=1234)), "httpx://domain.com:1234/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com', port=1234)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com', port=1234)), "httpx://host.com:1234/a/c/f") - self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss', host='host.com', port=1234)), + self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss', host='host.com', port=1234)), "wss://host.com:1234/a/c/f") def test_request_uri(self): @@ -349,18 +349,18 @@ routes_out = [ Test REQUEST_URI in env ''' data = r'''routes_in = [ - ('/abc', '/init/default/abc'), - ('/index/$anything', '/init/default/index/$anything'), + ('/abc', '/init/default/abc'), + ('/index/$anything', '/init/default/index/$anything'), ] ''' load(data=data) - self.assertEqual(filter_url('http://domain.com/abc', env=True).request_uri, + self.assertEqual(filter_url('http://domain.com/abc', env=True).request_uri, '/init/default/abc') - self.assertEqual(filter_url('http://domain.com/abc?def', env=True).request_uri, + self.assertEqual(filter_url('http://domain.com/abc?def', env=True).request_uri, '/init/default/abc?def') - self.assertEqual(filter_url('http://domain.com/index/abc', env=True).request_uri, + self.assertEqual(filter_url('http://domain.com/index/abc', env=True).request_uri, "/init/default/index/abc") - self.assertEqual(filter_url('http://domain.com/index/a%20bc', env=True).request_uri, + self.assertEqual(filter_url('http://domain.com/index/a%20bc', env=True).request_uri, "/init/default/index/a bc") @@ -368,3 +368,4 @@ if __name__ == '__main__': setUpModule() # pre-2.7 unittest.main() tearDownModule() + diff --git a/gluon/tests/test_storage.py b/gluon/tests/test_storage.py index 330ea766..6d35860b 100644 --- a/gluon/tests/test_storage.py +++ b/gluon/tests/test_storage.py @@ -21,38 +21,38 @@ class TestStorage(unittest.TestCase): """ Tests Storage attribute handling """ s = Storage(a=1) - + self.assertEqual(s.a, 1) self.assertEqual(s['a'], 1) self.assertEqual(s.b, None) - + s.b = 2 self.assertEqual(s.a, 1) self.assertEqual(s['a'], 1) self.assertEqual(s.b, 2) self.assertEqual(s['b'], 2) - + s['c'] = 3 self.assertEqual(s.c, 3) self.assertEqual(s['c'], 3) s.d = list() self.assertTrue(s.d is s['d']) - - + + def test_store_none(self): """ Test Storage store-None handling s.key = None deletes an item s['key'] = None sets the item to None """ - + s = Storage(a=1) - + self.assertTrue('a' in s) self.assertFalse('b' in s) s.a = None # self.assertFalse('a' in s) # how about this? - + s.a = 1 self.assertTrue('a' in s) s['a'] = None @@ -62,12 +62,12 @@ class TestStorage(unittest.TestCase): def test_item(self): """ Tests Storage item handling """ - + s = Storage() - + self.assertEqual(s.d, None) self.assertEqual(s['d'], None) - #self.assertRaises(KeyError, lambda x: s[x], 'd') # old Storage + #self.assertRaises(KeyError, lambda x: s[x], 'd') # old Storage s.a = 1 s['a'] = None self.assertEquals(s.a, None) @@ -76,3 +76,4 @@ class TestStorage(unittest.TestCase): if __name__ == '__main__': unittest.main() + diff --git a/gluon/tests/test_template.py b/gluon/tests/test_template.py index 810c9876..bba6cded 100644 --- a/gluon/tests/test_template.py +++ b/gluon/tests/test_template.py @@ -58,3 +58,4 @@ class TestVirtualFields(unittest.TestCase): if __name__ == '__main__': unittest.main() + diff --git a/gluon/tests/test_utils.py b/gluon/tests/test_utils.py index 97d4d2b2..7c0465dc 100644 --- a/gluon/tests/test_utils.py +++ b/gluon/tests/test_utils.py @@ -25,3 +25,4 @@ class TestUtils(unittest.TestCase): if __name__ == '__main__': unittest.main() + diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py index 65b0a5b5..9d5e1448 100644 --- a/gluon/tests/test_web.py +++ b/gluon/tests/test_web.py @@ -18,8 +18,8 @@ class TestWeb(unittest.TestCase): client = WebClient('http://127.0.0.1:8000/welcome/default/') client.get('index') - session_id_welcome = client.cookies['session_id_welcome'] + # register data = dict(first_name = 'Homer', last_name = 'Simpson', email = 'homer@web2py.com', @@ -28,20 +28,19 @@ class TestWeb(unittest.TestCase): _formname = 'register') client.post('user/register',data = data) - + # logout + client.get('user/logout') + + # login again data = dict(email='homer@web2py.com', password='test', _formname = 'login') client.post('user/login',data = data) - - client.get('index') # check registration and login were successful - self.assertTrue('Welcome Homer' in client.text) - - # check we are always in the same session - self.assertEqual(session_id_welcome, - client.cookies['session_id_welcome']) + client.get('index') + self.assertTrue('Welcome Homer' in client.text) if __name__ == '__main__': unittest.main() + From 6a9aa69c9359c3221b8f7f961f255912a68bebe7 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 29 Aug 2012 22:00:59 -0500 Subject: [PATCH 19/68] 2.0.2 --- Makefile | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4eace126..bcc81d39 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ update: wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py echo "remember that pymysql was tweaked" src: - echo 'Version 2.00.1 ('`date +%Y-%m-%d\ %H:%M:%S`') rc4' > VERSION + echo 'Version 2.0.2 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION ### rm -f all junk files make clean ### clean up baisc apps diff --git a/VERSION b/VERSION index e4f3de1b..85677852 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 21:57:05) rc4 +Version 2.0.2 (2012-08-29 22:00:56) stable From 04fe5199f1cc22cff20ca804b6215b504177afd8 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 08:05:09 -0500 Subject: [PATCH 20/68] allow tasks without timeout --- Makefile | 2 +- VERSION | 2 +- applications/welcome/languages/default.py | 88 +++++++++++++++++++++++ gluon/scheduler.py | 3 +- 4 files changed, 92 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index bcc81d39..7e7c9920 100644 --- a/Makefile +++ b/Makefile @@ -127,5 +127,5 @@ push: tag: git tag -l '$(S)' hg tag -l '$(S)' - make commit + make commit S='$(S)' make push diff --git a/VERSION b/VERSION index 85677852..c330710c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-29 22:00:56) stable +Version 2.0.2 (2012-08-30 08:05:05) stable diff --git a/applications/welcome/languages/default.py b/applications/welcome/languages/default.py index 7fddedd2..4586b206 100644 --- a/applications/welcome/languages/default.py +++ b/applications/welcome/languages/default.py @@ -6,11 +6,99 @@ '%s %%(shop[0])': '%s %%(shop[0])', '%s %%{shop[0]}': '%s %%{shop[0]}', '%s %%{shop}': '%s %%{shop}', +'%Y-%m-%d': '%Y-%m-%d', '%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', '@markmin\x01**Hello World**': '**Hello World**', +'About': 'About', +'Access Control': 'Access Control', +'Administrative Interface': 'Administrative Interface', +'Ajax Recipes': 'Ajax Recipes', +'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?', +'Buy this book': 'Buy this book', +'Cannot be empty': 'Cannot be empty', +'Client IP': 'Client IP', +'Community': 'Community', +'Components and Plugins': 'Components and Plugins', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Created By': 'Created By', +'Created On': 'Created On', +'customize me!': 'customize me!', +'Database': 'Database', +'DB Model': 'DB Model', +'Demo': 'Demo', +'Deployment Recipes': 'Deployment Recipes', +'Description': 'Description', +'Documentation': 'Documentation', +"Don't know what to do?": "Don't know what to do?", +'Download': 'Download', +'E-mail': 'E-mail', +'Email and SMS': 'Email and SMS', 'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g', 'enter date and time as %(format)s': 'enter date and time as %(format)s', +'Errors': 'Errors', +'FAQ': 'FAQ', +'First name': 'First name', +'Forms and Validators': 'Forms and Validators', +'Free Applications': 'Free Applications', +'Group ID': 'Group ID', +'Groups': 'Groups', 'Hello World': 'Hello World', 'Hello World ## comment': 'Hello World ', 'Hello World## comment': 'Hello World', +'Home': 'Home', +'How did you get here?': 'How did you get here?', +'Introduction': 'Introduction', +'Invalid email': 'Invalid email', +'Is Active': 'Is Active', +'Last name': 'Last name', +'Layout': 'Layout', +'Layout Plugins': 'Layout Plugins', +'Layouts': 'Layouts', +'Live Chat': 'Live Chat', +'Login': 'Login', +'Lost password?': 'Lost password?', +'Menu Model': 'Menu Model', +'Modified By': 'Modified By', +'Modified On': 'Modified On', +'My Sites': 'My Sites', +'Name': 'Name', +'Object or table name': 'Object or table name', +'Online examples': 'Online examples', +'Origin': 'Origin', +'Other Plugins': 'Other Plugins', +'Other Recipes': 'Other Recipes', +'Overview': 'Overview', +'Password': 'Password', +'Plugins': 'Plugins', +'Powered by': 'Powered by', +'Preface': 'Preface', +'Python': 'Python', +'Quick Examples': 'Quick Examples', +'Recipes': 'Recipes', +'Record ID': 'Record ID', +'Register': 'Register', +'Registration identifier': 'Registration identifier', +'Registration key': 'Registration key', +'Reset Password key': 'Reset Password key', +'Role': 'Role', +'Semantic': 'Semantic', +'Services': 'Services', +'Stylesheet': 'Stylesheet', +'Support': 'Support', +'The Core': 'The Core', +'The output of the file is a dictionary that was rendered by the view %s': 'The output of the file is a dictionary that was rendered by the view %s', +'The Views': 'The Views', +'This App': 'This App', +'Timestamp': 'Timestamp', +'Twitter': 'Twitter', +'User ID': 'User ID', +'Videos': 'Videos', +'View': 'View', +'Welcome': 'Welcome', +'Welcome to web2py!': 'Welcome to web2py!', +'Which called the function %s located in the file %s': 'Which called the function %s located in the file %s', +'You are successfully running web2py': 'You are successfully running web2py', +'You can modify this application and adapt it to your needs': 'You can modify this application and adapt it to your needs', +'You visited the url %s': 'You visited the url %s', } diff --git a/gluon/scheduler.py b/gluon/scheduler.py index 9e585d39..07e4a28f 100644 --- a/gluon/scheduler.py +++ b/gluon/scheduler.py @@ -262,7 +262,8 @@ class MetaScheduler(threading.Thread): start = time.time() - while p.is_alive() and (time.time()-start < task.timeout): + while p.is_alive() and ( + not task.timeout or time.time()-start < task.timeout): if tout: try: logging.debug(' partial output saved') From a8580673b60b4467e844b25608d7e59aaa171876 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 08:09:33 -0500 Subject: [PATCH 21/68] update link fixed, thanks Niphlod --- VERSION | 2 +- applications/admin/controllers/default.py | 4 ++-- applications/welcome/languages/default.py | 17 +++++++++++++++++ gluon/contrib/webclient.py | 1 + gluon/tests/test_web.py | 5 +++++ 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index c330710c..5046c345 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 08:05:05) stable +Version 2.0.2 (2012-08-30 08:09:29) stable diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index ccc36121..523af721 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -140,8 +140,8 @@ def check_version(): return SPAN('You should upgrade to version %s' % version_number) else: return sp_button(URL('upgrade_web2py'), T('upgrade now')) \ - + XML(' %s' % version_number) - + + XML(' %s.%s.%s' \ + % version_number[:3]) def logout(): """ Logout handler """ diff --git a/applications/welcome/languages/default.py b/applications/welcome/languages/default.py index 4586b206..31757f18 100644 --- a/applications/welcome/languages/default.py +++ b/applications/welcome/languages/default.py @@ -16,6 +16,7 @@ 'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?', 'Buy this book': 'Buy this book', 'Cannot be empty': 'Cannot be empty', +'Check to delete': 'Check to delete', 'Client IP': 'Client IP', 'Community': 'Community', 'Components and Plugins': 'Components and Plugins', @@ -41,7 +42,9 @@ 'First name': 'First name', 'Forms and Validators': 'Forms and Validators', 'Free Applications': 'Free Applications', +'Group %(group_id)s created': 'Group %(group_id)s created', 'Group ID': 'Group ID', +'Group uniquely assigned to user %(id)s': 'Group uniquely assigned to user %(id)s', 'Groups': 'Groups', 'Hello World': 'Hello World', 'Hello World ## comment': 'Hello World ', @@ -56,7 +59,11 @@ 'Layout Plugins': 'Layout Plugins', 'Layouts': 'Layouts', 'Live Chat': 'Live Chat', +'Logged in': 'Logged in', +'Logged out': 'Logged out', 'Login': 'Login', +'Logout': 'Logout', +'Lost Password': 'Lost Password', 'Lost password?': 'Lost password?', 'Menu Model': 'Menu Model', 'Modified By': 'Modified By', @@ -70,9 +77,12 @@ 'Other Recipes': 'Other Recipes', 'Overview': 'Overview', 'Password': 'Password', +"Password fields don't match": "Password fields don't match", +'please input your password again': 'please input your password again', 'Plugins': 'Plugins', 'Powered by': 'Powered by', 'Preface': 'Preface', +'Profile': 'Profile', 'Python': 'Python', 'Quick Examples': 'Quick Examples', 'Recipes': 'Recipes', @@ -80,6 +90,8 @@ 'Register': 'Register', 'Registration identifier': 'Registration identifier', 'Registration key': 'Registration key', +'Registration successful': 'Registration successful', +'Remember me (for 30 days)': 'Remember me (for 30 days)', 'Reset Password key': 'Reset Password key', 'Role': 'Role', 'Semantic': 'Semantic', @@ -92,7 +104,12 @@ 'This App': 'This App', 'Timestamp': 'Timestamp', 'Twitter': 'Twitter', +'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': 'User ID', +'value already in database or empty': 'value already in database or empty', +'Verify Password': 'Verify Password', 'Videos': 'Videos', 'View': 'View', 'Welcome': 'Welcome', diff --git a/gluon/contrib/webclient.py b/gluon/contrib/webclient.py index 4bcfe7c8..b4ab242b 100644 --- a/gluon/contrib/webclient.py +++ b/gluon/contrib/webclient.py @@ -39,6 +39,7 @@ class WebClient(object): session_regex = SESSION_REGEX): self.app = app self.postbacks = postbacks + self.forms = {} self.history = [] self.cookies = {} self.default_headers = default_headers diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py index 9d5e1448..54295c88 100644 --- a/gluon/tests/test_web.py +++ b/gluon/tests/test_web.py @@ -41,6 +41,11 @@ class TestWeb(unittest.TestCase): client.get('index') self.assertTrue('Welcome Homer' in client.text) + client = WebClient('http://127.0.0.1:8000/admin/default/') + client.post('index',data=dict(password='hello')) + client.get('site') + client.get('design/welcome') + if __name__ == '__main__': unittest.main() From 528cf0ed0f94ea516991b1f86a415f021c840213 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 08:14:10 -0500 Subject: [PATCH 22/68] fixed grin without login, thanks Liam --- VERSION | 2 +- gluon/sqlhtml.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 5046c345..797c616a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 08:09:29) stable +Version 2.0.2 (2012-08-30 08:14:07) stable diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index 32979bcc..145fe029 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -1656,7 +1656,7 @@ class SQLFORM(FORM): if user_signature: if (args != request.args and user_signature and \ not URL.verify(request,user_signature=user_signature)) or \ - (not session.auth.user and \ + (not (session.auth and session.auth.user) and \ ('edit' in request.args or \ 'create' in request.args or \ 'delete' in request.args)): From 3c0ed7b8b99a13908b76ff332a9cd6d5eb28df60 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 08:17:28 -0500 Subject: [PATCH 23/68] fixed bug in markmin --- VERSION | 2 +- gluon/contrib/markmin/markmin2html.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 797c616a..ab5528da 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 08:14:07) stable +Version 2.0.2 (2012-08-30 08:17:25) stable diff --git a/gluon/contrib/markmin/markmin2html.py b/gluon/contrib/markmin/markmin2html.py index 8e6431bc..e0b955e2 100755 --- a/gluon/contrib/markmin/markmin2html.py +++ b/gluon/contrib/markmin/markmin2html.py @@ -1230,9 +1230,9 @@ def render(text, if code[:1]=='\n': code=code[1:] if code[-1:]=='\n': code=code[:-1] if p: - return extra[b](code,p) + return str(extra[b](code,p)) else: - return extra[b](code) + return str(extra[b](code)) elif b=='cite': return '['+','.join('%s' \ % (d,b,d) \ From 1444c5b476cc6b377d6cf375e247703a04acbd6a Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 08:31:52 -0500 Subject: [PATCH 24/68] fixed janrain login with GAE --- VERSION | 2 +- gluon/tools.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index ab5528da..e1c65f4e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 08:17:25) stable +Version 2.0.2 (2012-08-30 08:31:49) stable diff --git a/gluon/tools.py b/gluon/tools.py index 58ab9617..69894bbf 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -1612,12 +1612,12 @@ class Auth(object): checks = [] # make a guess about who this user is for fieldname in ['registration_id','username','email']: - if fieldname in table_user.fields() and keys.get(fieldname,None): + if fieldname in table_user.fields() and \ + keys.get(fieldname,None): checks.append(fieldname) value = keys[fieldname] - user = user or table_user._db( - (table_user.registration_id==value)| - (table_user[fieldname]==value)).select().first() + user = table_user(**{fieldname:value}) + if user: break if not checks: return None if not 'registration_id' in keys: From 9f763c677ce05bd834af34230e236027ec703f24 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 14:42:32 -0500 Subject: [PATCH 25/68] fixed AttributeError: 'Expression' object has no attribute '_table' issue --- VERSION | 2 +- gluon/dal.py | 1 + gluon/tests/test_dal.py | 8 +++++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index e1c65f4e..8772e387 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 08:31:49) stable +Version 2.0.2 (2012-08-30 14:42:29) stable diff --git a/gluon/dal.py b/gluon/dal.py index 22eed09b..db95fa62 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -7935,6 +7935,7 @@ class Expression(object): self.op = op self.first = first self.second = second + self._table = getattr(first,'_table',None) ### self._tablename = first._tablename ## CHECK if not type and first and hasattr(first,'type'): self.type = first.type diff --git a/gluon/tests/test_dal.py b/gluon/tests/test_dal.py index 419d78d2..70cb09c1 100644 --- a/gluon/tests/test_dal.py +++ b/gluon/tests/test_dal.py @@ -411,12 +411,14 @@ class TestMinMaxSum(unittest.TestCase): self.assertEqual(db.t.insert(a=3), 3) s = db.t.a.min() self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 1) + self.assertEqual(db(db.t.id > 0).select(s).first()[s], 1) + self.assertEqual(db().select(s).first()[s], 1) s = db.t.a.max() - self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 3) + self.assertEqual(db().select(s).first()[s], 3) s = db.t.a.sum() - self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 6) + self.assertEqual(db().select(s).first()[s], 6) s = db.t.a.count() - self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 3) + self.assertEqual(db().select(s).first()[s], 3) db.t.drop() From 25f349a3a9afb01c35a1ee26e0b8ba7ee1b1a0ac Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 14:49:50 -0500 Subject: [PATCH 26/68] logging rotation in example, thanks Jonathan --- VERSION | 2 +- logging.example.conf | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/VERSION b/VERSION index 8772e387..0c636d4b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 14:42:29) stable +Version 2.0.2 (2012-08-30 14:49:47) stable diff --git a/logging.example.conf b/logging.example.conf index ac365aa1..892e242d 100644 --- a/logging.example.conf +++ b/logging.example.conf @@ -27,12 +27,14 @@ # set by the setLevel call, the [logger_myapp] section, and the [handler_...] # section. For example, you will not see DEBUG messages unless all three are # set to DEBUG. +# +# Available levels: DEBUG INFO WARNING ERROR CRITICAL [loggers] keys=root,rocket,markdown,web2py,rewrite,cron,app,welcome [handlers] -keys=consoleHandler,messageBoxHandler +keys=consoleHandler,messageBoxHandler,rotatingFileHandler #keys=consoleHandler,rotatingFileHandler #keys=osxSysLogHandler @@ -41,50 +43,53 @@ keys=simpleFormatter [logger_root] level=WARNING -handlers=consoleHandler +handlers=consoleHandler,rotatingFileHandler [logger_web2py] level=WARNING -handlers=consoleHandler +handlers=consoleHandler,rotatingFileHandler qualname=web2py propagate=0 +# URL rewrite logging (routes.py) +# See also the logging parameter in routes.py +# [logger_rewrite] level=WARNING qualname=web2py.rewrite -handlers=consoleHandler +handlers=consoleHandler,rotatingFileHandler propagate=0 [logger_cron] level=WARNING qualname=web2py.cron -handlers=consoleHandler +handlers=consoleHandler,rotatingFileHandler propagate=0 # generic app handler [logger_app] level=WARNING qualname=web2py.app -handlers=consoleHandler +handlers=consoleHandler,rotatingFileHandler propagate=0 # welcome app handler [logger_welcome] level=WARNING -qualname=web2py.app.welcome +qualname=web2py.app.welcome,rotatingFileHandler handlers=consoleHandler propagate=0 # loggers for legacy getLogger calls: Rocket and markdown [logger_rocket] level=WARNING -handlers=consoleHandler,messageBoxHandler +handlers=consoleHandler,messageBoxHandler,rotatingFileHandler qualname=Rocket propagate=0 [logger_markdown] level=WARNING -handlers=consoleHandler +handlers=consoleHandler,rotatingFileHandler qualname=markdown propagate=0 @@ -106,7 +111,7 @@ args=() # [handler_rotatingFileHandler] class=handlers.RotatingFileHandler -level=INFO +level=DEBUG formatter=simpleFormatter args=("logs/web2py.log", "a", 1000000, 5) From ec92b8fff19c2573fa04bcbd33801ed7b62f5a0a Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 14:54:40 -0500 Subject: [PATCH 27/68] fixed path find for pluralization rules --- VERSION | 2 +- applications/welcome/languages/default.py | 1 + applications/welcome/languages/plural-en.py | 1 + gluon/languages.py | 6 ++++-- gluon/tests/test_languages.py | 4 ++++ 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 0c636d4b..267b104b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 14:49:47) stable +Version 2.0.2 (2012-08-30 14:54:37) stable diff --git a/applications/welcome/languages/default.py b/applications/welcome/languages/default.py index 31757f18..5e6e8125 100644 --- a/applications/welcome/languages/default.py +++ b/applications/welcome/languages/default.py @@ -4,6 +4,7 @@ '!langname!': 'English (US)', '%s %%(shop)': '%s %%(shop)', '%s %%(shop[0])': '%s %%(shop[0])', +'%s %%{quark[0]}': '%s %%{quark[0]}', '%s %%{shop[0]}': '%s %%{shop[0]}', '%s %%{shop}': '%s %%{shop}', '%Y-%m-%d': '%Y-%m-%d', diff --git a/applications/welcome/languages/plural-en.py b/applications/welcome/languages/plural-en.py index d0ea585e..562d96f1 100644 --- a/applications/welcome/languages/plural-en.py +++ b/applications/welcome/languages/plural-en.py @@ -6,6 +6,7 @@ 'is': ['are'], 'man': ['men'], 'person': ['people'], +'quark': ['quarks'], 'shop': ['shops'], 'this': ['these'], 'was': ['were'], diff --git a/gluon/languages.py b/gluon/languages.py index c0a13df0..1cf40780 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -18,7 +18,7 @@ import portalocker import logging import marshal import copy_reg -from fileutils import abspath, listdir +from fileutils import listdir import settings from cfs import getcfs from thread import allocate_lock @@ -31,6 +31,8 @@ __all__ = ['translator', 'findT', 'update_all_languages'] ospath = os.path ostat = os.stat osep = os.sep +pjoin = os.path.join +pdirname = os.path.dirname isdir = os.path.isdir is_gae = settings.global_settings.web2py_runtime_gae @@ -244,7 +246,7 @@ def read_possible_plurals(): create list of all possible plural rules files result is cached to increase speed """ - pdir = abspath('gluon','contrib','rules') + pdir = pjoin(pdirname(__file__),'contrib','rules') plurals = {} # scan rules directory for plural_rules-*.py files: for pname in os.listdir(pdir): diff --git a/gluon/tests/test_languages.py b/gluon/tests/test_languages.py index 070fb874..635eeea2 100644 --- a/gluon/tests/test_languages.py +++ b/gluon/tests/test_languages.py @@ -81,6 +81,10 @@ try: '1 shop') self.assertEqual(str(T('%s %%{shop[0]}', 2)), '2 shops') + self.assertEqual(str(T('%s %%{quark[0]}', 1)), + '1 quark') + self.assertEqual(str(T('%s %%{quark[0]}', 2)), + '2 quarks') self.assertEqual(str(T.M('**Hello World**')), 'Hello World') T.force('it') From 10555af3e608373a85d6c05df0c6cba1cec0b352 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 15:01:22 -0500 Subject: [PATCH 28/68] fixing missing use_username issue, thanks Annet --- VERSION | 2 +- gluon/tools.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 267b104b..adca1046 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 14:54:37) stable +Version 2.0.2 (2012-08-30 15:01:19) stable diff --git a/gluon/tools.py b/gluon/tools.py index 69894bbf..ecbc37e2 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -1060,6 +1060,7 @@ 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: @@ -1257,6 +1258,9 @@ class Auth(object): 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: bar.insert(-1, s2) From 910f4c0f1379135c08fe943b1a5020d28463c0a5 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 15:10:45 -0500 Subject: [PATCH 29/68] new default firebird driver, thanks mariuz --- VERSION | 2 +- gluon/dal.py | 29 +++++++++++++++++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/VERSION b/VERSION index adca1046..320f8d40 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 15:01:19) stable +Version 2.0.2 (2012-08-30 15:10:39) stable diff --git a/gluon/dal.py b/gluon/dal.py index db95fa62..0ede6db1 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -20,6 +20,7 @@ including: - SQLite & SpatiaLite - MySQL - Postgres +- Firebird - Oracle - MS SQL - DB2 @@ -312,6 +313,12 @@ if not 'google' in drivers: drivers.append('Sybase') except ImportError: logger.debug('no Sybase driver') + + try: + import fdb + drivers.append('Firebird') + except ImportError: + logger.debug('no Firebird driver') try: import kinterbasdb @@ -3191,12 +3198,14 @@ class FireBirdAdapter(BaseAdapter): credential_decoder=IDENTITY, driver_args={}, adapter_args={}): if 'driver_name' in adapter_args: - if adapter_args['driver_name'] == 'kinterbasdb': - self.driver = kinterbasdb + if adapter_args['driver_name'] == 'fdb': + self.driver = fdb elif adapter_args['driver_name'] == 'firebirdsql': self.driver = firebirdsql + elif adapter_args['driver_name'] == 'kinterbasdb': + self.driver = kinterbasdb else: - self.driver = kinterbasdb + self.driver = fdb if not self.driver: raise RuntimeError, "Unable to import driver" @@ -3257,13 +3266,15 @@ class FireBirdEmbeddedAdapter(FireBirdAdapter): adapter_args={}): if 'driver_name' in adapter_args: - if adapter_args['driver_name'] == 'kinterbasdb': - self.driver = kinterbasdb + if adapter_args['driver_name'] == 'fdb': + self.driver = fdb elif adapter_args['driver_name'] == 'firebirdsql': self.driver = firebirdsql + elif adapter_args['driver_name'] == 'kinterbasdb': + self.driver = kinterbasdb else: - self.driver = kinterbasdb - + self.driver = fdb + if not self.driver: raise RuntimeError, "Unable to import driver" self.db = db @@ -3296,9 +3307,7 @@ class FireBirdEmbeddedAdapter(FireBirdAdapter): user=credential_decoder(user), password=credential_decoder(password), charset=charset) - #def connect(driver_args=driver_args): - # return kinterbasdb.connect(**driver_args) - + def connect(driver_args=driver_args): return self.driver.connect(**driver_args) self.pool_connection(connect) From 53889ece3d39434f8555b0c016ea4cb8eb9ce2b6 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 15:20:36 -0500 Subject: [PATCH 30/68] fixed bug in pluralization, thanks Vladyslav --- VERSION | 2 +- gluon/languages.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 320f8d40..56bbcbad 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 15:10:39) stable +Version 2.0.2 (2012-08-30 15:20:33) stable diff --git a/gluon/languages.py b/gluon/languages.py index 1cf40780..d8ef8735 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -507,7 +507,7 @@ class translator(object): if int(n)==1: return word elif word: - id = min(int(n)-1,1) # self.get_plural_id(abs(int(n))) + id = self.get_plural_id(abs(int(n))) # id = 0 first plural form # id = 1 second plural form # etc. From 21cf4f9024206d43840e4557c80ff78b6934b3ad Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 15:26:58 -0500 Subject: [PATCH 31/68] cacheable select in grid, thanks Anthony --- VERSION | 2 +- gluon/sqlhtml.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/VERSION b/VERSION index 56bbcbad..1abebcec 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 15:20:33) stable +Version 2.0.2 (2012-08-30 15:26:54) stable diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index 145fe029..b93b5b6e 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -1826,14 +1826,15 @@ class SQLFORM(FORM): try: dbset = dbset(SQLFORM.build_query( fields,request.vars.get('keywords',''))) - rows = dbset.select() + rows = dbset.select(cacheable=True) except Exception, e: response.flash = T('Internal Error') rows = [] else: - rows = dbset.select() + rows = dbset.select(cacheable=True) else: - rows = dbset.select(left=left,orderby=orderby,*columns) + rows = dbset.select(left=left,orderby=orderby, + cacheable=True*columns) if export_type in exportManager: value = exportManager[export_type] @@ -1892,7 +1893,8 @@ class SQLFORM(FORM): try: if left or groupby: c = 'count(*)' - nrows = dbset.select(c,left=left,groupby=groupby).first()[c] + nrows = dbset.select(c,left=left,cacheable=True, + groupby=groupby).first()[c] else: nrows = dbset.count() except: @@ -1976,7 +1978,9 @@ class SQLFORM(FORM): try: table_fields = [f for f in fields if f._tablename in tablenames] - rows = dbset.select(left=left,orderby=orderby,groupby=groupby,limitby=limitby,*table_fields) + rows = dbset.select(left=left,orderby=orderby, + groupby=groupby,limitby=limitby, + cacheable=True,*table_fields) except SyntaxError: rows = None error = T("Query Not Supported") From 4ee59cfaafb7cf11f570d32488749a2b881df063 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 15:36:36 -0500 Subject: [PATCH 32/68] fixed bug in smartgrid header, thanks Adi --- VERSION | 2 +- gluon/sqlhtml.py | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/VERSION b/VERSION index 1abebcec..2a46c438 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 15:26:54) stable +Version 2.0.2 (2012-08-30 15:36:33) stable diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index b93b5b6e..3861262d 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -2190,12 +2190,14 @@ class SQLFORM(FORM): LI(A(T(db[referee]._plural), _class=trap_class(), _href=url()), - SPAN(divider,_class='divider'),_class='w2p_grid_breadcrumb_elem')) + SPAN(divider,_class='divider'), + _class='w2p_grid_breadcrumb_elem')) if kwargs.get('details',True): breadcrumbs.append( LI(A(name,_class=trap_class(), _href=url(args=['view',referee,id])), - SPAN(divider,_class='divider'),_class='w2p_grid_breadcrumb_elem')) + SPAN(divider,_class='divider'), + _class='w2p_grid_breadcrumb_elem')) nargs+=2 else: break @@ -2221,16 +2223,18 @@ class SQLFORM(FORM): del kwargs[key] check = {} id_field_name = table._id.name - for field in table._referenced_by: - if field.readable: - check[field.tablename] = check.get(field.tablename,[])+[field.name] + for rfield in table._referenced_by: + if rfield.readable: + check[rfield.tablename] = \ + check.get(rfield.tablename,[])+[rfield.name] for tablename in sorted(check): linked_fieldnames = check[tablename] tb = db[tablename] multiple_links = len(linked_fieldnames)>1 for fieldname in linked_fieldnames: if linked_tables is None or tablename in linked_tables: - t = T(tb._plural) if not multiple_links else T(tb._plural+'('+fieldname+')') + t = T(tb._plural) if not multiple_links else \ + T(tb._plural+'('+fieldname+')') args0 = tablename+'.'+fieldname links.append( lambda row,t=t,nargs=nargs,args0=args0:\ From b3be11953d692bd7b2974fde0175ec1e78e019ef Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 15:38:41 -0500 Subject: [PATCH 33/68] R-2.0.3 --- Makefile | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7e7c9920..71419c7c 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ update: wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py echo "remember that pymysql was tweaked" src: - echo 'Version 2.0.2 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION + echo 'Version 2.0.3 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION ### rm -f all junk files make clean ### clean up baisc apps diff --git a/VERSION b/VERSION index 2a46c438..fb1d7594 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.2 (2012-08-30 15:36:33) stable +Version 2.0.3 (2012-08-30 15:38:37) stable From c844759c6330f56d46bc52eb43a6e5412b18eb2f Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 17:04:07 -0500 Subject: [PATCH 34/68] fixed issue 964, thanks Michael and Jonathan --- VERSION | 2 +- gluon/main.py | 14 +++++++++----- gluon/rewrite.py | 10 +++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/VERSION b/VERSION index fb1d7594..7b238d89 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-30 15:38:37) stable +Version 2.0.3 (2012-08-30 17:04:01) stable diff --git a/gluon/main.py b/gluon/main.py index d5e6d25c..94610f41 100644 --- a/gluon/main.py +++ b/gluon/main.py @@ -30,7 +30,7 @@ import string import urllib2 from thread import allocate_lock -from fileutils import abspath, write_file, parse_version +from fileutils import abspath, write_file, parse_version, copystream from settings import global_settings from admin import add_path_first, create_missing_folders, create_missing_app_folders from globals import current @@ -84,7 +84,6 @@ from http import HTTP, redirect from globals import Request, Response, Session from compileapp import build_environment, run_models_in, \ run_controller_in, run_view_in -from fileutils import copystream, parse_version from contenttype import contenttype from dal import BaseAdapter from settings import global_settings @@ -384,7 +383,7 @@ def wsgibase(environ, responder): # ################################################## eget = environ.get - if not eget('PATH_INFO',None) and eget('REQUEST_URI',None): + 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('?') @@ -393,9 +392,14 @@ def wsgibase(environ, responder): environ['QUERY_STRING'] = items[1] else: environ['QUERY_STRING'] = '' - if not eget('HTTP_HOST',None): + 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') + eget('SERVER_NAME') + ':' + eget('SERVER_PORT') (static_file, environ) = url_in(request, environ) diff --git a/gluon/rewrite.py b/gluon/rewrite.py index 731cee67..8d124670 100644 --- a/gluon/rewrite.py +++ b/gluon/rewrite.py @@ -994,6 +994,7 @@ class MapUrlIn(object): static_file = pjoin(self.request.env.applications_parent, 'applications', self.application, 'static', file) + self.extension = None log_rewrite("route: static=%s" % static_file) return static_file @@ -1053,7 +1054,7 @@ class MapUrlIn(object): if self.map_hyphen: uri = uri.replace('_', '-') app = app.replace('_', '-') - if self.extension != 'html': + if self.extension and self.extension != 'html': uri += '.' + self.extension if self.language: uri = '/%s%s' % (self.language, uri) @@ -1271,19 +1272,14 @@ def map_url_in(request, env, app=False): # handle mapping of lang/static to static/lang in externally-rewritten URLs # in case we have to handle them ourselves if map.languages and map.map_static is False and map.arg0 == 'static' and map.args(1) in map.languages: - if 'es' in map.languages: - print 'handle static/lang %s' % map.args(1) map.map_controller() map.map_language() else: - if 'es' in map.languages: - print 'NO handle static/lang %s' % map.args(1) map.map_language() map.map_controller() static_file = map.map_static() - if 'es' in map.languages: - print 'static_file=%s' % static_file if static_file: + map.update_request() return (static_file, map.env) map.map_function() map.validate_args() From 1cf2f84d2477fe714babd7e7e0324a30b6873e88 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 17:36:19 -0500 Subject: [PATCH 35/68] added empty applications/examples/languages/README else folder not version controller in git and hg --- VERSION | 2 +- applications/examples/languages/README | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 applications/examples/languages/README diff --git a/VERSION b/VERSION index 7b238d89..bfe480c7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-30 17:04:01) stable +Version 2.0.3 (2012-08-30 17:36:15) stable diff --git a/applications/examples/languages/README b/applications/examples/languages/README new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/applications/examples/languages/README @@ -0,0 +1 @@ + From 79e7d974ad1623311cd8ece10e5c708df23527b4 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 20:07:34 -0500 Subject: [PATCH 36/68] Rows.find(f,limitby=(0,10)) --- VERSION | 2 +- gluon/dal.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/VERSION b/VERSION index bfe480c7..b9c7b62f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-30 17:36:15) stable +Version 2.0.3 (2012-08-30 20:07:31) stable diff --git a/gluon/dal.py b/gluon/dal.py index 0ede6db1..b053af81 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -8946,18 +8946,24 @@ class Rows(object): return None return self[-1] - def find(self,f): + def find(self,f,limitby=None): """ returns a new Rows object, a subset of the original object, filtered by the function f """ - if not self.records: + if not self: return Rows(self.db, [], self.colnames) records = [] - for i in range(0,len(self)): - row = self[i] + if limitby: + a,b = limitby + else: + a,b = 0,len(self) + k = 0 + for row in self: if f(row): - records.append(self.records[i]) + if a<=k: records.append(row) + k += 1 + if k==b: break return Rows(self.db, records, self.colnames) def exclude(self, f): From 017fd0dd14a5040f0819be4577639bc79dd14cd2 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 20:19:01 -0500 Subject: [PATCH 37/68] fixed typo in index_name --- VERSION | 2 +- gluon/dal.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index b9c7b62f..f61853e6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-30 20:07:31) stable +Version 2.0.3 (2012-08-30 20:18:58) stable diff --git a/gluon/dal.py b/gluon/dal.py index b053af81..6e01073f 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -2205,7 +2205,7 @@ class MySQLAdapter(BaseAdapter): 'list:string': 'LONGTEXT', 'list:reference': 'LONGTEXT', 'big-id': 'BIGINT AUTO_INCREMENT NOT NULL', - 'big-reference': 'BIGINT, INDEX %(index_name`)s (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', + 'big-reference': 'BIGINT, INDEX %(index_name)s (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', } def varquote(self,name): From efbabbafa3b6edf8a9bbe7dbd0199ced06f86001 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 20:24:56 -0500 Subject: [PATCH 38/68] fixed 'fdb' is not defined issue, thanks villas --- VERSION | 2 +- gluon/dal.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index f61853e6..9c0e3b1d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-30 20:18:58) stable +Version 2.0.3 (2012-08-30 20:24:53) stable diff --git a/gluon/dal.py b/gluon/dal.py index 6e01073f..73559108 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -3196,7 +3196,7 @@ class FireBirdAdapter(BaseAdapter): def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, - adapter_args={}): + adapter_args={}): if 'driver_name' in adapter_args: if adapter_args['driver_name'] == 'fdb': self.driver = fdb @@ -3204,8 +3204,12 @@ class FireBirdAdapter(BaseAdapter): self.driver = firebirdsql elif adapter_args['driver_name'] == 'kinterbasdb': self.driver = kinterbasdb - else: + elif 'fdb' in globals(): self.driver = fdb + elif 'kinterbasdb' in globals(): + self.driver = kinterbasdb + else: + raise RuntimeError, "no fdb and no kinterbasdb driver found" if not self.driver: raise RuntimeError, "Unable to import driver" From 1554a831ac428bd94527663b0adb0f9dc52791bb Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 22:10:46 -0500 Subject: [PATCH 39/68] improved logic in dal driver selection --- VERSION | 2 +- gluon/dal.py | 312 +++++++++++++++++++++++++-------------------------- 2 files changed, 152 insertions(+), 162 deletions(-) diff --git a/VERSION b/VERSION index 9c0e3b1d..3d26aae4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-30 20:24:53) stable +Version 2.0.3 (2012-08-30 22:10:41) stable diff --git a/gluon/dal.py b/gluon/dal.py index 73559108..8027a6b2 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -239,7 +239,7 @@ regex_select_as_parser = re.compile("\s+AS\s+(\S+)") # list of drivers will be built on the fly # and lists only what is available -drivers = [] +DRIVERS = [] try: from new import classobj @@ -247,22 +247,23 @@ try: from google.appengine.api import namespace_manager, rdbms from google.appengine.api.datastore_types import Key ### for belongs on ID from google.appengine.ext.db.polymodel import PolyModel - drivers.append('google') + DRIVERS.append('google') except ImportError: pass -if not 'google' in drivers: +if not 'google' in DRIVERS: try: - # first try pysqlite2 else try sqlite3 - try: - from pysqlite2 import dbapi2 as sqlite3 - drivers.append('pysqlite2') - except ImportError: - from sqlite3 import dbapi2 as sqlite3 - drivers.append('SQLite3') + from pysqlite2 import dbapi2 as sqlite2 + DRIVERS.append('SQLite(sqlite2)') except ImportError: - logger.debug('no sqlite3 or pysqlite2.dbapi2 driver') + logger.debug('no SQLite drivers pysqlite2.dbapi2') + + try: + from sqlite3 import dbapi2 as sqlite3 + DRIVERS.append('SQLite(sqlite3)') + except ImportError: + logger.debug('no SQLite drivers sqlite3') try: # first try contrib driver, then from site-packages (if installed) @@ -275,16 +276,23 @@ if not 'google' in drivers: # end monkeypatch except ImportError: import pymysql - drivers.append('pymysql') + DRIVERS.append('MySQL(pymysql)') except ImportError: - logger.debug('no pymysql driver') + logger.debug('no MySQL driver pymysql') + + try: + import MySQLdb + DRIVERS.append('MySQL(MySQLdb)') + except ImportError: + logger.debug('no MySQL driver MySQLDB') + try: import psycopg2 from psycopg2.extensions import adapt as psycopg2_adapt - drivers.append('psycopg2') + DRIVERS.append('PostgreSQL(psycopg2)') except ImportError: - logger.debug('no psycopg2 driver') + logger.debug('no PostgreSQL driver psycopg2') try: # first try contrib driver, then from site-packages (if installed) @@ -292,103 +300,108 @@ if not 'google' in drivers: import contrib.pg8000.dbapi as pg8000 except ImportError: import pg8000.dbapi as pg8000 - drivers.append('pg8000') + DRIVERS.append('PostgreSQL(pg8000)') except ImportError: - logger.debug('no pg8000 driver') + logger.debug('no PostgreSQL driver pg8000') try: import cx_Oracle - drivers.append('Oracle') + DRIVERS.append('Oracle(cx_Oracle)') except ImportError: - logger.debug('no cx_Oracle driver') + logger.debug('no Oracle driver cx_Oracle') try: import pyodbc - drivers.append('MSSQL/DB2/Teradata') + DRIVERS.append('MSSQL(pyodbc)') + DRIVERS.append('DB2(pyodbc)') + DRIVERS.append('Teradata(pyodbc)') except ImportError: - logger.debug('no MSSQL/DB2/Teradata driver') + logger.debug('no MSSQL/DB2/Teradata driver pyodbc') try: import Sybase - drivers.append('Sybase') + DRIVERS.append('Sybase(Sybase)') except ImportError: logger.debug('no Sybase driver') try: - import fdb - drivers.append('Firebird') + import kinterbasdb + DRIVERS.append('Interbase(kinterbasdb)') + DRIVERS.append('Firebird(kinterbasdb)') except ImportError: - logger.debug('no Firebird driver') + logger.debug('no Firebird/Interbase driver kinterbasdb') try: - import kinterbasdb - drivers.append('Interbase') + import fdb + DRIVERS.append('Firbird(fdb)') except ImportError: - logger.debug('no kinterbasdb driver') - + logger.debug('no Firebird driver fdb') +##### try: import firebirdsql - drivers.append('Firebird') + DRIVERS.append('Firebird(firebirdsql)') except ImportError: - logger.debug('no Firebird driver') + logger.debug('no Firebird driver firebirdsql') try: import informixdb - drivers.append('Informix') + DRIVERS.append('Informix(informixdb)') logger.warning('Informix support is experimental') except ImportError: - logger.debug('no informixdb driver') + logger.debug('no Informix driver informixdb') try: import sapdb - drivers.append('SAPDB') + DRIVERS.append('SQL(sapdb)') logger.warning('SAPDB support is experimental') except ImportError: - logger.debug('no sapdb driver') + logger.debug('no SAP driver sapdb') try: import cubriddb - drivers.append('Cubrid') + DRIVERS.append('Cubrid(cubriddb)') logger.warning('Cubrid support is experimental') except ImportError: - logger.debug('no cubriddb driver') + logger.debug('no Cubrid driver cubriddb') try: from com.ziclix.python.sql import zxJDBC import java.sql # Try sqlite jdbc driver from http://www.zentus.com/sqlitejdbc/ from org.sqlite import JDBC # required by java.sql; ensure we have it - drivers.append('zxJDBC') + zxJDBC_sqlite = java.sql.DriverManager + DRIVERS.append('PostgreSQL(zxJDBC)') + DRIVERS.append('SQLite(zxJDBC)') logger.warning('zxJDBC support is experimental') is_jdbc = True except ImportError: - logger.debug('no zxJDBC driver') + logger.debug('no SQLite/PostgreSQL driver zxJDBC') is_jdbc = False try: import ingresdbi - drivers.append('Ingres') + DRIVERS.append('Ingres(ingresdbi)') except ImportError: - logger.debug('no Ingres driver') + logger.debug('no Ingres driver ingresdbi') # NOTE could try JDBC....... try: import couchdb - drivers.append('CouchDB') + DRIVERS.append('CouchDB(couchdb)') except ImportError: - logger.debug('no couchdb driver') + logger.debug('no Couchdb driver couchdb') try: import pymongo - drivers.append('mongoDB') + DRIVERS.append('MongoDB(pymongo)') except: - logger.debug('no mongoDB driver') + logger.debug('no MongoDB driver pymongo') try: import imaplib - drivers.append('IMAP') + DRIVERS.append('IMAP(imaplib)') except: - logger.debug('could not import imaplib') + logger.debug('no IMAP driver imaplib') PLURALIZE_RULES = [ (re.compile('child$'), re.compile('child$'), 'children'), @@ -423,7 +436,7 @@ def IDENTITY(x): return x def varquote_aux(name,quotestr='%s'): return name if regex_safe.match(name) else quotestr % name -if 'google' in drivers: +if 'google' in DRIVERS: is_jdbc = False @@ -637,6 +650,28 @@ class BaseAdapter(ConnectionPool): def file_delete(self, filename): os.unlink(filename) + def find_driver(self,adapter_args,uri=None): + if hasattr(self,'driver') and self.driver!=None: + return + drivers_available = [driver for driver in self.drivers + if driver in globals()] + if uri: + items = uri.split('://')[0].split(':') + request_driver = items[1] if len(items)>1 else None + else: + request_driver = None + request_driver = request_driver or adapter_args.get('driver') + if request_driver: + if request_driver in drivers_available: + self.driver = globals().get(request_driver) + else: + raise RuntimeError, "driver %s not available" % request_driver + elif drivers_available: + self.driver = globals().get(drivers_available[0]) + else: + raise RuntimeError, "no driver available %s", self.drivers + + def __init__(self, db,uri,pool_size=0, folder=None, db_codec='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): @@ -1943,8 +1978,8 @@ class BaseAdapter(ConnectionPool): ################################################################################### class SQLiteAdapter(BaseAdapter): + drivers = ('sqlite3','sqlite2') - driver = globals().get('sqlite3', None) can_select_for_update = None # support ourselves with BEGIN TRANSACTION def EXTRACT(self,field,what): @@ -1973,11 +2008,10 @@ class SQLiteAdapter(BaseAdapter): def __init__(self, db, uri, pool_size=0, folder=None, db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "sqlite" self.uri = uri + self.find_driver(adapter_args) self.pool_size = 0 self.folder = folder self.db_codec = db_codec @@ -2029,19 +2063,18 @@ class SQLiteAdapter(BaseAdapter): return super(SQLiteAdapter, self).select(query, fields, attributes) class SpatiaLiteAdapter(SQLiteAdapter): + drivers = ('sqlite3','sqlite2') - import copy types = copy.copy(BaseAdapter.types) types.update(geometry='GEOMETRY') def __init__(self, db, uri, pool_size=0, folder=None, db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}, srid=4326): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "spatialite" self.uri = uri + self.find_driver(adapter_args) self.pool_size = 0 self.folder = folder self.db_codec = db_codec @@ -2138,17 +2171,15 @@ class SpatiaLiteAdapter(SQLiteAdapter): class JDBCSQLiteAdapter(SQLiteAdapter): - - driver = globals().get('zxJDBC', None) + drivers = ('zxJDBC_sqlite',) def __init__(self, db, uri, pool_size=0, folder=None, db_codec='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "sqlite" self.uri = uri + self.find_driver(adapter_args) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -2164,7 +2195,7 @@ class JDBCSQLiteAdapter(SQLiteAdapter): self.folder.decode(path_encoding).encode('utf8'), dbpath) def connect(dbpath=dbpath,driver_args=driver_args): return self.driver.connect( - java.sql.DriverManager.getConnection('jdbc:sqlite:'+dbpath), + self.driver.getConnection('jdbc:sqlite:'+dbpath), **driver_args) self.pool_connection(connect) self.after_connection() @@ -2179,8 +2210,8 @@ class JDBCSQLiteAdapter(SQLiteAdapter): class MySQLAdapter(BaseAdapter): + drivers = ('MySQLdb','pymysql') - driver = globals().get('pymysql',None) maxcharlength = 255 commit_on_alter_table = True support_distributed_transaction = True @@ -2242,11 +2273,10 @@ class MySQLAdapter(BaseAdapter): def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "mysql" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -2276,6 +2306,8 @@ class MySQLAdapter(BaseAdapter): host=host, port=port, charset=charset) + + def connect(driver_args=driver_args): return self.driver.connect(**driver_args) self.pool_connection(connect) @@ -2290,10 +2322,7 @@ class MySQLAdapter(BaseAdapter): return int(self.cursor.fetchone()[0]) class PostgreSQLAdapter(BaseAdapter): - - driver = None - drivers = {'psycopg2': globals().get('psycopg2', None), - 'pg8000': globals().get('pg8000', None), } + drivers = ('psycopg2','pg8000') support_distributed_transaction = True types = { @@ -2326,9 +2355,12 @@ class PostgreSQLAdapter(BaseAdapter): return varquote_aux(name,'"%s"') def adapt(self,obj): - #if self.driver == self.drivers.get('pg8000'): - # obj = str(obj).replace('%','%%') - return psycopg2_adapt(obj).getquoted() + if self.driver_name == 'psycopg2': + return psycopg2_adapt(obj).getquoted() + elif self.driver_name == 'pg8000': + return str(obj).replace("%","%%").replace("'","''") + else: + return str(obj).replace("'","''") def sequence_name(self,table): return '%s_id_Seq' % table @@ -2365,17 +2397,15 @@ class PostgreSQLAdapter(BaseAdapter): def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}, srid=4326): - if not self.drivers.get('psycopg2') and not self.drivers.get('pg8000'): - raise RuntimeError, "Unable to import any drivers (psycopg2 or pg8000)" self.db = db self.dbengine = "postgres" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec self.srid = srid self.find_or_make_work_folder() - library, uri = uri.split('://')[:2] m = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:@]+)(\:(?P[0-9]+))?/(?P[^\?]+)(\?sslmode=(?P.+))?$').match(uri) if not m: raise SyntaxError, "Invalid URI string in DAL" @@ -2402,20 +2432,6 @@ class PostgreSQLAdapter(BaseAdapter): "port=%s password='%s'") \ % (db, user, host, port, password) # choose diver according uri - if library == "postgres": - if 'psycopg2' in self.drivers: - self.driver = self.drivers['psycopg2'] - elif 'pg8000' in self.drivers: - self.driver = self.drivers['pg8000'] - else: - raise RuntimeError, "No pgsql driver" - elif library == "postgres:psycopg2": - self.driver = self.drivers.get('psycopg2') - elif library == "postgres:pg8000": - self.driver = self.drivers.get('pg8000') - if not self.driver: - raise RuntimeError, "%s is not available" % library - self.__version__ = "%s %s" % (self.driver.__name__, self.driver.__version__) def connect(msg=msg,driver_args=driver_args): return self.driver.connect(msg,**driver_args) @@ -2546,6 +2562,8 @@ class PostgreSQLAdapter(BaseAdapter): return BaseAdapter.represent(self, obj, fieldtype) class NewPostgreSQLAdapter(PostgreSQLAdapter): + drivers = ('psycopg2','pg8000') + types = { 'boolean': 'CHAR(1)', 'string': 'VARCHAR(%(length)s)', @@ -2597,15 +2615,15 @@ class NewPostgreSQLAdapter(PostgreSQLAdapter): class JDBCPostgreSQLAdapter(PostgreSQLAdapter): + drivers = ('zxJDBC',) def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "postgres" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -2640,6 +2658,7 @@ class JDBCPostgreSQLAdapter(PostgreSQLAdapter): class OracleAdapter(BaseAdapter): + drivers = ('cx_Oracle',) driver = globals().get('cx_Oracle',None) @@ -2728,11 +2747,10 @@ class OracleAdapter(BaseAdapter): def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "oracle" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -2799,14 +2817,13 @@ class OracleAdapter(BaseAdapter): if blob_decode and isinstance(value, cx_Oracle.LOB): try: value = value.read() - except cx_Oracle.ProgrammingError: + except self.driver.ProgrammingError: # After a subsequent fetch the LOB value is not valid anymore pass return BaseAdapter.parse_value(self, value, field_type, blob_decode) class MSSQLAdapter(BaseAdapter): - - driver = globals().get('pyodbc',None) + drivers = ('pyodbc',) types = { 'boolean': 'BIT', @@ -2879,11 +2896,10 @@ class MSSQLAdapter(BaseAdapter): def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}, fake_connect=False, srid=4326): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "mssql" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -3004,6 +3020,8 @@ class MSSQLAdapter(BaseAdapter): class MSSQL2Adapter(MSSQLAdapter): + drivers = ('pyodbc',) + types = { 'boolean': 'CHAR(1)', 'string': 'NVARCHAR(%(length)s)', @@ -3040,8 +3058,7 @@ class MSSQL2Adapter(MSSQLAdapter): return self.log_execute(a.decode('utf8')) class SybaseAdapter(MSSQLAdapter): - - driver = globals().get('Sybase',None) + drivers = ('Sybase',) types = { 'boolean': 'BIT', @@ -3075,12 +3092,10 @@ class SybaseAdapter(MSSQLAdapter): def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}, fake_connect=False, srid=4326): - ### Fix this for sybase - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "sybase" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -3136,8 +3151,7 @@ class SybaseAdapter(MSSQLAdapter): class FireBirdAdapter(BaseAdapter): - - driver = globals().get('pyodbc',None) + drivers = ('kinterbasdb','firebirdsql','fdb','pyodbc') commit_on_alter_table = False support_distributed_transaction = True @@ -3196,26 +3210,11 @@ class FireBirdAdapter(BaseAdapter): def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, - adapter_args={}): - if 'driver_name' in adapter_args: - if adapter_args['driver_name'] == 'fdb': - self.driver = fdb - elif adapter_args['driver_name'] == 'firebirdsql': - self.driver = firebirdsql - elif adapter_args['driver_name'] == 'kinterbasdb': - self.driver = kinterbasdb - elif 'fdb' in globals(): - self.driver = fdb - elif 'kinterbasdb' in globals(): - self.driver = kinterbasdb - else: - raise RuntimeError, "no fdb and no kinterbasdb driver found" - - if not self.driver: - raise RuntimeError, "Unable to import driver" + adapter_args={}): self.db = db self.dbengine = "firebird" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -3264,26 +3263,15 @@ class FireBirdAdapter(BaseAdapter): class FireBirdEmbeddedAdapter(FireBirdAdapter): + drivers = ('kinterbasdb','firebirdsql','fdb','pyodbc') def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): - - if 'driver_name' in adapter_args: - if adapter_args['driver_name'] == 'fdb': - self.driver = fdb - elif adapter_args['driver_name'] == 'firebirdsql': - self.driver = firebirdsql - elif adapter_args['driver_name'] == 'kinterbasdb': - self.driver = kinterbasdb - else: - self.driver = fdb - - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "firebird" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -3319,8 +3307,7 @@ class FireBirdEmbeddedAdapter(FireBirdAdapter): class InformixAdapter(BaseAdapter): - - driver = globals().get('informixdb',None) + drivers = ('informixdb',) types = { 'boolean': 'CHAR(1)', @@ -3387,11 +3374,10 @@ class InformixAdapter(BaseAdapter): def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "informix" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -3435,8 +3421,7 @@ class InformixAdapter(BaseAdapter): class DB2Adapter(BaseAdapter): - - driver = globals().get('pyodbc',None) + drivers = ('pyodbc',) types = { 'boolean': 'CHAR(1)', @@ -3491,11 +3476,10 @@ class DB2Adapter(BaseAdapter): def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "db2" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -3522,8 +3506,7 @@ class DB2Adapter(BaseAdapter): class TeradataAdapter(BaseAdapter): - - driver = globals().get('pyodbc',None) + drivers = ('pyodbc',) types = { 'boolean': 'CHAR(1)', @@ -3556,11 +3539,10 @@ class TeradataAdapter(BaseAdapter): def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "teradata" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -3590,8 +3572,7 @@ INGRES_SEQNAME='ii***lineitemsequence' # NOTE invalid database object name # to be a delimited identifier) class IngresAdapter(BaseAdapter): - - driver = globals().get('ingresdbi',None) + drivers = ('ingredbi',) types = { 'boolean': 'CHAR(1)', @@ -3639,11 +3620,10 @@ class IngresAdapter(BaseAdapter): def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "ingres" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -3693,6 +3673,9 @@ class IngresAdapter(BaseAdapter): class IngresUnicodeAdapter(IngresAdapter): + + drivers = ('ingresdbi',) + types = { 'boolean': 'CHAR(1)', 'string': 'NVARCHAR(%(length)s)', @@ -3720,8 +3703,8 @@ class IngresUnicodeAdapter(IngresAdapter): } class SAPDBAdapter(BaseAdapter): + drivers = ('sapdb',) - driver = globals().get('sapdb',None) support_distributed_transaction = False types = { 'boolean': 'CHAR(1)', @@ -3770,11 +3753,10 @@ class SAPDBAdapter(BaseAdapter): def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "sapdb" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -3807,17 +3789,15 @@ class SAPDBAdapter(BaseAdapter): return int(self.cursor.fetchone()[0]) class CubridAdapter(MySQLAdapter): - - driver = globals().get('cubriddb', None) + drivers = ('cubriddb',) def __init__(self, db, uri, pool_size=0, folder=None, db_codec='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): - if not self.driver: - raise RuntimeError, "Unable to import driver" self.db = db self.dbengine = "cubrid" self.uri = uri + self.find_driver(adapter_args,uri) self.pool_size = pool_size self.folder = folder self.db_codec = db_codec @@ -4546,6 +4526,8 @@ def int2uuid(n): return str(uuid.UUID(int=n)) class CouchDBAdapter(NoSQLAdapter): + drivers = ('couchdb',) + uploads_in_blob = True types = { 'boolean': bool, @@ -4612,6 +4594,7 @@ class CouchDBAdapter(NoSQLAdapter): adapter_args={}): self.db = db self.uri = uri + self.find_driver(adapter_args) self.dbengine = 'couchdb' self.folder = folder db['_lastsql'] = '' @@ -4620,7 +4603,7 @@ class CouchDBAdapter(NoSQLAdapter): url='http://'+uri[10:] def connect(url=url,driver_args=driver_args): - return couchdb.Server(url,**driver_args) + return self.driver.Server(url,**driver_args) self.pool_connection(connect,cursor=False) self.after_connection() @@ -4747,6 +4730,8 @@ def cleanup(text): return text class MongoDBAdapter(NoSQLAdapter): + drivers = ('pymongo',) + uploads_in_blob = True types = { @@ -4774,6 +4759,10 @@ class MongoDBAdapter(NoSQLAdapter): pool_size=0,folder=None,db_codec ='UTF-8', credential_decoder=IDENTITY, driver_args={}, adapter_args={}): + self.db = db + self.uri = uri + self.find_driver(adapter_args) + m=None try: #Since version 2 @@ -4788,8 +4777,6 @@ class MongoDBAdapter(NoSQLAdapter): raise ImportError("Uriparser for mongodb is not available") except: raise SyntaxError("This type of uri is not supported by the mongodb uri parser") - self.db = db - self.uri = uri self.dbengine = 'mongodb' self.folder = folder db['_lastsql'] = '' @@ -4808,8 +4795,8 @@ class MongoDBAdapter(NoSQLAdapter): raise SyntaxError("Database is required!") def connect(uri=self.uri,m=m): try: - return pymongo.Connection(uri)[m.get('database')] - except pymongo.errors.ConnectionFailure, inst: + return self.driver.Connection(uri)[m.get('database')] + except self.driver.errors.ConnectionFailure, inst: raise SyntaxError, "The connection to " + uri + " could not be made" except Exception, inst: if inst == "cannot specify database without a username and password": @@ -5346,6 +5333,8 @@ class MongoDBAdapter(NoSQLAdapter): class IMAPAdapter(NoSQLAdapter): + drivers = ('imaplib',) + """ IMAP server adapter This class is intended as an interface with @@ -5490,6 +5479,7 @@ class IMAPAdapter(NoSQLAdapter): uri = uri.split("://")[1] self.db = db self.uri = uri + self.find_driver(adapter_args) self.pool_size=pool_size self.folder = folder self.db_codec = db_codec @@ -7560,7 +7550,7 @@ class Table(object): if rows: return rows[0] return None - elif str(key).isdigit() or 'google' in drivers and isinstance(key, Key): + elif str(key).isdigit() or 'google' in DRIVERS and isinstance(key, Key): return self._db(self._id == key).select(limitby=(0,1)).first() elif key: return ogetattr(self, str(key)) From ba70a779327d905dfe6f6a1bd7b2cac3fec8ae58 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 22:19:59 -0500 Subject: [PATCH 40/68] improved logic in dal driver selection (fixed) --- VERSION | 2 +- gluon/sql.py | 4 ++-- gluon/widget.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index 3d26aae4..492567e6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-30 22:10:41) stable +Version 2.0.3 (2012-08-30 22:19:55) stable diff --git a/gluon/sql.py b/gluon/sql.py index 1a43f4cc..f49878de 100644 --- a/gluon/sql.py +++ b/gluon/sql.py @@ -1,8 +1,8 @@ # this file exists for backward compatibility -__all__ = ['DAL','Field','drivers'] +__all__ = ['DAL','Field','DRIVERS'] -from dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, drivers, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType +from dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, DRIVERS, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType diff --git a/gluon/widget.py b/gluon/widget.py index 3b8055f5..f995df3d 100644 --- a/gluon/widget.py +++ b/gluon/widget.py @@ -1004,9 +1004,9 @@ def start(cron=True): print ProgramAuthor print ProgramVersion - from dal import drivers + from dal import DRIVERS if not options.nobanner: - print 'Database drivers available: %s' % ', '.join(drivers) + print 'Database drivers available: %s' % ', '.join(DRIVERS) # ## if -L load options from options.config file From 5edde2638e8498437cf12adaa174eb0a5c42342e Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 22:36:10 -0500 Subject: [PATCH 41/68] another driver selection issue --- VERSION | 2 +- gluon/dal.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 492567e6..d250e172 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-30 22:19:55) stable +Version 2.0.3 (2012-08-30 22:36:07) stable diff --git a/gluon/dal.py b/gluon/dal.py index 8027a6b2..41826093 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -663,11 +663,13 @@ class BaseAdapter(ConnectionPool): request_driver = request_driver or adapter_args.get('driver') if request_driver: if request_driver in drivers_available: + self.driver_name = request_driver self.driver = globals().get(request_driver) else: raise RuntimeError, "driver %s not available" % request_driver elif drivers_available: - self.driver = globals().get(drivers_available[0]) + self.driver_name = drivers_available[0] + self.driver = globals().get(self.driver_name) else: raise RuntimeError, "no driver available %s", self.drivers From c6037b035574c02870e9a895d0cd76c9ffb327e0 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 23:07:51 -0500 Subject: [PATCH 42/68] windows and osx cannot find the rules folder, for now, ignore it --- VERSION | 2 +- gluon/languages.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/VERSION b/VERSION index d250e172..24b002fd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-30 22:36:07) stable +Version 2.0.3 (2012-08-30 23:07:46) stable diff --git a/gluon/languages.py b/gluon/languages.py index d8ef8735..818ac90d 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -249,13 +249,14 @@ def read_possible_plurals(): pdir = pjoin(pdirname(__file__),'contrib','rules') plurals = {} # scan rules directory for plural_rules-*.py files: - for pname in os.listdir(pdir): - if not isdir(pname) and regex_plural_rules.match(pname): - lang = pname[13:-3] - fname = ospath.join(pdir, pname) - n, f1, f2, status = read_global_plural_rules(fname) - if status == 'ok': - plurals[lang] = (lang, n, f1, f2, pname) + if os.path.exists(pdir): + for pname in os.listdir(pdir): + if not isdir(pname) and regex_plural_rules.match(pname): + lang = pname[13:-3] + fname = ospath.join(pdir, pname) + n, f1, f2, status = read_global_plural_rules(fname) + if status == 'ok': + plurals[lang] = (lang, n, f1, f2, pname) plurals['default'] = ('default', DEFAULT_NPLURALS, DEFAULT_GET_PLURAL_ID, From 156c2c294e9ffe3c62fc495c3fab2fd671a4b658 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 30 Aug 2012 23:55:14 -0500 Subject: [PATCH 43/68] fixed plural rules with pkgutil --- VERSION | 2 +- applications/admin/languages/default.py | 3 +- applications/admin/views/default/design.html | 8 +--- gluon/contrib/rules/__init__.py | 1 - gluon/contrib/rules/plural_rules-af.py | 17 ------- gluon/contrib/rules/plural_rules-bg.py | 17 ------- gluon/contrib/rules/plural_rules-cs.py | 19 -------- gluon/contrib/rules/plural_rules-de.py | 17 ------- gluon/contrib/rules/plural_rules-en.py | 20 -------- gluon/contrib/rules/plural_rules-es.py | 17 ------- gluon/contrib/rules/plural_rules-fr.py | 17 ------- gluon/contrib/rules/plural_rules-he.py | 17 ------- gluon/contrib/rules/plural_rules-hi.py | 17 ------- gluon/contrib/rules/plural_rules-hu.py | 17 ------- gluon/contrib/rules/plural_rules-it.py | 17 ------- gluon/contrib/rules/plural_rules-ja.py | 14 ------ gluon/contrib/rules/plural_rules-lt.py | 19 -------- gluon/contrib/rules/plural_rules-pl.py | 19 -------- gluon/contrib/rules/plural_rules-pt.py | 17 ------- gluon/contrib/rules/plural_rules-ro.py | 17 ------- gluon/contrib/rules/plural_rules-ru.py | 20 -------- gluon/contrib/rules/plural_rules-sk.py | 20 -------- gluon/contrib/rules/plural_rules-sl.py | 20 -------- gluon/contrib/rules/plural_rules-tr.py | 14 ------ gluon/contrib/rules/plural_rules-uk.py | 21 --------- gluon/contrib/rules/plural_rules-zh.py | 14 ------ gluon/languages.py | 49 +++++--------------- 27 files changed, 17 insertions(+), 433 deletions(-) delete mode 100644 gluon/contrib/rules/__init__.py delete mode 100644 gluon/contrib/rules/plural_rules-af.py delete mode 100644 gluon/contrib/rules/plural_rules-bg.py delete mode 100644 gluon/contrib/rules/plural_rules-cs.py delete mode 100644 gluon/contrib/rules/plural_rules-de.py delete mode 100644 gluon/contrib/rules/plural_rules-en.py delete mode 100644 gluon/contrib/rules/plural_rules-es.py delete mode 100644 gluon/contrib/rules/plural_rules-fr.py delete mode 100644 gluon/contrib/rules/plural_rules-he.py delete mode 100644 gluon/contrib/rules/plural_rules-hi.py delete mode 100644 gluon/contrib/rules/plural_rules-hu.py delete mode 100644 gluon/contrib/rules/plural_rules-it.py delete mode 100644 gluon/contrib/rules/plural_rules-ja.py delete mode 100644 gluon/contrib/rules/plural_rules-lt.py delete mode 100644 gluon/contrib/rules/plural_rules-pl.py delete mode 100644 gluon/contrib/rules/plural_rules-pt.py delete mode 100644 gluon/contrib/rules/plural_rules-ro.py delete mode 100644 gluon/contrib/rules/plural_rules-ru.py delete mode 100644 gluon/contrib/rules/plural_rules-sk.py delete mode 100644 gluon/contrib/rules/plural_rules-sl.py delete mode 100644 gluon/contrib/rules/plural_rules-tr.py delete mode 100644 gluon/contrib/rules/plural_rules-uk.py delete mode 100644 gluon/contrib/rules/plural_rules-zh.py diff --git a/VERSION b/VERSION index 24b002fd..eb7c1797 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-30 23:07:46) stable +Version 2.0.3 (2012-08-30 23:55:09) stable diff --git a/applications/admin/languages/default.py b/applications/admin/languages/default.py index 5e7b0a7d..beeff162 100644 --- a/applications/admin/languages/default.py +++ b/applications/admin/languages/default.py @@ -53,6 +53,7 @@ 'Exception instance attributes': 'Exception instance attributes', 'exposes': 'exposes', 'extends': 'extends', +'file does not exist': 'file does not exist', 'filter': 'filter', 'Frames': 'Frames', 'Get from URL:': 'Get from URL:', @@ -72,8 +73,8 @@ 'Logout': 'Logout', 'Models': 'Models', 'models': 'models', -'modules': 'modules', 'Modules': 'Modules', +'modules': 'modules', 'New application wizard': 'New application wizard', 'New simple application': 'New simple application', 'Overwrite installed app': 'Overwrite installed app', diff --git a/applications/admin/views/default/design.html b/applications/admin/views/default/design.html index 1f1b0887..1934fc1d 100644 --- a/applications/admin/views/default/design.html +++ b/applications/admin/views/default/design.html @@ -223,16 +223,12 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi {{=editpluralsfile('languages',pfile,dict(nplurals=p[0]))}} - {{=peekfile('languages',pfile,dict(id=id))}}, + {{=peekfile('languages',pfile,dict(id=id))}} {{else:}} - {{=T("are not used yet")}}, + {{=T("are not used yet")}} {{pass}} {{pass}} - {{=T("rules:")}} - - {{=peekfile('gluon/contrib/rules', p[2], dict(app=app, id=id), p[3] if p[3]!='ok' else None)}} - {{pass}} ) diff --git a/gluon/contrib/rules/__init__.py b/gluon/contrib/rules/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/gluon/contrib/rules/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/gluon/contrib/rules/plural_rules-af.py b/gluon/contrib/rules/plural_rules-af.py deleted file mode 100644 index 9c0c8c61..00000000 --- a/gluon/contrib/rules/plural_rules-af.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for af (Afrikaans (South Africa)) - -nplurals=2 # Afrikaans language has 2 forms: - # 1 singular and 1 plural - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: int(n != 1) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-bg.py b/gluon/contrib/rules/plural_rules-bg.py deleted file mode 100644 index 985c5797..00000000 --- a/gluon/contrib/rules/plural_rules-bg.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for bg (Bulgarian) - -nplurals=2 # Bulgarian language has 2 forms: - # 1 singular and 1 plural - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: int(n != 1) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-cs.py b/gluon/contrib/rules/plural_rules-cs.py deleted file mode 100644 index 47e5e1d5..00000000 --- a/gluon/contrib/rules/plural_rules-cs.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for cs (Czech) - -nplurals=3 # Czech language has 3 forms: - # 1 singular and 2 plurals - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: ( 0 if n==1 else - 1 if 2<=n<=4 else - 2 ) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-de.py b/gluon/contrib/rules/plural_rules-de.py deleted file mode 100644 index 5393cd92..00000000 --- a/gluon/contrib/rules/plural_rules-de.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for de (Deutsch) - -nplurals=2 # German language has 2 forms: - # 1 singular and 1 plural - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: int(n != 1) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-en.py b/gluon/contrib/rules/plural_rules-en.py deleted file mode 100644 index 73a52400..00000000 --- a/gluon/contrib/rules/plural_rules-en.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for en (English) - -nplurals=2 # English language has 2 forms: - # 1 singular and 1 plural - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: int(n != 1) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -construct_plural_form = lambda word, plural_id: (word + - ('es' if word[-1:] in ('s','x','o') or - word[-2:] in ('sh','ch') - else 's')) - diff --git a/gluon/contrib/rules/plural_rules-es.py b/gluon/contrib/rules/plural_rules-es.py deleted file mode 100644 index 7f6a510a..00000000 --- a/gluon/contrib/rules/plural_rules-es.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for es (Spanish) - -nplurals=2 # Spanish language has 2 forms: - # 1 singular and 1 plural - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: int(n != 1) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-fr.py b/gluon/contrib/rules/plural_rules-fr.py deleted file mode 100644 index 4237c0e3..00000000 --- a/gluon/contrib/rules/plural_rules-fr.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for fr (French)) - -nplurals=2 # French language has 2 forms: - # 1 singular and 1 plural - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: int(n != 1) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-he.py b/gluon/contrib/rules/plural_rules-he.py deleted file mode 100644 index c1e316da..00000000 --- a/gluon/contrib/rules/plural_rules-he.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for he (Hebrew) - -nplurals=2 # Hebrew language has 2 forms: - # 1 singular and 1 plural - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: int(n != 1) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-hi.py b/gluon/contrib/rules/plural_rules-hi.py deleted file mode 100644 index d636c1e0..00000000 --- a/gluon/contrib/rules/plural_rules-hi.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for he (Hindi) - -nplurals=2 # Hindi has 2 forms: - # 1 singular and 1 plural - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: int(n != 1) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-hu.py b/gluon/contrib/rules/plural_rules-hu.py deleted file mode 100644 index ea960e29..00000000 --- a/gluon/contrib/rules/plural_rules-hu.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for hu (Hungarian) - -nplurals=2 # Hungarian language has 2 forms: - # 1 singular and 1 plural - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: int(n != 1) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-it.py b/gluon/contrib/rules/plural_rules-it.py deleted file mode 100644 index ba3c7b5d..00000000 --- a/gluon/contrib/rules/plural_rules-it.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for it (Italian) - -nplurals=2 # Italian language has 2 forms: - # 1 singular and 1 plural - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: int(n != 1) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-ja.py b/gluon/contrib/rules/plural_rules-ja.py deleted file mode 100644 index 168bccb8..00000000 --- a/gluon/contrib/rules/plural_rules-ja.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for ja (Japanese) - -nplurals=1 # Japanese language has ONE form! - -# Always returns 0: -get_plural_id = lambda n: 0 - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: word - diff --git a/gluon/contrib/rules/plural_rules-lt.py b/gluon/contrib/rules/plural_rules-lt.py deleted file mode 100644 index c7f4451f..00000000 --- a/gluon/contrib/rules/plural_rules-lt.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for lt (Lithuanian) - -nplurals=3 # Lithuanian language has 3 forms: - # 1 singular and 2 plurals - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: (0 if n % 10 == 1 and n % 100 != 11 else - 1 if n % 10 >= 2 and (n % 100 < 10 or n % 100 >= 20) else - 2) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-pl.py b/gluon/contrib/rules/plural_rules-pl.py deleted file mode 100644 index a61a6a53..00000000 --- a/gluon/contrib/rules/plural_rules-pl.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for pl (Polish) - -nplurals=3 # Polish language has 3 forms: - # 1 singular and 2 plurals - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: (0 if n==1 else - 1 if 2<=n<=4 else - 2) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-pt.py b/gluon/contrib/rules/plural_rules-pt.py deleted file mode 100644 index 57718af3..00000000 --- a/gluon/contrib/rules/plural_rules-pt.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for pt (Portuguese) - -nplurals=2 # Portuguese has 2 forms: - # 1 singular and 1 plural - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: int(n != 1) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-ro.py b/gluon/contrib/rules/plural_rules-ro.py deleted file mode 100644 index 9158a3d4..00000000 --- a/gluon/contrib/rules/plural_rules-ro.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for ro (Romanian) - -nplurals=2 # Romanian has 2 forms: - # 1 singular and 1 plural - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: int(n != 1) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-ru.py b/gluon/contrib/rules/plural_rules-ru.py deleted file mode 100644 index 23c4f563..00000000 --- a/gluon/contrib/rules/plural_rules-ru.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for ru (Russian) - -nplurals=3 # Russian language has 3 forms: - # 1 singular and 2 plurals - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: (0 if n % 10 == 1 and n % 100 != 11 else - 1 if n % 10 >= 2 and n % 10 <= 4 and - (n % 100 < 10 or n % 100 >= 20) else - 2) - -# construct_plural_form() is not used now because of complex -# rules of Russian language. Default version of -# this function is used to simple insert new words into -# plural_dict dictionary) -# construct_plural_form = lambda word, plural_id: word diff --git a/gluon/contrib/rules/plural_rules-sk.py b/gluon/contrib/rules/plural_rules-sk.py deleted file mode 100644 index df8d5232..00000000 --- a/gluon/contrib/rules/plural_rules-sk.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for sk (Slovak (Slovakia)) - -nplurals=3 # Slovak language has 3 forms: - # 1 singular and 2 plurals - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: (0 if n % 10 == 1 and n % 100 != 11 else - 1 if n % 10 >= 2 and n % 10 <= 4 and - (n % 100 < 10 or n % 100 >= 20) else - 2) - -# construct_plural_form() is not used now because of complex -# rules of Slovak language. Default version of this function -# is used to simple insert new words into plural_dict dictionary) -# construct_plural_form = lambda word, plural_id: word - diff --git a/gluon/contrib/rules/plural_rules-sl.py b/gluon/contrib/rules/plural_rules-sl.py deleted file mode 100644 index 465072d2..00000000 --- a/gluon/contrib/rules/plural_rules-sl.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for sl (Slovenian) - -nplurals=4 # Slovenian language has 4 forms: - # 1 singular and 3 plurals - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: (0 if n % 100 == 1 else - 1 if n % 100 == 2 else - 2 if n % 100 in (3,4) else - 3) - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: (word + 'suffix') - diff --git a/gluon/contrib/rules/plural_rules-tr.py b/gluon/contrib/rules/plural_rules-tr.py deleted file mode 100644 index 8d61e14e..00000000 --- a/gluon/contrib/rules/plural_rules-tr.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for tr (Turkish) - -nplurals=1 # Turkish language has ONE form! - -# Always returns 0: -get_plural_id = lambda n: 0 - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: word - diff --git a/gluon/contrib/rules/plural_rules-uk.py b/gluon/contrib/rules/plural_rules-uk.py deleted file mode 100644 index bbd4b4e4..00000000 --- a/gluon/contrib/rules/plural_rules-uk.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for uk (Ukrainian) - -nplurals=3 # Ukrainian language has 3 forms: - # 1 singular and 2 plurals - -# Determine plural_id for number *n* as sequence of positive -# integers: 0,1,... -# NOTE! For singular form ALWAYS return plural_id = 0 -get_plural_id = lambda n: (0 if n % 10 == 1 and n % 100 != 11 else - 1 if n % 10 >= 2 and n % 10 <= 4 and - (n % 100 < 10 or n % 100 >= 20) else - 2) - -# construct_plural_form() is not used now because of complex -# rules of Ukrainian language. Default version of -# this function is used to simple insert new words into -# plural_dict dictionary) -# construct_plural_form = lambda word, plural_id: word - diff --git a/gluon/contrib/rules/plural_rules-zh.py b/gluon/contrib/rules/plural_rules-zh.py deleted file mode 100644 index 0eed6c7c..00000000 --- a/gluon/contrib/rules/plural_rules-zh.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf8 -*- -# Plural-Forms for zh (Chinese) - -nplurals=1 # Chinese language has ONE form! - -# Always returns 0: -get_plural_id = lambda n: 0 - -# Construct and return plural form of *word* using -# *plural_id* (which ALWAYS>0). This function will be executed -# for words (or phrases) not found in plural_dict dictionary -# construct_plural_form = lambda word, plural_id: word - diff --git a/gluon/languages.py b/gluon/languages.py index 818ac90d..2d751d95 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -12,6 +12,7 @@ Plural subsystem is created by Vladyslav Kozlovskyy (Ukraine) import os import re +import pkgutil from utf8 import Utf8 from cgi import escape import portalocker @@ -82,7 +83,6 @@ regex_backslash = re.compile(r"\\([\\{}%])") regex_plural = re.compile('%({.+?})') regex_plural_dict = re.compile('^{(?P[^()[\]][^()[\]]*?)\((?P[^()\[\]]+)\)}$') # %%{word(varname or number)} regex_plural_tuple = re.compile('^{(?P[^[\]()]+)(?:\[(?P\d+)\])?}$') # %%{word[index]} or %%{word} -regex_plural_rules = re.compile('^plural_rules-[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$') # UTF8 helper functions def upper_fun(s): @@ -215,48 +215,23 @@ def read_possible_languages(appdir): langs['en'] = ('en', 'English', 0) return langs -def read_global_plural_rules(filename): - """ - retrieve plural rules from rules/*plural_rules-lang*.py file. - - args: - filename (str): plural_rules filename - - returns: - (nplurals, get_plural_id, construct_plural_form, status) - e.g.: (3, , , ok) - """ - env = {} - data = portalocker.read_locked(filename) - try: - exec(data) in env - status='ok' - except Exception, e: - status='Syntax error in %s (%s)' % (filename, e) - logging.error(status) - nplurals = env.get('nplurals', DEFAULT_NPLURALS) - get_plural_id = env.get('get_plural_id', DEFAULT_GET_PLURAL_ID) - construct_plural_form = env.get('construct_plural_form', - DEFAULT_CONSTRUCTOR_PLURAL_FORM) - return (nplurals, get_plural_id, construct_plural_form, status) - - def read_possible_plurals(): """ create list of all possible plural rules files result is cached to increase speed """ - pdir = pjoin(pdirname(__file__),'contrib','rules') + import gluon.contrib.plural_rules as package plurals = {} - # scan rules directory for plural_rules-*.py files: - if os.path.exists(pdir): - for pname in os.listdir(pdir): - if not isdir(pname) and regex_plural_rules.match(pname): - lang = pname[13:-3] - fname = ospath.join(pdir, pname) - n, f1, f2, status = read_global_plural_rules(fname) - if status == 'ok': - plurals[lang] = (lang, n, f1, f2, pname) + for importer, modname, ispkg in pkgutil.iter_modules(package.__path__): + if len(modname)==2: + module = __import__(package.__name__+'.'+modname) + lang = modname + pname = modname+'.py' + nplurals = getattr(module,'nplurals', DEFAULT_NPLURALS) + get_plural_id = getattr(module,'get_plural_id', DEFAULT_GET_PLURAL_ID) + construct_plural_form = getattr(module,'construct_plural_form', + DEFAULT_CONSTRUCTOR_PLURAL_FORM) + plurals[lang] = (lang, nplurals, get_plural_id, construct_plural_form, pname) plurals['default'] = ('default', DEFAULT_NPLURALS, DEFAULT_GET_PLURAL_ID, From f0d075bbd6894473c911a494265c8d795efe17af Mon Sep 17 00:00:00 2001 From: mdipierro Date: Fri, 31 Aug 2012 00:11:33 -0500 Subject: [PATCH 44/68] fixed postgresql uri bug --- VERSION | 2 +- gluon/dal.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index eb7c1797..9ac0127c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-30 23:55:09) stable +Version 2.0.3 (2012-08-31 00:11:28) stable diff --git a/gluon/dal.py b/gluon/dal.py index 41826093..156b42bc 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -2408,6 +2408,7 @@ class PostgreSQLAdapter(BaseAdapter): self.db_codec = db_codec self.srid = srid self.find_or_make_work_folder() + uri = uri.split('://')[:2] m = re.compile('^(?P[^:@]+)(\:(?P[^@]*))?@(?P[^\:@]+)(\:(?P[0-9]+))?/(?P[^\?]+)(\?sslmode=(?P.+))?$').match(uri) if not m: raise SyntaxError, "Invalid URI string in DAL" @@ -3936,10 +3937,10 @@ class GoogleSQLAdapter(UseDatabaseStoredFile,MySQLAdapter): self.db_codec = db_codec self.folder = folder or pjoin('$HOME',thread.folder.split( os.sep+'applications'+os.sep,1)[1]) - - m = re.compile('^(?P.*)/(?P.*)$').match(self.uri[len('google:sql://'):]) + uri = uri.split("://")[1] + m = re.compile('^(?P.*)/(?P.*)$').match(uri) if not m: - raise SyntaxError, "Invalid URI string in SQLDB: %s" % self._uri + raise SyntaxError, "Invalid URI string in SQLDB: %s" % uri instance = credential_decoder(m.group('instance')) self.dbstring = db = credential_decoder(m.group('db')) driver_args['instance'] = instance @@ -5478,7 +5479,6 @@ class IMAPAdapter(NoSQLAdapter): # db uri: user@example.com:password@imap.server.com:123 # TODO: max size adapter argument for preventing large mail transfers - uri = uri.split("://")[1] self.db = db self.uri = uri self.find_driver(adapter_args) @@ -5492,6 +5492,7 @@ class IMAPAdapter(NoSQLAdapter): self.charset = sys.getfilesystemencoding() # imap class self.imap4 = None + uri = uri.split("://")[1] """ MESSAGE is an identifier for sequence number""" From 77618dd18be3787152441fe8e80c1778dea7e9c0 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Fri, 31 Aug 2012 00:29:09 -0500 Subject: [PATCH 45/68] added plural rules again --- VERSION | 2 +- gluon/contrib/plural_rules/__init__.py | 1 + gluon/contrib/plural_rules/af.py | 17 +++++++++++++++++ gluon/contrib/plural_rules/bg.py | 17 +++++++++++++++++ gluon/contrib/plural_rules/cs.py | 19 +++++++++++++++++++ gluon/contrib/plural_rules/de.py | 17 +++++++++++++++++ gluon/contrib/plural_rules/en.py | 20 ++++++++++++++++++++ gluon/contrib/plural_rules/es.py | 17 +++++++++++++++++ gluon/contrib/plural_rules/fr.py | 17 +++++++++++++++++ gluon/contrib/plural_rules/he.py | 17 +++++++++++++++++ gluon/contrib/plural_rules/hi.py | 17 +++++++++++++++++ gluon/contrib/plural_rules/hu.py | 17 +++++++++++++++++ gluon/contrib/plural_rules/it.py | 17 +++++++++++++++++ gluon/contrib/plural_rules/ja.py | 14 ++++++++++++++ gluon/contrib/plural_rules/lt.py | 19 +++++++++++++++++++ gluon/contrib/plural_rules/pl.py | 19 +++++++++++++++++++ gluon/contrib/plural_rules/pt.py | 17 +++++++++++++++++ gluon/contrib/plural_rules/ro.py | 17 +++++++++++++++++ gluon/contrib/plural_rules/ru.py | 20 ++++++++++++++++++++ gluon/contrib/plural_rules/sk.py | 20 ++++++++++++++++++++ gluon/contrib/plural_rules/sl.py | 20 ++++++++++++++++++++ gluon/contrib/plural_rules/tr.py | 14 ++++++++++++++ gluon/contrib/plural_rules/uk.py | 21 +++++++++++++++++++++ gluon/contrib/plural_rules/zh.py | 14 ++++++++++++++ 24 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 gluon/contrib/plural_rules/__init__.py create mode 100644 gluon/contrib/plural_rules/af.py create mode 100644 gluon/contrib/plural_rules/bg.py create mode 100644 gluon/contrib/plural_rules/cs.py create mode 100644 gluon/contrib/plural_rules/de.py create mode 100644 gluon/contrib/plural_rules/en.py create mode 100644 gluon/contrib/plural_rules/es.py create mode 100644 gluon/contrib/plural_rules/fr.py create mode 100644 gluon/contrib/plural_rules/he.py create mode 100644 gluon/contrib/plural_rules/hi.py create mode 100644 gluon/contrib/plural_rules/hu.py create mode 100644 gluon/contrib/plural_rules/it.py create mode 100644 gluon/contrib/plural_rules/ja.py create mode 100644 gluon/contrib/plural_rules/lt.py create mode 100644 gluon/contrib/plural_rules/pl.py create mode 100644 gluon/contrib/plural_rules/pt.py create mode 100644 gluon/contrib/plural_rules/ro.py create mode 100644 gluon/contrib/plural_rules/ru.py create mode 100644 gluon/contrib/plural_rules/sk.py create mode 100644 gluon/contrib/plural_rules/sl.py create mode 100644 gluon/contrib/plural_rules/tr.py create mode 100644 gluon/contrib/plural_rules/uk.py create mode 100644 gluon/contrib/plural_rules/zh.py diff --git a/VERSION b/VERSION index 9ac0127c..b232783f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-31 00:11:28) stable +Version 2.0.3 (2012-08-31 00:29:05) stable diff --git a/gluon/contrib/plural_rules/__init__.py b/gluon/contrib/plural_rules/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/gluon/contrib/plural_rules/__init__.py @@ -0,0 +1 @@ + diff --git a/gluon/contrib/plural_rules/af.py b/gluon/contrib/plural_rules/af.py new file mode 100644 index 00000000..9c0c8c61 --- /dev/null +++ b/gluon/contrib/plural_rules/af.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for af (Afrikaans (South Africa)) + +nplurals=2 # Afrikaans language has 2 forms: + # 1 singular and 1 plural + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: int(n != 1) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/bg.py b/gluon/contrib/plural_rules/bg.py new file mode 100644 index 00000000..985c5797 --- /dev/null +++ b/gluon/contrib/plural_rules/bg.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for bg (Bulgarian) + +nplurals=2 # Bulgarian language has 2 forms: + # 1 singular and 1 plural + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: int(n != 1) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/cs.py b/gluon/contrib/plural_rules/cs.py new file mode 100644 index 00000000..47e5e1d5 --- /dev/null +++ b/gluon/contrib/plural_rules/cs.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for cs (Czech) + +nplurals=3 # Czech language has 3 forms: + # 1 singular and 2 plurals + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: ( 0 if n==1 else + 1 if 2<=n<=4 else + 2 ) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/de.py b/gluon/contrib/plural_rules/de.py new file mode 100644 index 00000000..5393cd92 --- /dev/null +++ b/gluon/contrib/plural_rules/de.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for de (Deutsch) + +nplurals=2 # German language has 2 forms: + # 1 singular and 1 plural + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: int(n != 1) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/en.py b/gluon/contrib/plural_rules/en.py new file mode 100644 index 00000000..73a52400 --- /dev/null +++ b/gluon/contrib/plural_rules/en.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for en (English) + +nplurals=2 # English language has 2 forms: + # 1 singular and 1 plural + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: int(n != 1) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +construct_plural_form = lambda word, plural_id: (word + + ('es' if word[-1:] in ('s','x','o') or + word[-2:] in ('sh','ch') + else 's')) + diff --git a/gluon/contrib/plural_rules/es.py b/gluon/contrib/plural_rules/es.py new file mode 100644 index 00000000..7f6a510a --- /dev/null +++ b/gluon/contrib/plural_rules/es.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for es (Spanish) + +nplurals=2 # Spanish language has 2 forms: + # 1 singular and 1 plural + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: int(n != 1) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/fr.py b/gluon/contrib/plural_rules/fr.py new file mode 100644 index 00000000..4237c0e3 --- /dev/null +++ b/gluon/contrib/plural_rules/fr.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for fr (French)) + +nplurals=2 # French language has 2 forms: + # 1 singular and 1 plural + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: int(n != 1) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/he.py b/gluon/contrib/plural_rules/he.py new file mode 100644 index 00000000..c1e316da --- /dev/null +++ b/gluon/contrib/plural_rules/he.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for he (Hebrew) + +nplurals=2 # Hebrew language has 2 forms: + # 1 singular and 1 plural + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: int(n != 1) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/hi.py b/gluon/contrib/plural_rules/hi.py new file mode 100644 index 00000000..d636c1e0 --- /dev/null +++ b/gluon/contrib/plural_rules/hi.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for he (Hindi) + +nplurals=2 # Hindi has 2 forms: + # 1 singular and 1 plural + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: int(n != 1) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/hu.py b/gluon/contrib/plural_rules/hu.py new file mode 100644 index 00000000..ea960e29 --- /dev/null +++ b/gluon/contrib/plural_rules/hu.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for hu (Hungarian) + +nplurals=2 # Hungarian language has 2 forms: + # 1 singular and 1 plural + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: int(n != 1) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/it.py b/gluon/contrib/plural_rules/it.py new file mode 100644 index 00000000..ba3c7b5d --- /dev/null +++ b/gluon/contrib/plural_rules/it.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for it (Italian) + +nplurals=2 # Italian language has 2 forms: + # 1 singular and 1 plural + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: int(n != 1) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/ja.py b/gluon/contrib/plural_rules/ja.py new file mode 100644 index 00000000..168bccb8 --- /dev/null +++ b/gluon/contrib/plural_rules/ja.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for ja (Japanese) + +nplurals=1 # Japanese language has ONE form! + +# Always returns 0: +get_plural_id = lambda n: 0 + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: word + diff --git a/gluon/contrib/plural_rules/lt.py b/gluon/contrib/plural_rules/lt.py new file mode 100644 index 00000000..c7f4451f --- /dev/null +++ b/gluon/contrib/plural_rules/lt.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for lt (Lithuanian) + +nplurals=3 # Lithuanian language has 3 forms: + # 1 singular and 2 plurals + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: (0 if n % 10 == 1 and n % 100 != 11 else + 1 if n % 10 >= 2 and (n % 100 < 10 or n % 100 >= 20) else + 2) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/pl.py b/gluon/contrib/plural_rules/pl.py new file mode 100644 index 00000000..a61a6a53 --- /dev/null +++ b/gluon/contrib/plural_rules/pl.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for pl (Polish) + +nplurals=3 # Polish language has 3 forms: + # 1 singular and 2 plurals + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: (0 if n==1 else + 1 if 2<=n<=4 else + 2) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/pt.py b/gluon/contrib/plural_rules/pt.py new file mode 100644 index 00000000..57718af3 --- /dev/null +++ b/gluon/contrib/plural_rules/pt.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for pt (Portuguese) + +nplurals=2 # Portuguese has 2 forms: + # 1 singular and 1 plural + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: int(n != 1) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/ro.py b/gluon/contrib/plural_rules/ro.py new file mode 100644 index 00000000..9158a3d4 --- /dev/null +++ b/gluon/contrib/plural_rules/ro.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for ro (Romanian) + +nplurals=2 # Romanian has 2 forms: + # 1 singular and 1 plural + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: int(n != 1) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/ru.py b/gluon/contrib/plural_rules/ru.py new file mode 100644 index 00000000..23c4f563 --- /dev/null +++ b/gluon/contrib/plural_rules/ru.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for ru (Russian) + +nplurals=3 # Russian language has 3 forms: + # 1 singular and 2 plurals + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: (0 if n % 10 == 1 and n % 100 != 11 else + 1 if n % 10 >= 2 and n % 10 <= 4 and + (n % 100 < 10 or n % 100 >= 20) else + 2) + +# construct_plural_form() is not used now because of complex +# rules of Russian language. Default version of +# this function is used to simple insert new words into +# plural_dict dictionary) +# construct_plural_form = lambda word, plural_id: word diff --git a/gluon/contrib/plural_rules/sk.py b/gluon/contrib/plural_rules/sk.py new file mode 100644 index 00000000..df8d5232 --- /dev/null +++ b/gluon/contrib/plural_rules/sk.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for sk (Slovak (Slovakia)) + +nplurals=3 # Slovak language has 3 forms: + # 1 singular and 2 plurals + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: (0 if n % 10 == 1 and n % 100 != 11 else + 1 if n % 10 >= 2 and n % 10 <= 4 and + (n % 100 < 10 or n % 100 >= 20) else + 2) + +# construct_plural_form() is not used now because of complex +# rules of Slovak language. Default version of this function +# is used to simple insert new words into plural_dict dictionary) +# construct_plural_form = lambda word, plural_id: word + diff --git a/gluon/contrib/plural_rules/sl.py b/gluon/contrib/plural_rules/sl.py new file mode 100644 index 00000000..465072d2 --- /dev/null +++ b/gluon/contrib/plural_rules/sl.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for sl (Slovenian) + +nplurals=4 # Slovenian language has 4 forms: + # 1 singular and 3 plurals + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: (0 if n % 100 == 1 else + 1 if n % 100 == 2 else + 2 if n % 100 in (3,4) else + 3) + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: (word + 'suffix') + diff --git a/gluon/contrib/plural_rules/tr.py b/gluon/contrib/plural_rules/tr.py new file mode 100644 index 00000000..8d61e14e --- /dev/null +++ b/gluon/contrib/plural_rules/tr.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for tr (Turkish) + +nplurals=1 # Turkish language has ONE form! + +# Always returns 0: +get_plural_id = lambda n: 0 + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: word + diff --git a/gluon/contrib/plural_rules/uk.py b/gluon/contrib/plural_rules/uk.py new file mode 100644 index 00000000..bbd4b4e4 --- /dev/null +++ b/gluon/contrib/plural_rules/uk.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for uk (Ukrainian) + +nplurals=3 # Ukrainian language has 3 forms: + # 1 singular and 2 plurals + +# Determine plural_id for number *n* as sequence of positive +# integers: 0,1,... +# NOTE! For singular form ALWAYS return plural_id = 0 +get_plural_id = lambda n: (0 if n % 10 == 1 and n % 100 != 11 else + 1 if n % 10 >= 2 and n % 10 <= 4 and + (n % 100 < 10 or n % 100 >= 20) else + 2) + +# construct_plural_form() is not used now because of complex +# rules of Ukrainian language. Default version of +# this function is used to simple insert new words into +# plural_dict dictionary) +# construct_plural_form = lambda word, plural_id: word + diff --git a/gluon/contrib/plural_rules/zh.py b/gluon/contrib/plural_rules/zh.py new file mode 100644 index 00000000..0eed6c7c --- /dev/null +++ b/gluon/contrib/plural_rules/zh.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# Plural-Forms for zh (Chinese) + +nplurals=1 # Chinese language has ONE form! + +# Always returns 0: +get_plural_id = lambda n: 0 + +# Construct and return plural form of *word* using +# *plural_id* (which ALWAYS>0). This function will be executed +# for words (or phrases) not found in plural_dict dictionary +# construct_plural_form = lambda word, plural_id: word + From 92ad68aad7d134a47df1d5006d336e2d584fdd24 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Fri, 31 Aug 2012 00:31:51 -0500 Subject: [PATCH 46/68] try... execpt plural_rules --- VERSION | 2 +- gluon/languages.py | 31 +++++++++++++++++++------------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/VERSION b/VERSION index b232783f..a684f03d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-31 00:29:05) stable +Version 2.0.3 (2012-08-31 00:31:48) stable diff --git a/gluon/languages.py b/gluon/languages.py index 2d751d95..b39dc4ce 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -220,18 +220,25 @@ def read_possible_plurals(): create list of all possible plural rules files result is cached to increase speed """ - import gluon.contrib.plural_rules as package - plurals = {} - for importer, modname, ispkg in pkgutil.iter_modules(package.__path__): - if len(modname)==2: - module = __import__(package.__name__+'.'+modname) - lang = modname - pname = modname+'.py' - nplurals = getattr(module,'nplurals', DEFAULT_NPLURALS) - get_plural_id = getattr(module,'get_plural_id', DEFAULT_GET_PLURAL_ID) - construct_plural_form = getattr(module,'construct_plural_form', - DEFAULT_CONSTRUCTOR_PLURAL_FORM) - plurals[lang] = (lang, nplurals, get_plural_id, construct_plural_form, pname) + try: + import gluon.contrib.plural_rules as package + plurals = {} + for importer, modname, ispkg in pkgutil.iter_modules(package.__path__): + if len(modname)==2: + module = __import__(package.__name__+'.'+modname) + lang = modname + pname = modname+'.py' + nplurals = getattr(module,'nplurals', DEFAULT_NPLURALS) + get_plural_id = getattr( + module,'get_plural_id', + DEFAULT_GET_PLURAL_ID) + construct_plural_form = getattr( + module,'construct_plural_form', + DEFAULT_CONSTRUCTOR_PLURAL_FORM) + plurals[lang] = (lang, nplurals, get_plural_id, + construct_plural_form, pname) + except ImportError: + logging.warn('Unable to import plural rules') plurals['default'] = ('default', DEFAULT_NPLURALS, DEFAULT_GET_PLURAL_ID, From a017996aba0d14db7b4b95ec273a56bc8c1f2bd7 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Fri, 31 Aug 2012 09:02:54 -0500 Subject: [PATCH 47/68] yet better markmin has [[NEWLINE]], thanks Vladyslav --- VERSION | 2 +- applications/admin/languages/default.py | 15 ++ gluon/contrib/markmin/markmin2html.py | 232 +++++++++++++++--------- gluon/dal.py | 5 - gluon/tests/__init__.py | 1 + 5 files changed, 160 insertions(+), 95 deletions(-) diff --git a/VERSION b/VERSION index a684f03d..edc6d4fa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.0.3 (2012-08-31 00:31:48) stable +Version 2.0.3 (2012-08-31 09:02:48) stable diff --git a/applications/admin/languages/default.py b/applications/admin/languages/default.py index beeff162..88f8dde5 100644 --- a/applications/admin/languages/default.py +++ b/applications/admin/languages/default.py @@ -33,6 +33,7 @@ 'created by': 'created by', 'crontab': 'crontab', 'currently running': 'currently running', +'currently saved or': 'currently saved or', 'database administration': 'database administration', 'Debug': 'Debug', 'defines tables': 'defines tables', @@ -43,17 +44,22 @@ 'Detailed traceback description': 'Detailed traceback description', 'direction: ltr': 'direction: ltr', 'Disable': 'Disable', +'docs': 'docs', 'download layouts': 'download layouts', 'download plugins': 'download plugins', 'Edit': 'Edit', 'Edit application': 'Edit application', +'edit views:': 'edit views:', +'Editing file "%s"': 'Editing file "%s"', 'Error snapshot': 'Error snapshot', 'Error ticket': 'Error ticket', 'Errors': 'Errors', 'Exception instance attributes': 'Exception instance attributes', 'exposes': 'exposes', +'exposes:': 'exposes:', 'extends': 'extends', 'file does not exist': 'file does not exist', +'file saved on %s': 'file saved on %s', 'filter': 'filter', 'Frames': 'Frames', 'Get from URL:': 'Get from URL:', @@ -64,8 +70,10 @@ 'inspect attributes': 'inspect attributes', 'Install': 'Install', 'Installed applications': 'Installed applications', +'Key bindings': 'Key bindings', 'languages': 'languages', 'Languages': 'Languages', +'Last saved on:': 'Last saved on:', 'loading...': 'loading...', 'locals': 'locals', 'Login': 'Login', @@ -89,10 +97,15 @@ 'Reload routes': 'Reload routes', 'request': 'request', 'response': 'response', +'restore': 'restore', +'revert': 'revert', 'rules are not defined': 'rules are not defined', 'rules:': 'rules:', "Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')", 'Running on %s': 'Running on %s', +'Save': 'Save', +'Save via Ajax': 'Save via Ajax', +'Saved file hash:': 'Saved file hash:', 'session': 'session', 'shell': 'shell', 'Site': 'Site', @@ -110,7 +123,9 @@ 'These files are served without processing, your images go here': 'These files are served without processing, your images go here', 'Ticket ID': 'Ticket ID', 'Ticket Missing': 'Ticket Missing', +'to previous version.': 'to previous version.', 'To create a plugin, name a file/folder plugin_[name]': 'To create a plugin, name a file/folder plugin_[name]', +'toggle breakpoint': 'toggle breakpoint', 'Traceback': 'Traceback', 'Translation strings for the application': 'Translation strings for the application', 'Uninstall': 'Uninstall', diff --git a/gluon/contrib/markmin/markmin2html.py b/gluon/contrib/markmin/markmin2html.py index e0b955e2..e1f365c0 100755 --- a/gluon/contrib/markmin/markmin2html.py +++ b/gluon/contrib/markmin/markmin2html.py @@ -1,4 +1,4 @@ -#!/bin/env python +#!/usr/bin/env python # -*- coding: utf-8 -*- # created by Massimo Di Pierro # recreated by Vladyslav Kozlovskyy @@ -44,8 +44,8 @@ from markmin2pdf import markmin2pdf # requires pdflatex print markmin2pdf(m) `` ==================== -# This is a test block with new features: - +# This is a test block + with new features: This is a blockquote with a list with tables in it: ----------- @@ -71,7 +71,9 @@ a list with tables in it: -----------:blockquoteclass[blockquoteid] This this a new paragraph -with a table. Table has header, footer, sections, odd and even rows: +with a followed table. +Table has header, footer, sections, +odd and even rows: ------------------------------- **Title 1**|**Title 2**|**Title 3** ============================== @@ -98,6 +100,8 @@ Now lists can be multilevel: You can continue item text on next strings +. paragraph in an item + ++. Ordered item 1 of sublevel 2 with a paragraph (paragraph can start with point after plus or minus @@ -143,13 +147,13 @@ line 1 line 2 line 3 `` -++++. Yet another item with code block: -`` +++++. Yet another item with code block (we need to indent \`\` to add code block as part of item): + `` line 1 line 2 line 3 `` -This item finishes with this paragraph. + This item finishes with this paragraph. ... Item in sublevel 3 can be continued with paragraphs. @@ -201,7 +205,7 @@ We wanted a markup language with the following requirements: - 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 specify class for tables, blockquotes and code elements - can add anchors - does not use _ for markup (since it creates odd behavior) - automatically links urls @@ -420,6 +424,23 @@ generates (the ``!`!`...`!`!:custom`` block is rendered by the ``custom=lambda`` function passed to ``render``). +### Line breaks + +``[[NEWLINE]]`` tag is used to break lines: +`` +#### Multiline [[NEWLINE]] + title +paragraph [[NEWLINE]] +with breaks[[NEWLINE]]in it +`` +generates: + +#### Multiline [[NEWLINE]] + title +paragraph [[NEWLINE]] +with breaks[[NEWLINE]]in it + + ### Html5 support Markmin also supports the