From e40937bd8b7eafd57328b717dcc885a98c04deb5 Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 25 Sep 2014 09:51:38 -0300 Subject: [PATCH 01/30] added https option for tornado now tornado can be called by https --- gluon/contrib/websocket_messaging.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/gluon/contrib/websocket_messaging.py b/gluon/contrib/websocket_messaging.py index dcd4a35e..72d53b35 100644 --- a/gluon/contrib/websocket_messaging.py +++ b/gluon/contrib/websocket_messaging.py @@ -65,6 +65,11 @@ Here is a complete sample web2py action: 'http://127.0.0.1:8888',form.vars.message,'mykey','mygroup') return form +https is possible too using 'https://127.0.0.1:8888' instead of 'http://127.0.0.1:8888', but need to +be started with + + python gluon/contrib/websocket_messaging.py -k mykey -p 8888 -s keyfile.pem -c certfile.pem + Acknowledgements: Tornado code inspired by http://thomas.pelletier.im/2010/08/websocket-tornado-redis/ @@ -194,6 +199,16 @@ if __name__ == "__main__": default=False, dest='tokens', help='require tockens to join') + parser.add_option('-s', + '--sslkey', + default=False, + dest='keyfile', + help='require ssl keyfile full path') + parser.add_option('-c', + '--sslcert', + default=False, + dest='certfile', + help='require ssl certfile full path') (options, args) = parser.parse_args() hmac_key = options.hmac_key DistributeHandler.tokens = options.tokens @@ -202,6 +217,10 @@ if __name__ == "__main__": (r'/token', TokenHandler), (r'/realtime/(.*)', DistributeHandler)] application = tornado.web.Application(urls, auto_reload=True) - http_server = tornado.httpserver.HTTPServer(application) + if options.keyfile and options.certfile: + ssl_options = dict(certfile=options.certfile, keyfile=options.keyfile) + else: + ssl_options = None + http_server = tornado.httpserver.HTTPServer(application, ssl_options=ssl_options) http_server.listen(int(options.port), address=options.address) tornado.ioloop.IOLoop.instance().start() From 11082987ea6b6849795a064f69c238d1f6b56962 Mon Sep 17 00:00:00 2001 From: Diogo Date: Sat, 27 Sep 2014 23:03:24 -0300 Subject: [PATCH 02/30] removing bug with return true in post Error with get replicate in post: http://stackoverflow.com/questions/19563093 --- gluon/contrib/websocket_messaging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gluon/contrib/websocket_messaging.py b/gluon/contrib/websocket_messaging.py index 72d53b35..81705620 100644 --- a/gluon/contrib/websocket_messaging.py +++ b/gluon/contrib/websocket_messaging.py @@ -117,7 +117,7 @@ class PostHandler(tornado.web.RequestHandler): return 'false' for client in listeners.get(group, []): client.write_message(message) - return 'true' + return None return 'false' @@ -137,7 +137,7 @@ class TokenHandler(tornado.web.RequestHandler): if not hmac.new(hmac_key, message).hexdigest() == signature: return 'false' tokens[message] = None - return 'true' + return None return 'false' From ae5069d9b17ba26dc280abb32e3a26f35949dfb0 Mon Sep 17 00:00:00 2001 From: Diogo Date: Tue, 30 Sep 2014 08:47:31 -0300 Subject: [PATCH 03/30] add example for websocket wss --- gluon/contrib/websocket_messaging.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gluon/contrib/websocket_messaging.py b/gluon/contrib/websocket_messaging.py index 81705620..1cd7e13f 100644 --- a/gluon/contrib/websocket_messaging.py +++ b/gluon/contrib/websocket_messaging.py @@ -68,7 +68,11 @@ Here is a complete sample web2py action: https is possible too using 'https://127.0.0.1:8888' instead of 'http://127.0.0.1:8888', but need to be started with - python gluon/contrib/websocket_messaging.py -k mykey -p 8888 -s keyfile.pem -c certfile.pem + python gluon/contrib/websocket_messaging.py -k mykey -p 8888 -s keyfile.pem -c certfile.pem + +for secure websocket do: + + web2py_websocket('wss://127.0.0.1:8888/realtime/mygroup',callback) Acknowledgements: Tornado code inspired by http://thomas.pelletier.im/2010/08/websocket-tornado-redis/ From 1e35262e6746dd74485587ddef25205573785ca9 Mon Sep 17 00:00:00 2001 From: niphlod Date: Tue, 30 Sep 2014 21:30:28 +0200 Subject: [PATCH 04/30] fixes issue 1980 pyflaked the module, at least a bit. Sometimes I feel developers are in a contest where less characters and lines win. Concise implementation wins over verbose one, but less SLOC DON'T mean somewhat better code. Just reading through this module makes my brain hurt a bit --- gluon/sqlhtml.py | 166 ++++++++++++++++++++++++++++------------------- 1 file changed, 99 insertions(+), 67 deletions(-) diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index 98d4395a..898e53fe 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -14,13 +14,18 @@ Holds: """ +import datetime +import urllib +import re +import cStringIO + import os -from gluon.http import HTTP -from gluon.html import XmlComponent -from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG, SCRIPT, P +from gluon.http import HTTP, redirect +from gluon.html import XmlComponent, truncate_string +from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG from gluon.html import FORM, INPUT, LABEL, OPTION, SELECT, COL, COLGROUP -from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, DEFAULT_PASSWORD_DISPLAY -from gluon.html import URL, truncate_string, FIELDSET +from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, SCRIPT +from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY from gluon.dal import DAL, Field from gluon.dal.base import DEFAULT from gluon.dal.objects import Table, Row, Expression @@ -29,16 +34,12 @@ from gluon.dal.helpers.methods import smart_query, bar_encode, sqlhtml_validator from gluon.dal.helpers.classes import Reference, SQLCustomType from gluon.storage import Storage from gluon.utils import md5_hash -from gluon.validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \ - IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE, IS_STRONG +from gluon.validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE +from gluon.validators import IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE +from gluon.validators import IS_STRONG import gluon.serializers as serializers -import datetime -import urllib -import re -import cStringIO from gluon.globals import current -from gluon.http import redirect try: import gluon.settings as settings @@ -49,7 +50,7 @@ widget_class = re.compile('^\w*') def add_class(a, b): - return a+' '+b if a else b + return a + ' ' + b if a else b def represent(field, value, record): @@ -85,11 +86,11 @@ def show_if(cond): if not cond: return None base = "%s_%s" % (cond.first.tablename, cond.first.name) - if ((cond.op.__name__ == 'EQ' and cond.second == True) or - (cond.op.__name__ == 'NE' and cond.second == False)): + if ((cond.op.__name__ == 'EQ' and cond.second is True) or + (cond.op.__name__ == 'NE' and cond.second is False)): return base, ":checked" - if ((cond.op.__name__ == 'EQ' and cond.second == False) or - (cond.op.__name__ == 'NE' and cond.second == True)): + if ((cond.op.__name__ == 'EQ' and cond.second is False) or + (cond.op.__name__ == 'NE' and cond.second is True)): return base, ":not(:checked)" if cond.op.__name__ == 'EQ': return base, "[value='%s']" % cond.second @@ -305,7 +306,8 @@ class ListWidget(StringWidget): _class = 'string' requires = field.requires if isinstance( field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None - if isinstance(value, str): value = [value] + if isinstance(value, str): + value = [value] nvalue = value or [''] items = [LI(INPUT(_id=_id, _class=_class, _name=_name, value=v, hideerror=k < len(nvalue) - 1, @@ -351,7 +353,6 @@ class RadioWidget(OptionsWidget): else: value = str(value) - attr = cls._attributes(field, {}, **attributes) attr['_class'] = add_class(attr.get('_class'), 'web2py_radiowidget') @@ -626,7 +627,7 @@ class AutocompleteWidget(object): self.help_fields = help_fields or [] self.help_string = help_string if self.help_fields and not self.help_string: - self.help_string = ' '.join('%%(%s)s'%f.name + self.help_string = ' '.join('%%(%s)s' % f.name for f in self.help_fields) self.request = request @@ -900,7 +901,7 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3): label['_for'] = None label.insert(0, controls) _controls = DIV(DIV(label, _help, _class="checkbox"), - _class="%s %s" % (offset_class, col_class)) + _class="%s %s" % (offset_class, col_class)) label = '' elif isinstance(controls, SELECT): controls.add_class('form-control') @@ -1132,7 +1133,7 @@ class SQLFORM(FORM): if fieldname.find('.') >= 0: continue - field = (self.table[fieldname] if fieldname in self.table.fields + field = (self.table[fieldname] if fieldname in self.table.fields else self.extra_fields[fieldname]) comment = None @@ -1184,7 +1185,7 @@ class SQLFORM(FORM): cond = readonly or \ (not ignore_rw and not field.writable and field.readable) - if default is not None and not cond: + if default is not None and not cond: default = field.formatter(default) dspval = default @@ -1192,10 +1193,6 @@ class SQLFORM(FORM): if cond: - # ## if field.re field.requires = sqlhtml_validators(field) field.requires = sqlhtml_validators(field)present is available else - # ## ignore blob and preview uploaded images - # ## format everything else - if field.represent: inp = represent(field, default, record) elif field.type in ['blob']: @@ -1272,7 +1269,7 @@ class SQLFORM(FORM): (olname.replace('.', '__') + SQLFORM.ID_ROW_SUFFIX, '', widget, col3.get(olname, ''))) self.custom.linkto[olname.replace('.', '__')] = widget -# + # # when deletable, add delete? checkbox self.custom.delete = self.custom.deletable = '' @@ -1291,16 +1288,15 @@ class SQLFORM(FORM): ) xfields.append( (self.FIELDKEY_DELETE_RECORD + SQLFORM.ID_ROW_SUFFIX, - LABEL( + LABEL( T(delete_label), sep, _for=self.FIELDKEY_DELETE_RECORD, - _id=self.FIELDKEY_DELETE_RECORD + \ - SQLFORM.ID_LABEL_SUFFIX), + _id=self.FIELDKEY_DELETE_RECORD + + SQLFORM.ID_LABEL_SUFFIX), widget, col3.get(self.FIELDKEY_DELETE_RECORD, ''))) self.custom.delete = self.custom.deletable = widget - # when writable, add submit button self.custom.submit = '' if not readonly: @@ -1480,7 +1476,7 @@ class SQLFORM(FORM): # that does not pass validation, yet it should be deleted for fieldname in self.fields: - field = (self.table[fieldname] + field = (self.table[fieldname] if fieldname in self.table.fields else self.extra_fields[fieldname]) ### this is a workaround! widgets should always have default not None! @@ -1995,6 +1991,18 @@ class SQLFORM(FORM): details = details and not groupby rows = None + # see issue 1980. Basically we can have keywords in get_vars + # (i.e. when the search term is propagated through page=2&keywords=abc) + # but if there is keywords in post_vars (i.e. POSTing a search request) + # the one in get_vars should be replaced by the new one + keywords = '' + if 'keywords' in request.post_vars: + keywords = request.post_vars.keywords + elif 'keywords' in request.get_vars: + keywords = request.get_vars.keywords + + print 'keyworkds are', keywords + def fetch_count(dbset): ##FIXME for google:datastore cache_count is ignored ## if it's not an integer @@ -2051,7 +2059,7 @@ class SQLFORM(FORM): '/'.join(str(a) for a in args) == '/'.join(request.args) or URL.verify(request, user_signature=user_signature, hash_vars=False) or - (request.args(len(args))=='view' and not logged)): + (request.args(len(args))=='view' and not logged)): session.flash = T('not authorized') redirect(referrer) @@ -2096,8 +2104,8 @@ class SQLFORM(FORM): else: fields = [] columns = [] - filter1 = lambda f:isinstance(f, Field) - filter2 = lambda f:isinstance(f, Field) and f.readable + filter1 = lambda f: isinstance(f, Field) + filter2 = lambda f: isinstance(f, Field) and f.readable for table in tables: fields += filter(filter1, table) columns += filter(filter2, table) @@ -2111,15 +2119,18 @@ class SQLFORM(FORM): if groupby is None: field_id = tables[0]._id elif groupby and isinstance(groupby, Field): - field_id = groupby #take the field passed as groupby + #take the field passed as groupby + field_id = groupby elif groupby and isinstance(groupby, Expression): - field_id = groupby.first #take the first groupby field - while not(isinstance(field_id, Field)): # Navigate to the first Field of the expression + #take the first groupby field + field_id = groupby.first + while not(isinstance(field_id, Field)): + # Navigate to the first Field of the expression field_id = field_id.first table = field_id.table tablename = table._tablename if not any(str(f) == str(field_id) for f in fields): - fields = [f for f in fields]+[field_id] + fields = [f for f in fields] + [field_id] if upload == '': upload = lambda filename: url(args=['download', filename]) if request.args(-2) == 'download': @@ -2283,8 +2294,10 @@ class SQLFORM(FORM): expcolumns = [str(f) for f in columns] selectable_columns = [str(f) for f in columns if not isinstance(f, Field.Virtual)] if export_type.endswith('with_hidden_cols'): - #expcolumns = [] start with the visible columns, which includes visible virtual fields - selectable_columns = [] #like expcolumns but excluding virtual + # expcolumns = [] start with the visible columns, which + # includes visible virtual fields + selectable_columns = [] + #like expcolumns but excluding virtual for table in tables: for field in table: if field.readable and field.tablename in tablenames: @@ -2292,19 +2305,21 @@ class SQLFORM(FORM): expcolumns.append(str(field)) if not(isinstance(field, Field.Virtual)): selectable_columns.append(str(field)) - #look for virtual fields not displayed (and virtual method fields to be added here?) + #look for virtual fields not displayed (and virtual method + #fields to be added here?) for (field_name, field) in table.iteritems(): if isinstance(field, Field.Virtual) and not str(field) in expcolumns: expcolumns.append(str(field)) if export_type in exportManager and exportManager[export_type]: - if request.vars.keywords: + if keywords: try: - #the query should be constructed using searchable fields but not virtual fields + #the query should be constructed using searchable + #fields but not virtual fields sfields = reduce(lambda a, b: a + b, [[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables]) dbset = dbset(SQLFORM.build_query( - sfields, request.vars.get('keywords', ''))) + sfields, keywords)) rows = dbset.select(left=left, orderby=orderby, cacheable=True, *selectable_columns) except Exception, e: @@ -2316,7 +2331,8 @@ class SQLFORM(FORM): value = exportManager[export_type] clazz = value[0] if hasattr(value, '__getitem__') else value - rows.colnames = expcolumns # expcolumns is all cols to be exported including virtual fields + # expcolumns is all cols to be exported including virtual fields + rows.colnames = expcolumns oExp = clazz(rows) filename = '.'.join(('rows', oExp.file_ext)) response.headers['Content-Type'] = oExp.content_type @@ -2330,8 +2346,7 @@ class SQLFORM(FORM): elif not request.vars.records: request.vars.records = [] - session['_web2py_grid_referrer_' + formname] = \ - url2(vars=request.get_vars) + session['_web2py_grid_referrer_' + formname] = url2(vars=request.get_vars) console = DIV(_class='web2py_console %(header)s %(cornertop)s' % ui) error = None if create: @@ -2357,7 +2372,7 @@ class SQLFORM(FORM): sfields_id = '%s_query_panel' % prefix skeywords_id = '%s_keywords' % prefix search_widget = lambda sfield, url: CAT(FORM( - INPUT(_name='keywords', _value=request.vars.keywords, + INPUT(_name='keywords', _value=keywords, _id=skeywords_id,_class='form-control', _onfocus="jQuery('#%s').change();jQuery('#%s').slideDown();" % (spanel_id, sfields_id) if advanced_search else '' ), @@ -2368,7 +2383,6 @@ class SQLFORM(FORM): form = search_widget and search_widget(sfields, url()) or '' console.append(add) console.append(form) - keywords = request.vars.get('keywords', '') try: if callable(searchable): subquery = searchable(sfields, keywords) @@ -2428,7 +2442,7 @@ class SQLFORM(FORM): elif key == ordermatch[1:]: marker = sorter_icons[1] header = A(header, marker, _href=url(vars=dict( - keywords=request.vars.keywords or '', + keywords=keywords, order=key)), cid=request.cid) headcols.append(TH(header, _class=ui.get('default'))) @@ -2466,17 +2480,22 @@ class SQLFORM(FORM): if paginate and dbset._db._adapter.dbengine == 'google:datastore': cursor = request.vars.cursor or True limitby = (0, paginate) - try: page = int(request.vars.page or 1)-1 - except ValueError: page = 0 - elif paginate and paginate NPAGES + 1: @@ -2660,7 +2691,8 @@ class SQLFORM(FORM): _style='width:100%;overflow-x:auto;-ms-overflow-x:scroll') if selectable: if not callable(selectable): - #now expect that selectable and related parameters are iterator (list, tuple, etc) + #now expect that selectable and related parameters are + #iterator (list, tuple, etc) inputs = [] for i, submit_info in enumerate(selectable): submit_text = submit_info[0] @@ -2708,7 +2740,7 @@ class SQLFORM(FORM): link = url2(vars=dict( order=request.vars.order or '', _export_type=k, - keywords=request.vars.keywords or '')) + keywords=keywords or '')) export_links.append(A(T(label), _href=link, _title=title, _class='btn btn-default')) export_menu = \ DIV(T('Export:'), _class="w2p_export_menu", *export_links) @@ -2797,7 +2829,7 @@ class SQLFORM(FORM): elif callable(table._format): return table._format(row) else: - return '#'+str(row.id) + return '#' + str(row.id) try: nargs = len(args) + 1 previous_tablename, previous_fieldname, previous_id = \ @@ -2876,7 +2908,7 @@ class SQLFORM(FORM): opts = [OPTION(T('References')+':', _value='')] linked = [] if linked_tables: - for item in linked_tables: + for item in linked_tables: tb = None if isinstance(item, Table) and item._tablename in check: tablename = item._tablename From 88113637aecc8bf9fe3744bf9e34f5c4f31e8a9b Mon Sep 17 00:00:00 2001 From: niphlod Date: Tue, 30 Sep 2014 23:37:54 +0200 Subject: [PATCH 05/30] with dal new structure, API doc generation was left out of the picture. GROAN! --- docs/dal.adapters.rst | 150 +++++++++++++++++++++++++++++++++++ docs/dal.helpers.rst | 38 +++++++++ docs/dal.rst | 42 +++++++++- gluon/dal/adapters/google.py | 30 +++---- gluon/dal/adapters/imap.py | 51 ++++++------ 5 files changed, 270 insertions(+), 41 deletions(-) create mode 100644 docs/dal.adapters.rst create mode 100644 docs/dal.helpers.rst diff --git a/docs/dal.adapters.rst b/docs/dal.adapters.rst new file mode 100644 index 00000000..bef54380 --- /dev/null +++ b/docs/dal.adapters.rst @@ -0,0 +1,150 @@ +gluon.dal.adapters package +========================== + +Submodules +---------- + +gluon.dal.adapters.base module +------------------------------ + +.. automodule:: gluon.dal.adapters.base + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.couchdb module +--------------------------------- + +.. automodule:: gluon.dal.adapters.couchdb + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.cubrid module +-------------------------------- + +.. automodule:: gluon.dal.adapters.cubrid + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.db2 module +----------------------------- + +.. automodule:: gluon.dal.adapters.db2 + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.firebird module +---------------------------------- + +.. automodule:: gluon.dal.adapters.firebird + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.google module +-------------------------------- + +.. automodule:: gluon.dal.adapters.google + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.imap module +------------------------------ + +.. automodule:: gluon.dal.adapters.imap + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.informix module +---------------------------------- + +.. automodule:: gluon.dal.adapters.informix + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.ingres module +-------------------------------- + +.. automodule:: gluon.dal.adapters.ingres + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.mongo module +------------------------------- + +.. automodule:: gluon.dal.adapters.mongo + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.mssql module +------------------------------- + +.. automodule:: gluon.dal.adapters.mssql + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.mysql module +------------------------------- + +.. automodule:: gluon.dal.adapters.mysql + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.oracle module +-------------------------------- + +.. automodule:: gluon.dal.adapters.oracle + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.postgre module +--------------------------------- + +.. automodule:: gluon.dal.adapters.postgre + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.sapdb module +------------------------------- + +.. automodule:: gluon.dal.adapters.sapdb + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.sqlite module +-------------------------------- + +.. automodule:: gluon.dal.adapters.sqlite + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.adapters.teradata module +---------------------------------- + +.. automodule:: gluon.dal.adapters.teradata + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: gluon.dal.adapters + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/dal.helpers.rst b/docs/dal.helpers.rst new file mode 100644 index 00000000..bc146429 --- /dev/null +++ b/docs/dal.helpers.rst @@ -0,0 +1,38 @@ +gluon.dal.helpers package +========================= + +Submodules +---------- + +gluon.dal.helpers.classes module +-------------------------------- + +.. automodule:: gluon.dal.helpers.classes + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.helpers.methods module +-------------------------------- + +.. automodule:: gluon.dal.helpers.methods + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.helpers.regex module +------------------------------ + +.. automodule:: gluon.dal.helpers.regex + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: gluon.dal.helpers + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/dal.rst b/docs/dal.rst index 5208ed87..90c1564e 100644 --- a/docs/dal.rst +++ b/docs/dal.rst @@ -1,6 +1,44 @@ +gluon.dal package +================= -:mod:`dal` Module ------------------ +Subpackages +----------- + +.. toctree:: + + dal.adapters + dal.helpers + +Submodules +---------- + +gluon.dal.base module +--------------------- + +.. automodule:: gluon.dal.base + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.connection module +--------------------------- + +.. automodule:: gluon.dal.connection + :members: + :undoc-members: + :show-inheritance: + +gluon.dal.objects module +------------------------ + +.. automodule:: gluon.dal.objects + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- .. automodule:: gluon.dal :members: diff --git a/gluon/dal/adapters/google.py b/gluon/dal/adapters/google.py index ea64ed87..86c3ef3c 100644 --- a/gluon/dal/adapters/google.py +++ b/gluon/dal/adapters/google.py @@ -515,24 +515,26 @@ class GoogleDatastoreAdapter(NoSQLAdapter): """ This is the GAE version of select. Some notes to consider: - db['_lastsql'] is not set because there is not SQL statement string - for a GAE query + for a GAE query - 'nativeRef' is a magical fieldname used for self references on GAE - optional attribute 'projection' when set to True will trigger - use of the GAE projection queries. note that there are rules for - what is accepted imposed by GAE: each field must be indexed, - projection queries cannot contain blob or text fields, and you - cannot use == and also select that same field. see https://developers.google.com/appengine/docs/python/datastore/queries#Query_Projection + use of the GAE projection queries. note that there are rules for + what is accepted imposed by GAE: each field must be indexed, + projection queries cannot contain blob or text fields, and you + cannot use == and also select that same field. + see https://developers.google.com/appengine/docs/python/datastore/queries#Query_Projection - optional attribute 'filterfields' when set to True web2py will only - parse the explicitly listed fields into the Rows object, even though - all fields are returned in the query. This can be used to reduce - memory usage in cases where true projection queries are not - usable. + parse the explicitly listed fields into the Rows object, even though + all fields are returned in the query. This can be used to reduce + memory usage in cases where true projection queries are not + usable. - optional attribute 'reusecursor' allows use of cursor with queries - that have the limitby attribute. Set the attribute to True for the - first query, set it to the value of db['_lastcursor'] to continue - a previous query. The user must save the cursor value between - requests, and the filters must be identical. It is up to the user - to follow google's limitations: https://developers.google.com/appengine/docs/python/datastore/queries#Query_Cursors + that have the limitby attribute. Set the attribute to True for the + first query, set it to the value of db['_lastcursor'] to continue + a previous query. The user must save the cursor value between + requests, and the filters must be identical. It is up to the user + to follow google's limitations: + https://developers.google.com/appengine/docs/python/datastore/queries#Query_Cursors """ (items, tablename, fields) = self.select_raw(query,fields,attributes) diff --git a/gluon/dal/adapters/imap.py b/gluon/dal/adapters/imap.py index 922698c4..a63ac61b 100644 --- a/gluon/dal/adapters/imap.py +++ b/gluon/dal/adapters/imap.py @@ -12,7 +12,6 @@ from .base import NoSQLAdapter class IMAPAdapter(NoSQLAdapter): - drivers = ('imaplib',) """ IMAP server adapter @@ -44,29 +43,31 @@ class IMAPAdapter(NoSQLAdapter): Here is a list of supported fields: - Field Type Description - ################################################################ - uid string - answered boolean Flag - created date - content list:string A list of dict text or html parts - to string - cc string - bcc string - size integer the amount of octets of the message* - deleted boolean Flag - draft boolean Flag - flagged boolean Flag - sender string - recent boolean Flag - seen boolean Flag - subject string - mime string The mime header declaration - email string The complete RFC822 message** - attachments Each non text part as dict - encoding string The main detected encoding - - *At the application side it is measured as the length of the RFC822 + =========== ============== =========== + Field Type Description + =========== ============== =========== + uid string + answered boolean Flag + created date + content list:string A list of dict text or html parts + to string + cc string + bcc string + size integer the amount of octets of the message* + deleted boolean Flag + draft boolean Flag + flagged boolean Flag + sender string + recent boolean Flag + seen boolean Flag + subject string + mime string The mime header declaration + email string The complete RFC822 message (*) + attachments list Each non text part as dict + encoding string The main detected encoding + =========== ============== =========== + + (*) At the application side it is measured as the length of the RFC822 message string WARNING: As row id's are mapped to email sequence numbers, @@ -141,7 +142,7 @@ class IMAPAdapter(NoSQLAdapter): imapdb(q).select(imapdb.INBOX.sender, imapdb.INBOX.subject) """ - + drivers = ('imaplib',) types = { 'string': str, 'text': str, From 16da2edc6d3879159a82210ad7e1bca6df75a5f3 Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 1 Oct 2014 08:17:12 -0300 Subject: [PATCH 06/30] removing returns and added 401 error send to user --- gluon/contrib/websocket_messaging.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/gluon/contrib/websocket_messaging.py b/gluon/contrib/websocket_messaging.py index 1cd7e13f..72c6f186 100644 --- a/gluon/contrib/websocket_messaging.py +++ b/gluon/contrib/websocket_messaging.py @@ -110,7 +110,7 @@ class PostHandler(tornado.web.RequestHandler): """ def post(self): if hmac_key and not 'signature' in self.request.arguments: - return 'false' + self.send_error(401) if 'message' in self.request.arguments: message = self.request.arguments['message'][0] group = self.request.arguments.get('group', ['default'])[0] @@ -118,11 +118,9 @@ class PostHandler(tornado.web.RequestHandler): if hmac_key: signature = self.request.arguments['signature'][0] if not hmac.new(hmac_key, message).hexdigest() == signature: - return 'false' + self.send_error(401) for client in listeners.get(group, []): client.write_message(message) - return None - return 'false' class TokenHandler(tornado.web.RequestHandler): @@ -133,16 +131,14 @@ class TokenHandler(tornado.web.RequestHandler): """ def post(self): if hmac_key and not 'message' in self.request.arguments: - return 'false' + self.send_error(401) if 'message' in self.request.arguments: message = self.request.arguments['message'][0] if hmac_key: signature = self.request.arguments['signature'][0] if not hmac.new(hmac_key, message).hexdigest() == signature: - return 'false' + self.send_error(401) tokens[message] = None - return None - return 'false' class DistributeHandler(tornado.websocket.WebSocketHandler): @@ -178,6 +174,13 @@ class DistributeHandler(tornado.websocket.WebSocketHandler): client.write_message('-' + self.name) print '%s:DISCONNECT from %s' % (time.time(), self.group) + #if your webserver is different from tornado server uncomment this + #or override using something more restrictive: + #http://tornado.readthedocs.org/en/latest/websocket.html#tornado.websocket.WebSocketHandler.check_origin + #def check_origin(self, origin): + # return True + + if __name__ == "__main__": usage = __doc__ version = "" From 97e1d1cd9b60e50341176b741c1d8fb4625d27ec Mon Sep 17 00:00:00 2001 From: niphlod Date: Wed, 1 Oct 2014 22:01:37 +0200 Subject: [PATCH 07/30] added coverage template in makefile --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index fb0a6590..8bf38b2c 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,13 @@ epydoc: cp applications/examples/static/title.png applications/examples/static/epydoc tests: python web2py.py --run_system_tests +coverage: + coverage erase --rcfile=gluon/tests/coverage.ini + export COVERAGE_PROCESS_START=gluon/tests/coverage.ini + python web2py.py --run_system_tests --with_coverage + coverage combine --rcfile=gluon/tests/coverage.ini + sleep 1 + coverage html --rcfile=gluon/tests/coverage.ini update: wget -O gluon/contrib/feedparser.py http://feedparser.googlecode.com/svn/trunk/feedparser/feedparser.py wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py From b36ab988ccc52183c94629a5aaa32ebf4ca2d129 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 6 Oct 2014 21:24:44 -0500 Subject: [PATCH 08/30] updated scripts/setup-web2py-centos7.sh --- scripts/setup-web2py-centos7.sh | 302 ++++++++++++++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 scripts/setup-web2py-centos7.sh diff --git a/scripts/setup-web2py-centos7.sh b/scripts/setup-web2py-centos7.sh new file mode 100644 index 00000000..340ac05d --- /dev/null +++ b/scripts/setup-web2py-centos7.sh @@ -0,0 +1,302 @@ +echo "This script will: +1) Install modules needed to run web2py on Fedora and CentOS/RHEL +2) Install Python 2.6 to /opt and recompile wsgi if not provided +2) Install web2py in /opt/web-apps/ +3) Configure SELinux and iptables +5) Create a self signed ssl certificate +6) Setup web2py with mod_wsgi +7) Create virtualhost entries so that web2py responds for '/' +8) Restart Apache. + +You should probably read this script before running it. + +Although SELinux permissions changes have been made, +further SELinux changes will be required for your personal +apps. (There may also be additional changes required for the +bundled apps.) As a last resort, SELinux can be disabled. + +A simple iptables configuration has been applied. You may +want to review it to verify that it meets your needs. + +Finally, if you require a proxy to access the Internet, please +set up your machine to do so before running this script. + +(author: berubejd) + +Press ENTER to continue...[ctrl+C to abort]" + +read CONFIRM + +#!/bin/bash + +# (modified for centos7: Dragan (spamperakojotgenije@gmail.com) + +### +### Phase 0 - This may get messy. Lets work from a temporary directory +### + +current_dir=`pwd` + +if [ -d /tmp/setup-web2py/ ]; then + mv /tmp/setup-web2py/ /tmp/setup-web2py.old/ +fi + +mkdir -p /tmp/setup-web2py +cd /tmp/setup-web2py + +### +### Phase 1 - Requirements installation +### + +echo +echo " - Installing packages" +echo + +# Verify packages are up to date +yum update + +# Install required packages +yum install httpd mod_ssl mod_wsgi wget python + +### +### Phase 2 - Install web2py +### + +echo +echo " - Downloading, installing, and starting web2py" +echo + +# Create web-apps directory, if required +if [ ! -d "/opt/web-apps" ]; then + mkdir -p /opt/web-apps + + chmod 755 /opt + chmod 755 /opt/web-apps +fi + +cd /opt/web-apps + +# Download web2py +if [ -e web2py_src.zip* ]; then + rm web2py_src.zip* +fi + +wget http://web2py.com/examples/static/web2py_src.zip +unzip web2py_src.zip +mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py +chown -R apache:apache web2py + +### +### Phase 3 - Setup SELinux context +### +### SELinux doesn't behave well with web2py, for details +### see https://groups.google.com/forum/?fromgroups#!searchin/web2py/selinux/web2py/_thPGA9YhK4/dSnvF3D_lswJ +### +### For now you'll have to disable SELinux + + +# Allow http_tmp_exec required for wsgi +RETV=`setsebool -P httpd_tmp_exec on > /dev/null 2>&1; echo $?` +if [ ! ${RETV} -eq 0 ]; then + # CentOS doesn't support httpd_tmp_exec + cd /tmp/setup-web2py + + # Create the SELinux policy +cat > httpd.te < /etc/httpd/ssl/self_signed.key +openssl req -new -x509 -nodes -sha1 -days 365 -key /etc/httpd/ssl/self_signed.key > /etc/httpd/ssl/self_signed.cert +openssl x509 -noout -fingerprint -text < /etc/httpd/ssl/self_signed.cert > /etc/httpd/ssl/self_signed.info + +chmod 400 /etc/httpd/ssl/self_signed.* + +### +### Phase 6 - Configure Apache +### + +echo +echo " - Configure Apache to use mod_wsgi" +echo + +# Create config +if [ -e /etc/httpd/conf.d/welcome.conf ]; then + mv /etc/httpd/conf.d/welcome.conf /etc/httpd/conf.d/welcome.conf.disabled +fi + +cat > /etc/httpd/conf.d/default.conf < + WSGIDaemonProcess web2py user=apache group=apache processes=1 threads=1 + WSGIProcessGroup web2py + WSGIScriptAlias / /opt/web-apps/web2py/wsgihandler.py + WSGIPassAuthorization On + + + AllowOverride None + Order Allow,Deny + Deny from all + + Require all granted + Allow from all + + + + AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) /opt/web-apps/web2py/applications/\$1/static/\$2 + + + Options -Indexes + Order Allow,Deny + Allow from all + Require all granted + + + + Deny from all + + + + Deny from all + + + CustomLog /var/log/httpd/access_log common + ErrorLog /var/log/httpd/error_log + + + + SSLEngine on + SSLCertificateFile /etc/httpd/ssl/self_signed.cert + SSLCertificateKeyFile /etc/httpd/ssl/self_signed.key + + WSGIProcessGroup web2py + WSGIScriptAlias / /opt/web-apps/web2py/wsgihandler.py + WSGIPassAuthorization On + + + AllowOverride None + Order Allow,Deny + Deny from all + + Require all granted + Allow from all + + + + AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) /opt/web-apps/web2py/applications/\$1/static/\$2 + + + Options -Indexes + ExpiresActive On + ExpiresDefault "access plus 1 hour" + Order Allow,Deny + Allow from all + Require all granted + + + CustomLog /var/log/httpd/access_log common + ErrorLog /var/log/httpd/error_log + + +EOF + +# Fix wsgi socket locations +echo "WSGISocketPrefix run/wsgi" >> /etc/httpd/conf.d/wsgi.conf + +# Restart Apache to pick up changes +systemctl restart httpd.service + +### +### Phase 7 - Setup web2py admin password +### + +echo +echo " - Setup web2py admin password" +echo + +cd /opt/web-apps/web2py +sudo -u apache python -c "from gluon.main import save_password; save_password(raw_input('admin password: '),443)" + +### +### Phase 8 - Verify that required services start at boot +### + +/sbin/chkconfig iptables on +/sbin/chkconfig httpd on + +### +### Phase 999 - Done! +### + +# Change back to original directory +cd ${current_directory} + +echo " - Complete!" +echo From 4cf878c9f7a19241de22121447336666aac2b6be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonel=20C=C3=A2mara?= Date: Tue, 7 Oct 2014 15:15:08 +0100 Subject: [PATCH 09/30] Change reset_password to send the password as GET query var instead of as an argument. Added some more Portuguese Translations to the Admin application. --- applications/admin/languages/pt.py | 76 +++++++++++++++++++++++++----- gluon/tools.py | 6 +-- 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/applications/admin/languages/pt.py b/applications/admin/languages/pt.py index da379dc4..3f850c3c 100644 --- a/applications/admin/languages/pt.py +++ b/applications/admin/languages/pt.py @@ -7,8 +7,10 @@ '%s %%{row} updated': '%s registros atualizados', '%Y-%m-%d': '%d/%m/%Y', '%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S', -'(requires internet access)': '(requer acesso a internet)', +'(requires internet access)': '(requer acesso à internet)', +'(requires internet access, experimental)': '(requer acesso à internet, experimental)', '(something like "it-it")': '(algo como "it-it")', +'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)', '@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page', '@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files', 'A new version of web2py is available': 'Está disponível uma nova versão do web2py', @@ -16,7 +18,7 @@ 'About': 'sobre', 'About application': 'Sobre a aplicação', 'additional code for your application': 'código adicional para sua aplicação', -'Additional code for your application': 'Additional code for your application', +'Additional code for your application': 'Código adicional para a sua aplicação', 'admin disabled because no admin password': ' admin desabilitado por falta de senha definida', 'admin disabled because not supported on google app engine': 'admin dehabilitado, não é soportado no GAE', 'admin disabled because unable to access password file': 'admin desabilitado, não foi possível ler o arquivo de senha', @@ -33,6 +35,8 @@ 'application compiled': 'aplicação compilada', 'application is compiled and cannot be designed': 'A aplicação está compilada e não pode ser modificada', 'Application name:': 'Nome da aplicação:', +'are not used': 'não usadas', +'are not used yet': 'ainda não usadas', 'Are you sure you want to delete file "%s"?': 'Tem certeza que deseja apagar o arquivo "%s"?', 'Are you sure you want to delete plugin "%s"?': 'Tem certeza que deseja apagar o plugin "%s"?', 'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?', @@ -43,17 +47,20 @@ 'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENÇÃO o login requer uma conexão segura (HTTPS) ou executar de localhost.', 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENÇÃO OS TESTES NÃO THREAD SAFE, NÃO EFETUE MÚLTIPLOS TESTES AO MESMO TEMPO.', 'ATTENTION: you cannot edit the running application!': 'ATENÇÃO: Não pode modificar a aplicação em execução!', +'Autocomplete Python Code': 'Autocompletar Código Python', 'Available databases and tables': 'Bancos de dados e tabelas disponíveis', 'back': 'voltar', 'browse': 'buscar', 'cache': 'cache', 'cache, errors and sessions cleaned': 'cache, erros e sessões eliminadas', +'can be a git repo': 'can be a git repo', 'Cannot be empty': 'Não pode ser vazio', 'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Não é possível compilar: Existem erros em sua aplicação. Depure, corrija os errros e tente novamente', 'Cannot compile: there are errors in your app:': 'Não é possível compilar: Existem erros em sua aplicação', 'cannot create file': 'Não é possível criar o arquivo', 'cannot upload file "%(filename)s"': 'não é possível fazer upload do arquivo "%(filename)s"', 'Change admin password': 'mudar senha de administrador', +'change editor settings': 'mudar definições do editor', 'Change Password': 'Trocar Senha', 'check all': 'marcar todos', 'Check for upgrades': 'checar por atualizações', @@ -67,7 +74,7 @@ 'click to open': 'clique para abrir', 'Client IP': 'IP do cliente', 'code': 'código', -'collapse/expand all': 'collapse/expand all', +'collapse/expand all': 'colapsar/expandir tudo', 'commit (mercurial)': 'commit (mercurial)', 'Compile': 'compilar', 'compiled application removed': 'aplicação compilada removida', @@ -79,6 +86,7 @@ 'Create new application using the Wizard': 'Criar nova aplicação utilizando o assistente', 'create new application:': 'nome da nova aplicação:', 'Create new simple application': 'Crie uma nova aplicação', +'Create/Upload': 'Create/Upload', 'created by': 'criado por', 'crontab': 'crontab', 'Current request': 'Requisição atual', @@ -99,18 +107,24 @@ 'delete': 'apagar', 'delete all checked': 'apagar marcados', 'delete plugin': 'apagar plugin', +'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)', 'Delete:': 'Apague:', 'Deploy': 'publicar', 'Deploy on Google App Engine': 'Publicar no Google App Engine', +'Deploy to OpenShift': 'Deploy to OpenShift', 'Description': 'Descrição', -'DESIGN': 'Projeto', 'design': 'modificar', +'DESIGN': 'Projeto', 'Design for': 'Projeto de', 'Detailed traceback description': 'Detailed traceback description', 'direction: ltr': 'direção: ltr', +'Disable': 'Disable', +'docs': 'docs', 'done!': 'feito!', 'download layouts': 'download layouts', +'Download layouts from repository': 'Download layouts from repository', 'download plugins': 'download plugins', +'Download plugins from repository': 'Download plugins from repository', 'E-mail': 'E-mail', 'EDIT': 'EDITAR', 'Edit': 'editar', @@ -119,6 +133,7 @@ 'Edit current record': 'Editar o registro atual', 'Edit Profile': 'Editar Perfil', 'edit views:': 'editar visões:', +'Editing %s': 'A Editar %s', 'Editing file': 'Editando arquivo', 'Editing file "%s"': 'Editando arquivo "%s"', 'Editing Language file': 'Editando arquivo de linguagem', @@ -129,6 +144,8 @@ 'Error ticket': 'Error ticket', 'Errors': 'erros', 'Exception instance attributes': 'Atributos da instancia de excessão', +'Exit Fullscreen': 'Sair de Ecrã Inteiro', +'Expand Abbreviation (html files only)': 'Expandir Abreviação (só para ficheiros html)', 'export as csv file': 'exportar como arquivo CSV', 'exposes': 'expõe', 'extends': 'estende', @@ -144,20 +161,24 @@ 'file does not exist': 'arquivo não existe', 'file saved on %(time)s': 'arquivo salvo em %(time)s', 'file saved on %s': 'arquivo salvo em %s', -'filter': 'filter', +'filter': 'filtro', +'Find Next': 'Localizar Seguinte', +'Find Previous': 'Localizar Anterior', 'First name': 'Nome', 'Frames': 'Frames', 'Functions with no doctests will result in [passed] tests.': 'Funções sem doctests resultarão em testes [aceitos].', +'graph model': 'graph model', 'Group ID': 'ID do Grupo', 'Hello World': 'Olá Mundo', 'Help': 'ajuda', +'Hide/Show Translated strings': '', 'htmledit': 'htmledit', -'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Se o relatório acima contém um número de ticket, isso indica uma falha no controlador em execução, antes de tantar executar os doctests. Isto acontece geralmente por erro de endentação ou erro fora do código da função.\nO titulo em verde indica que os testes (se definidos) passaram. Neste caso os testes não são mostrados.', +'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Se o relatório acima contém um número de ticket, isso indica uma falha no controlador em execução, antes de tantar executar os doctests. Isto acontece geralmente por erro de endentação ou erro fora do código da função.\r\nO titulo em verde indica que os testes (se definidos) passaram. Neste caso os testes não são mostrados.', 'Import/Export': 'Importar/Exportar', 'includes': 'inclui', 'insert new': 'inserir novo', 'insert new %s': 'inserir novo %s', -'inspect attributes': 'inspect attributes', +'inspect attributes': 'inspecionar atributos', 'Install': 'instalar', 'Installed applications': 'Aplicações instaladas', 'internal error': 'erro interno', @@ -168,6 +189,7 @@ 'Invalid Query': 'Consulta inválida', 'invalid request': 'solicitação inválida', 'invalid ticket': 'ticket inválido', +'Keyboard shortcuts': 'Atalhos de teclado', 'language file "%(filename)s" created/updated': 'arquivo de linguagem "%(filename)s" criado/atualizado', 'Language files (static strings) updated': 'Arquivos de linguagem (textos estáticos) atualizados', 'languages': 'linguagens', @@ -178,11 +200,12 @@ 'License for': 'Licença para', 'loading...': 'carregando...', 'locals': 'locals', -'login': 'inicio de sessão', 'Login': 'Entrar', +'login': 'inicio de sessão', 'Login to the Administrative Interface': 'Entrar na interface adminitrativa', 'Logout': 'finalizar sessão', 'Lost Password': 'Senha perdida', +'Manage': 'Manage', 'manage': 'gerenciar', 'merge': 'juntar', 'Models': 'Modelos', @@ -200,7 +223,10 @@ 'NO': 'NÃO', 'No databases in this application': 'Não existem bancos de dados nesta aplicação', 'no match': 'não encontrado', -'no package selected': 'no package selected', +'no package selected': 'nenhum pacote selecionado', +'online designer': 'online designer', +'or alternatively': 'or alternatively', +'Or Get from URL:': 'Ou Obtenha do URL:', 'or import from csv file': 'ou importar de um arquivo CSV', 'or provide app url:': 'ou forneça a url de uma aplicação:', 'or provide application url:': 'ou forneça a url de uma aplicação:', @@ -209,6 +235,7 @@ 'Overwrite installed app': 'sobrescrever aplicação instalada', 'Pack all': 'criar pacote', 'Pack compiled': 'criar pacote compilado', +'Pack custom': 'Pack custom', 'pack plugin': 'empacotar plugin', 'PAM authenticated user, cannot change password here': 'usuario autenticado por PAM, não pode alterar a senha por aqui', 'Password': 'Senha', @@ -218,16 +245,23 @@ 'Plugin "%s" in application': 'Plugin "%s" na aplicação', 'plugins': 'plugins', 'Plugins': 'Plugins', +'Plural-Forms:': 'Plural-Forms:', 'Powered by': 'Este site utiliza', 'previous 100 rows': '100 registros anteriores', +'Private files': 'Private files', +'private files': 'private files', 'Query:': 'Consulta:', +'Rapid Search': 'Rapid Search', 'record': 'registro', 'record does not exist': 'o registro não existe', 'record id': 'id do registro', 'Record ID': 'ID do Registro', 'Register': 'Registrar-se', 'Registration key': 'Chave de registro', +'Reload routes': 'Reload routes', 'Remove compiled': 'eliminar compilados', +'Replace': 'Substituir', +'Replace All': 'Substituir Tudo', 'request': 'request', 'Resolve Conflict file': 'Arquivo de resolução de conflito', 'response': 'response', @@ -236,7 +270,14 @@ 'Role': 'Papel', 'Rows in table': 'Registros na tabela', 'Rows selected': 'Registros selecionados', +'rules are not defined': 'rules are not defined', +"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': 'A correr em %s', +'Save': 'Save', 'save': 'salvar', +'Save file:': 'Gravar ficheiro:', +'Save file: %s': 'Gravar ficheiro: %s', +'Save via Ajax': 'Gravar via Ajax', 'Saved file hash:': 'Hash do arquivo salvo:', 'selected': 'selecionado(s)', 'session': 'session', @@ -244,10 +285,13 @@ 'shell': 'Terminal', 'Site': 'site', 'some files could not be removed': 'alguns arquicos não puderam ser removidos', +'Start searching': 'Start searching', 'Start wizard': 'iniciar assistente', 'state': 'estado', +'Static': 'Static', 'static': 'estáticos', 'Static files': 'Arquivos estáticos', +'Submit': 'Submit', 'submit': 'enviar', 'Sure you want to delete this object?': 'Tem certeza que deseja apaagr este objeto?', 'table': 'tabela', @@ -255,21 +299,23 @@ 'test': 'testar', 'Testing application': 'Testando a aplicação', 'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "consulta" é uma condição como "db.tabela.campo1==\'valor\'". Algo como "db.tabela1.campo1==db.tabela2.campo2" resulta em um JOIN SQL.', -'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller', 'the application logic, each URL path is mapped in one exposed function in the controller': 'A lógica da aplicação, cada URL é mapeada para uma função exposta pelo controlador', -'The data representation, define database tables and sets': 'The data representation, define database tables and sets', +'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller', 'the data representation, define database tables and sets': 'A representação dos dadps, define tabelas e estruturas de dados', +'The data representation, define database tables and sets': 'The data representation, define database tables and sets', 'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates', 'the presentations layer, views are also known as templates': 'A camada de apresentação, As visões também são chamadas de templates', 'There are no controllers': 'Não existem controllers', 'There are no models': 'Não existem modelos', 'There are no modules': 'Não existem módulos', 'There are no plugins': 'There are no plugins', +'There are no private files': '', 'There are no static files': 'Não existem arquicos estáticos', 'There are no translators, only default language is supported': 'Não há traduções, somente a linguagem padrão é suportada', 'There are no views': 'Não existem visões', -'these files are served without processing, your images go here': 'Estes arquivos são servidos sem processamento, suas imagens ficam aqui', +'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app', 'These files are served without processing, your images go here': 'These files are served without processing, your images go here', +'these files are served without processing, your images go here': 'Estes arquivos são servidos sem processamento, suas imagens ficam aqui', 'This is the %(filename)s template': 'Este é o template %(filename)s', 'Ticket': 'Ticket', 'Ticket ID': 'Ticket ID', @@ -277,11 +323,15 @@ 'TM': 'MR', 'to previous version.': 'para a versão anterior.', 'To create a plugin, name a file/folder plugin_[name]': 'Para criar um plugin, nomeio um arquivo/pasta como plugin_[nome]', +'toggle breakpoint': 'toggle breakpoint', +'Toggle comment': 'Toggle comment', +'Toggle Fullscreen': 'Toggle Fullscreen', 'Traceback': 'Traceback', 'translation strings for the application': 'textos traduzidos para a aplicação', 'Translation strings for the application': 'Translation strings for the application', 'try': 'tente', 'try something like': 'tente algo como', +'Try the mobile interface': 'Try the mobile interface', 'Unable to check for upgrades': 'Não é possível checar as atualizações', 'unable to create application "%s"': 'não é possível criar a aplicação "%s"', 'unable to delete file "%(filename)s"': 'não é possível criar o arquico "%(filename)s"', @@ -300,8 +350,10 @@ 'Update:': 'Atualizar:', 'upgrade web2py now': 'atualize o web2py agora', 'upload': 'upload', +'Upload': 'Upload', 'Upload & install packed application': 'Faça upload e instale uma aplicação empacotada', 'Upload a package:': 'Faça upload de um pacote:', +'Upload and install packed application': 'Upload and install packed application', 'upload application:': 'Fazer upload de uma aplicação:', 'Upload existing application': 'Faça upload de uma aplicação existente', 'upload file:': 'Enviar arquivo:', diff --git a/gluon/tools.py b/gluon/tools.py index a5ec6ed7..5d44d015 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -3011,15 +3011,13 @@ class Auth(object): if self.settings.prevent_password_reset_attacks: key = request.vars.key - if not key and len(request.args)>1: - key = request.args[-1] if key: session._reset_password_key = key redirect(self.url(args='reset_password')) else: key = session._reset_password_key else: - key = request.vars.key or getarg(-1) + key = request.vars.key try: t0 = int(key.split('-')[0]) if time.time() - t0 > 60 * 60 * 24: @@ -3138,7 +3136,7 @@ class Auth(object): def email_reset_password(self, user): reset_password_key = str(int(time.time())) + '-' + web2py_uuid() link = self.url(self.settings.function, - args=('reset_password', reset_password_key), + args=('reset_password',), vars={'key': reset_password_key}, scheme=True) d = dict(user) d.update(dict(key=reset_password_key, link=link)) From 4e110c691ff71054c39c6f7c1c44fbb4ab58074d Mon Sep 17 00:00:00 2001 From: ilvalle Date: Sat, 11 Oct 2014 20:09:09 +0200 Subject: [PATCH 10/30] added initial gis tests --- .travis.yml | 2 +- gluon/tests/test_dal.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8cf53e1b..018bc16c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ before_script: - if [[ $DB == postgres* ]]; then pip install psycopg2; fi; - if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install pysqlite; fi - if [[ $DB == mysql* ]]; then mysql -e 'create database test_w2p;'; fi - - if [[ $DB == postgres* ]]; then psql -c 'create database test_w2p;' -U postgres; fi + - if [[ $DB == postgres* ]]; then psql -c 'create database test_w2p;' -U postgres; psql -U postgres -d test_w2p -c 'create extension postgis' fi # Install last sdk for app engine (update only whenever a new release is available) - if [[ $DB == google* ]]; then wget http://googleappengine.googlecode.com/files/google_appengine_1.8.9.zip -nv; fi diff --git a/gluon/tests/test_dal.py b/gluon/tests/test_dal.py index 3ce59562..9976392b 100644 --- a/gluon/tests/test_dal.py +++ b/gluon/tests/test_dal.py @@ -44,6 +44,9 @@ ALLOWED_DATATYPES = [ 'json', ] +IS_POSTGRESQL = 'postgres' in DEFAULT_URI + + def setUpModule(): pass @@ -1528,6 +1531,31 @@ class TestQuotesByDefault(unittest.TestCase): def testme(self): return + +class TestGis(unittest.TestCase): + + def testGeometry(self): + from gluon.dal import geoPoint, geoLine, geoPolygon + if not IS_POSTGRESQL: return + db = DAL(DEFAULT_URI, check_reserved=['all'], ignore_field_case=False) + t0 = db.define_table('t0', Field('point', 'geometry()')) + t1 = db.define_table('t1', Field('line', 'geometry(public, 4326, 2)')) + t2 = db.define_table('t2', Field('polygon', 'geometry(public, 4326, 2)')) + t0.insert(point=geoPoint(1,1)) + text = db(db.t0.id).select(db.t0.point.st_astext()).first()[db.t0.point.st_astext()] + self.assertEqual(text, "POINT(1 1)") + t1.insert(line=geoLine((1,1),(2,2))) + text = db(db.t1.id).select(db.t1.line.st_astext()).first()[db.t1.line.st_astext()] + self.assertEqual(text, "LINESTRING(1 1,2 2)") + t2.insert(polygon=geoPolygon((0,0),(2,0),(2,2),(0,2),(0,0))) + text = db(db.t2.id).select(db.t2.polygon.st_astext()).first()[db.t2.polygon.st_astext()] + self.assertEqual(text, "POLYGON((0 0,2 0,2 2,0 2,0 0))") + t0.drop() + t1.drop() + t2.drop() + return + + if __name__ == '__main__': unittest.main() tearDownModule() From c9494e2757ef040cc0f9003a8f1f5892c25f1777 Mon Sep 17 00:00:00 2001 From: ilvalle Date: Sat, 11 Oct 2014 20:16:06 +0200 Subject: [PATCH 11/30] updated travis.yml to add postgis --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 018bc16c..ac5914e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,9 @@ before_script: - if [[ $DB == postgres* ]]; then pip install psycopg2; fi; - if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install pysqlite; fi - if [[ $DB == mysql* ]]; then mysql -e 'create database test_w2p;'; fi - - if [[ $DB == postgres* ]]; then psql -c 'create database test_w2p;' -U postgres; psql -U postgres -d test_w2p -c 'create extension postgis' fi + - if [[ $DB == postgres* ]]; then psql -c 'create database test_w2p;' -U postgres; fi + - if [[ $DB == postgres* ]]; then psql -c 'create extension postgis;' -U postgres -d test_w2p; fi + # Install last sdk for app engine (update only whenever a new release is available) - if [[ $DB == google* ]]; then wget http://googleappengine.googlecode.com/files/google_appengine_1.8.9.zip -nv; fi From 50662b6acc3309ce3677e16ba8804435a63f23cd Mon Sep 17 00:00:00 2001 From: gi0baro Date: Sun, 12 Oct 2014 22:56:33 +0200 Subject: [PATCH 12/30] Added back DAL.Table for backward compatibility --- gluon/dal/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gluon/dal/base.py b/gluon/dal/base.py index eb9c95ce..1f6b69db 100644 --- a/gluon/dal/base.py +++ b/gluon/dal/base.py @@ -234,6 +234,7 @@ class DAL(object): """ + Table = Table def __new__(cls, uri='sqlite://dummy.db', *args, **kwargs): if not hasattr(THREAD_LOCAL,'db_instances'): From 15fe54bdcab0d321effbf629848d1a237336fa78 Mon Sep 17 00:00:00 2001 From: niphlod Date: Tue, 14 Oct 2014 23:42:41 +0200 Subject: [PATCH 13/30] refactor main_hook to a function --- applications/admin/static/js/web2py.js | 20 +++++++++++--------- applications/examples/static/js/web2py.js | 20 +++++++++++--------- applications/welcome/static/js/web2py.js | 20 +++++++++++--------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/applications/admin/static/js/web2py.js b/applications/admin/static/js/web2py.js index ffb6baa8..79d41094 100644 --- a/applications/admin/static/js/web2py.js +++ b/applications/admin/static/js/web2py.js @@ -684,19 +684,21 @@ } }); $.web2py.component_handler(target); + }, + main_hook : function() { + var flash = $('.flash'); + flash.hide(); + if(flash.html()) web2py.flash(flash.html()); + web2py.ajax_init(document); + web2py.event_handlers(); + web2py.a_handlers(); + web2py.form_handlers(); } } - /*end of functions */ /*main hook*/ - $(function() { - var flash = $('.flash'); - flash.hide(); - if(flash.html()) web2py.flash(flash.html()); - web2py.ajax_init(document); - web2py.event_handlers(); - web2py.a_handlers(); - web2py.form_handlers(); + $(function () { + web2py.main_hook(); }); })(jQuery); diff --git a/applications/examples/static/js/web2py.js b/applications/examples/static/js/web2py.js index ffb6baa8..79d41094 100644 --- a/applications/examples/static/js/web2py.js +++ b/applications/examples/static/js/web2py.js @@ -684,19 +684,21 @@ } }); $.web2py.component_handler(target); + }, + main_hook : function() { + var flash = $('.flash'); + flash.hide(); + if(flash.html()) web2py.flash(flash.html()); + web2py.ajax_init(document); + web2py.event_handlers(); + web2py.a_handlers(); + web2py.form_handlers(); } } - /*end of functions */ /*main hook*/ - $(function() { - var flash = $('.flash'); - flash.hide(); - if(flash.html()) web2py.flash(flash.html()); - web2py.ajax_init(document); - web2py.event_handlers(); - web2py.a_handlers(); - web2py.form_handlers(); + $(function () { + web2py.main_hook(); }); })(jQuery); diff --git a/applications/welcome/static/js/web2py.js b/applications/welcome/static/js/web2py.js index ffb6baa8..79d41094 100644 --- a/applications/welcome/static/js/web2py.js +++ b/applications/welcome/static/js/web2py.js @@ -684,19 +684,21 @@ } }); $.web2py.component_handler(target); + }, + main_hook : function() { + var flash = $('.flash'); + flash.hide(); + if(flash.html()) web2py.flash(flash.html()); + web2py.ajax_init(document); + web2py.event_handlers(); + web2py.a_handlers(); + web2py.form_handlers(); } } - /*end of functions */ /*main hook*/ - $(function() { - var flash = $('.flash'); - flash.hide(); - if(flash.html()) web2py.flash(flash.html()); - web2py.ajax_init(document); - web2py.event_handlers(); - web2py.a_handlers(); - web2py.form_handlers(); + $(function () { + web2py.main_hook(); }); })(jQuery); From 4df82d3a6ecdfe395393baadf0ca17491121ba35 Mon Sep 17 00:00:00 2001 From: "Jan M. Knaup" Date: Wed, 15 Oct 2014 11:03:31 +0200 Subject: [PATCH 14/30] in SQLTABLE, fixed use of REGEX_TABLE_DOT_FIELD leading to ignoring calculated / aggregate columns --- gluon/sqlhtml.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index 98d4395a..a100681b 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -3001,12 +3001,16 @@ class SQLTABLE(TABLE): return REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD if not columns: - columns = [c for c in sqlrows.colnames if REGEX_TABLE_DOT_FIELD.match(c)] + columns = list(sqlrows.colnames) if headers == 'fieldname:capitalize': headers = {} for c in columns: - (t, f) = REGEX_TABLE_DOT_FIELD.match(c).groups() - headers[t + '.' + f] = f.replace('_', ' ').title() + tfmatch=REGEX_TABLE_DOT_FIELD.match(c) + if tfmatch: + (t, f) = REGEX_TABLE_DOT_FIELD.match(c).groups() + headers[t + '.' + f] = f.replace('_', ' ').title() + else: + headers[c]=c elif headers == 'labels': headers = {} for c in columns: @@ -3025,7 +3029,7 @@ class SQLTABLE(TABLE): headers = {} else: for c in columns: # new implement dict - c = '.'.join(REGEX_TABLE_DOT_FIELD.match(c).groups()) + c = str(c) if isinstance(headers.get(c, c), dict): coldict = headers.get(c, c) attrcol = dict() From 45a689a8121457b371ec49ad301186a16a85b2da Mon Sep 17 00:00:00 2001 From: ilvalle Date: Thu, 16 Oct 2014 15:07:52 +0200 Subject: [PATCH 15/30] fix issue 1993: git push in the admin console throw an error --- applications/admin/views/default/git_push.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/applications/admin/views/default/git_push.html b/applications/admin/views/default/git_push.html index 36722c9e..c8ad74d4 100644 --- a/applications/admin/views/default/git_push.html +++ b/applications/admin/views/default/git_push.html @@ -4,12 +4,10 @@ frm = form smt_button = frm.element(_type="submit") smt_button['_class'] = 'btn' smt_button['_style'] = 'margin-right:4px;' -ccl_button = frm.element(_type="button") -ccl_button['_class'] = 'btn' }}

{{=T('This will push changes to the remote repo for application "%s".', app)}}

{{=form}}
- \ No newline at end of file + From f255da79f2d8706152713696434160a27e88f931 Mon Sep 17 00:00:00 2001 From: ilvalle Date: Thu, 16 Oct 2014 16:05:38 +0200 Subject: [PATCH 16/30] updated .gitignore (progress.log and temp directory) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 4b13a236..4aa1d556 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,8 @@ applications/*/errors/* applications/*/cache/* applications/*/uploads/* applications/*/*.py[oc] +applications/*/static/temp +applications/*/progress.log applications/examples/static/epydoc applications/examples/static/sphinx applications/admin/cron/cron.master From b616ee6a32d87686146c396f71e6f0724976e58a Mon Sep 17 00:00:00 2001 From: niphlod Date: Thu, 16 Oct 2014 20:56:24 +0200 Subject: [PATCH 17/30] fix for GAE tests often failing for no apparent reason --- gluon/tests/test_languages.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gluon/tests/test_languages.py b/gluon/tests/test_languages.py index 13f13a9d..60172fe9 100644 --- a/gluon/tests/test_languages.py +++ b/gluon/tests/test_languages.py @@ -35,6 +35,10 @@ try: #due to http://bugs.python.org/issue10845, testing multiprocessing in python is impossible if sys.platform.startswith('win'): MP_WORKING = 0 + #multiprocessing is also not available on GAE. Since tests randomly + #fail, let's not make them on it too + if 'datastore' in os.getenv('DB', ''): + MP_WORKING = 0 except ImportError: pass From b43ef65eb1de1a74d783a7f0d8fcca4b98332c6e Mon Sep 17 00:00:00 2001 From: niphlod Date: Thu, 16 Oct 2014 21:08:55 +0200 Subject: [PATCH 18/30] while we wait for a sane "prefixing", use a much more efficient way to find errors This comes from an unfortunate choice of naming conventions and the fact that manage errors gets called even on the full document on page load. I had a table full of .error-"classed" elements and it took nearly 30 seconds for that snippet to hide and fade them in. We should definitevely prefix every css class with something that won't clash with everything else. --- applications/admin/static/js/web2py.js | 2 +- applications/examples/static/js/web2py.js | 2 +- applications/welcome/static/js/web2py.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/admin/static/js/web2py.js b/applications/admin/static/js/web2py.js index ffb6baa8..5d77dfb1 100644 --- a/applications/admin/static/js/web2py.js +++ b/applications/admin/static/js/web2py.js @@ -141,7 +141,7 @@ }, /* manage errors in forms */ manage_errors: function(target) { - $('.error', target).hide().slideDown('slow'); + $('div.error', target).hide().slideDown('slow'); }, after_ajax: function(xhr) { /* called whenever an ajax request completes */ diff --git a/applications/examples/static/js/web2py.js b/applications/examples/static/js/web2py.js index ffb6baa8..5d77dfb1 100644 --- a/applications/examples/static/js/web2py.js +++ b/applications/examples/static/js/web2py.js @@ -141,7 +141,7 @@ }, /* manage errors in forms */ manage_errors: function(target) { - $('.error', target).hide().slideDown('slow'); + $('div.error', target).hide().slideDown('slow'); }, after_ajax: function(xhr) { /* called whenever an ajax request completes */ diff --git a/applications/welcome/static/js/web2py.js b/applications/welcome/static/js/web2py.js index ffb6baa8..5d77dfb1 100644 --- a/applications/welcome/static/js/web2py.js +++ b/applications/welcome/static/js/web2py.js @@ -141,7 +141,7 @@ }, /* manage errors in forms */ manage_errors: function(target) { - $('.error', target).hide().slideDown('slow'); + $('div.error', target).hide().slideDown('slow'); }, after_ajax: function(xhr) { /* called whenever an ajax request completes */ From 7e0e7eb6c861bb0a248dbd3ed0af121175a8d032 Mon Sep 17 00:00:00 2001 From: niphlod Date: Thu, 16 Oct 2014 22:27:40 +0200 Subject: [PATCH 19/30] ilike was wronlgy defined --- gluon/dal/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gluon/dal/objects.py b/gluon/dal/objects.py index c0cd0001..44730709 100644 --- a/gluon/dal/objects.py +++ b/gluon/dal/objects.py @@ -1208,7 +1208,7 @@ class Expression(object): return Query(db, op, self, value) def ilike(self, value): - return self.like(case_sensitive=False) + return self.like(value, case_sensitive=False) def regexp(self, value): db = self.db From d33091d76bcdd0808a3d45b7ff102be456a57553 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 16 Oct 2014 15:58:57 -0500 Subject: [PATCH 20/30] added ssl option for tornado, thanks dmvieira --- VERSION | 2 +- gluon/contrib/websocket_messaging.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/VERSION b/VERSION index 6126c887..ce54ce20 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.10.0-beta+timestamp.2014.09.24.13.35.58 +Version 2.10.0-beta+timestamp.2014.10.16.15.58.50 diff --git a/gluon/contrib/websocket_messaging.py b/gluon/contrib/websocket_messaging.py index c763098c..89a90897 100644 --- a/gluon/contrib/websocket_messaging.py +++ b/gluon/contrib/websocket_messaging.py @@ -143,6 +143,7 @@ class TokenHandler(tornado.web.RequestHandler): self.send_error(401) tokens[message] = None + class DistributeHandler(tornado.websocket.WebSocketHandler): def open(self, params): group, token, name = params.split('/') + [None, None] @@ -176,12 +177,11 @@ class DistributeHandler(tornado.websocket.WebSocketHandler): client.write_message('-' + self.name) print '%s:DISCONNECT from %s' % (time.time(), self.group) - #if your webserver is different from tornado server uncomment this - #or override using something more restrictive: - #http://tornado.readthedocs.org/en/latest/websocket.html#tornado.websocket.WebSocketHandler.check_origin - #def check_origin(self, origin): - # return True - +# if your webserver is different from tornado server uncomment this +# or override using something more restrictive: +# http://tornado.readthedocs.org/en/latest/websocket.html#tornado.websocket.WebSocketHandler.check_origin +# def check_origin(self, origin): +# return True if __name__ == "__main__": usage = __doc__ From 64ae27862ad0240ac5bb6dec78a6eb7337fb277d Mon Sep 17 00:00:00 2001 From: ilvalle Date: Fri, 17 Oct 2014 11:16:07 +0200 Subject: [PATCH 21/30] few more tests: gis functions, starts/endswith --- gluon/tests/test_dal.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/gluon/tests/test_dal.py b/gluon/tests/test_dal.py index 9976392b..451d23bc 100644 --- a/gluon/tests/test_dal.py +++ b/gluon/tests/test_dal.py @@ -407,6 +407,13 @@ class TestLike(unittest.TestCase): self.assertEqual(db(db.tt.aa.upper().like('A%')).count(), 1) self.assertEqual(db(db.tt.aa.upper().like('%B%')).count(),1) self.assertEqual(db(db.tt.aa.upper().like('%C')).count(), 1) + + # startswith endswith tests + self.assertEqual(db(db.tt.aa.startswith('a')).count(), 1) + self.assertEqual(db(db.tt.aa.endswith('c')).count(), 1) + self.assertEqual(db(db.tt.aa.startswith('c')).count(), 0) + self.assertEqual(db(db.tt.aa.endswith('a')).count(), 0) + db.tt.drop() db.define_table('tt', Field('aa', 'integer')) self.assertEqual(db.tt.insert(aa=1111111111), 1) @@ -1550,6 +1557,17 @@ class TestGis(unittest.TestCase): t2.insert(polygon=geoPolygon((0,0),(2,0),(2,2),(0,2),(0,0))) text = db(db.t2.id).select(db.t2.polygon.st_astext()).first()[db.t2.polygon.st_astext()] self.assertEqual(text, "POLYGON((0 0,2 0,2 2,0 2,0 0))") + query = t0.point.st_intersects(geoLine((0,0),(2,2))) + output = db(query).select(db.t0.point).first()[db.t0.point] + self.assertEqual(output, "POINT(1 1)") + query = t2.polygon.st_contains(geoPoint(1,1)) + n = db(query).count() + self.assertEqual(n, 1) + x=t0.point.st_x() + y=t0.point.st_y() + point = db(t0.id).select(x, y).first() + self.assertEqual(point[x], 1) + self.assertEqual(point[y], 1) t0.drop() t1.drop() t2.drop() From 9076971d751983434f8fd813cf5323c90cfbdc75 Mon Sep 17 00:00:00 2001 From: ilvalle Date: Fri, 17 Oct 2014 17:56:54 +0200 Subject: [PATCH 22/30] renamed postgre.py into postgres.py, removed starts/ends with overriding in PG adapter --- gluon/dal/adapters/__init__.py | 2 +- gluon/dal/adapters/{postgre.py => postgres.py} | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) rename gluon/dal/adapters/{postgre.py => postgres.py} (97%) diff --git a/gluon/dal/adapters/__init__.py b/gluon/dal/adapters/__init__.py index 4bad5bc8..54927648 100644 --- a/gluon/dal/adapters/__init__.py +++ b/gluon/dal/adapters/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from .sqlite import SQLiteAdapter, SpatiaLiteAdapter, JDBCSQLiteAdapter from .mysql import MySQLAdapter -from .postgre import PostgreSQLAdapter, NewPostgreSQLAdapter, JDBCPostgreSQLAdapter +from .postgres import PostgreSQLAdapter, NewPostgreSQLAdapter, JDBCPostgreSQLAdapter from .oracle import OracleAdapter from .mssql import MSSQLAdapter, MSSQL2Adapter, MSSQL3Adapter, MSSQL4Adapter, \ VerticaAdapter, SybaseAdapter diff --git a/gluon/dal/adapters/postgre.py b/gluon/dal/adapters/postgres.py similarity index 97% rename from gluon/dal/adapters/postgre.py rename to gluon/dal/adapters/postgres.py index c675f39e..e997949b 100644 --- a/gluon/dal/adapters/postgre.py +++ b/gluon/dal/adapters/postgres.py @@ -211,14 +211,6 @@ class PostgreSQLAdapter(BaseAdapter): return '(%s ~ %s)' % (self.expand(first), self.expand(second,'string')) - def STARTSWITH(self,first,second): - return '(%s LIKE %s)' % (self.expand(first), - self.expand(second+'%','string')) - - def ENDSWITH(self,first,second): - return '(%s LIKE %s)' % (self.expand(first), - self.expand('%'+second,'string')) - # GIS functions def ST_ASGEOJSON(self, first, second): From 9d4b2e66c4736c218033e8be70d135cdf0fc718f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonel=20C=C3=A2mara?= Date: Fri, 17 Oct 2014 19:12:00 +0100 Subject: [PATCH 23/30] Fix for issue 1996 This bug was caused by appadmin creating unnecessary files in the cache folder. --- applications/admin/controllers/appadmin.py | 46 ++++++++------------ applications/welcome/controllers/appadmin.py | 46 ++++++++------------ 2 files changed, 36 insertions(+), 56 deletions(-) diff --git a/applications/admin/controllers/appadmin.py b/applications/admin/controllers/appadmin.py index 3b78a019..50263b35 100644 --- a/applications/admin/controllers/appadmin.py +++ b/applications/admin/controllers/appadmin.py @@ -461,34 +461,24 @@ def ccache(): if value[0] < ram['oldest']: ram['oldest'] = value[0] ram['keys'].append((key, GetInHMS(time.time() - value[0]))) - folder = os.path.join(request.folder,'cache') - if not os.path.exists(folder): - os.mkdir(folder) - locker = open(os.path.join(folder, 'cache.lock'), 'a') - portalocker.lock(locker, portalocker.LOCK_EX) - disk_storage = shelve.open( - os.path.join(folder, 'cache.shelve')) - try: - for key, value in disk_storage.items(): - if isinstance(value, dict): - disk['hits'] = value['hit_total'] - value['misses'] - disk['misses'] = value['misses'] - try: - disk['ratio'] = disk['hits'] * 100 / value['hit_total'] - except (KeyError, ZeroDivisionError): - disk['ratio'] = 0 - else: - if hp: - disk['bytes'] += hp.iso(value[1]).size - disk['objects'] += hp.iso(value[1]).count - disk['entries'] += 1 - if value[0] < disk['oldest']: - disk['oldest'] = value[0] - disk['keys'].append((key, GetInHMS(time.time() - value[0]))) - finally: - portalocker.unlock(locker) - locker.close() - disk_storage.close() + + for key in cache.disk.storage: + value = cache.disk.storage[key] + if isinstance(value, dict): + disk['hits'] = value['hit_total'] - value['misses'] + disk['misses'] = value['misses'] + try: + disk['ratio'] = disk['hits'] * 100 / value['hit_total'] + except (KeyError, ZeroDivisionError): + disk['ratio'] = 0 + else: + if hp: + disk['bytes'] += hp.iso(value[1]).size + disk['objects'] += hp.iso(value[1]).count + disk['entries'] += 1 + if value[0] < disk['oldest']: + disk['oldest'] = value[0] + disk['keys'].append((key, GetInHMS(time.time() - value[0]))) total['entries'] = ram['entries'] + disk['entries'] total['bytes'] = ram['bytes'] + disk['bytes'] diff --git a/applications/welcome/controllers/appadmin.py b/applications/welcome/controllers/appadmin.py index 3b78a019..50263b35 100644 --- a/applications/welcome/controllers/appadmin.py +++ b/applications/welcome/controllers/appadmin.py @@ -461,34 +461,24 @@ def ccache(): if value[0] < ram['oldest']: ram['oldest'] = value[0] ram['keys'].append((key, GetInHMS(time.time() - value[0]))) - folder = os.path.join(request.folder,'cache') - if not os.path.exists(folder): - os.mkdir(folder) - locker = open(os.path.join(folder, 'cache.lock'), 'a') - portalocker.lock(locker, portalocker.LOCK_EX) - disk_storage = shelve.open( - os.path.join(folder, 'cache.shelve')) - try: - for key, value in disk_storage.items(): - if isinstance(value, dict): - disk['hits'] = value['hit_total'] - value['misses'] - disk['misses'] = value['misses'] - try: - disk['ratio'] = disk['hits'] * 100 / value['hit_total'] - except (KeyError, ZeroDivisionError): - disk['ratio'] = 0 - else: - if hp: - disk['bytes'] += hp.iso(value[1]).size - disk['objects'] += hp.iso(value[1]).count - disk['entries'] += 1 - if value[0] < disk['oldest']: - disk['oldest'] = value[0] - disk['keys'].append((key, GetInHMS(time.time() - value[0]))) - finally: - portalocker.unlock(locker) - locker.close() - disk_storage.close() + + for key in cache.disk.storage: + value = cache.disk.storage[key] + if isinstance(value, dict): + disk['hits'] = value['hit_total'] - value['misses'] + disk['misses'] = value['misses'] + try: + disk['ratio'] = disk['hits'] * 100 / value['hit_total'] + except (KeyError, ZeroDivisionError): + disk['ratio'] = 0 + else: + if hp: + disk['bytes'] += hp.iso(value[1]).size + disk['objects'] += hp.iso(value[1]).count + disk['entries'] += 1 + if value[0] < disk['oldest']: + disk['oldest'] = value[0] + disk['keys'].append((key, GetInHMS(time.time() - value[0]))) total['entries'] = ram['entries'] + disk['entries'] total['bytes'] = ram['bytes'] + disk['bytes'] From b2401a5923727f478c70c7c0d8d5ff8c41fcdbdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonel=20C=C3=A2mara?= Date: Sat, 18 Oct 2014 12:37:32 +0100 Subject: [PATCH 24/30] Fix for sanitize(''') returning '&#x27;' instead of ''' --- gluon/sanitizer.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/gluon/sanitizer.py b/gluon/sanitizer.py index a85b256a..5bd46625 100644 --- a/gluon/sanitizer.py +++ b/gluon/sanitizer.py @@ -10,8 +10,7 @@ Cross-site scripting (XSS) defense ----------------------------------- """ - -from htmllib import HTMLParser +from HTMLParser import HTMLParser from cgi import escape from urlparse import urlparse from formatter import AbstractFormatter @@ -48,11 +47,10 @@ class XssCleaner(HTMLParser): ], allowed_attributes={'a': ['href', 'title'], 'img': ['src', 'alt' ], 'blockquote': ['type']}, - fmt=AbstractFormatter, strip_disallowed=False ): - HTMLParser.__init__(self, fmt) + HTMLParser.__init__(self) self.result = '' self.open_tags = [] self.permitted_tags = [i for i in permitted_tags if i[-1] != '/'] @@ -77,7 +75,7 @@ class XssCleaner(HTMLParser): def handle_charref(self, ref): if self.in_disallowed: return - elif len(ref) < 7 and ref.isdigit(): + elif len(ref) < 7 and (ref.isdigit() or ref == 'x27'): # x27 is a special case for apostrophe self.result += '&#%s;' % ref else: self.result += xssescape('&#%s' % ref) From f10b1b93a9414a8ddeb7f2591b5b5eb9c6aaf9cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonel=20C=C3=A2mara?= Date: Sat, 18 Oct 2014 13:05:27 +0100 Subject: [PATCH 25/30] fixed remaining methods in HTMLParser that were still using the old htmllib.HTMLParser interface --- gluon/sanitizer.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/gluon/sanitizer.py b/gluon/sanitizer.py index 5bd46625..728c4bee 100644 --- a/gluon/sanitizer.py +++ b/gluon/sanitizer.py @@ -97,8 +97,7 @@ class XssCleaner(HTMLParser): def handle_starttag( self, tag, - method, - attrs, + attrs ): if tag not in self.permitted_tags: if self.strip_disallowed: @@ -128,7 +127,7 @@ class XssCleaner(HTMLParser): self.result += bt self.open_tags.insert(0, tag) - def handle_endtag(self, tag, attrs): + def handle_endtag(self, tag): bracketed = '' % tag if tag not in self.permitted_tags: if self.strip_disallowed: @@ -139,12 +138,6 @@ class XssCleaner(HTMLParser): self.result += bracketed self.open_tags.remove(tag) - def unknown_starttag(self, tag, attributes): - self.handle_starttag(tag, None, attributes) - - def unknown_endtag(self, tag): - self.handle_endtag(tag, None) - def url_is_acceptable(self, url): """ Accepts relative, absolute, and mailto urls From 794979abe1d37828c7da406b537b8358abeaf8c2 Mon Sep 17 00:00:00 2001 From: niphlod Date: Sat, 18 Oct 2014 20:20:34 +0200 Subject: [PATCH 26/30] pg8000 doesn't support json at all --- gluon/dal/adapters/postgre.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gluon/dal/adapters/postgre.py b/gluon/dal/adapters/postgre.py index c675f39e..601ca287 100644 --- a/gluon/dal/adapters/postgre.py +++ b/gluon/dal/adapters/postgre.py @@ -173,9 +173,12 @@ class PostgreSQLAdapter(BaseAdapter): def try_json(self): # check JSON data type support # (to be added to after_connection) - if self.driver_name == "pg8000": - supports_json = self.connection.server_version >= "9.2.0" - elif (self.driver_name == "psycopg2" and + + # until pg8000 supports json, leave this commented + #if self.driver_name == "pg8000": + # supports_json = self.connection.server_version >= "9.2.0" + + if (self.driver_name == "psycopg2" and self.driver.__version__ >= "2.0.12"): supports_json = self.connection.server_version >= 90200 elif self.driver_name == "zxJDBC": From 85a0e8f1b0702c749df6b752a115273dc2367e17 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 18 Oct 2014 15:09:53 -0500 Subject: [PATCH 27/30] added space for testing --- applications/admin/controllers/appadmin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/admin/controllers/appadmin.py b/applications/admin/controllers/appadmin.py index 3b78a019..4b50ed73 100644 --- a/applications/admin/controllers/appadmin.py +++ b/applications/admin/controllers/appadmin.py @@ -462,6 +462,7 @@ def ccache(): ram['oldest'] = value[0] ram['keys'].append((key, GetInHMS(time.time() - value[0]))) folder = os.path.join(request.folder,'cache') + if not os.path.exists(folder): os.mkdir(folder) locker = open(os.path.join(folder, 'cache.lock'), 'a') From 643748db0272df8a7e25348168db4b4340b57aba Mon Sep 17 00:00:00 2001 From: Jan Beilicke Date: Tue, 21 Oct 2014 15:56:21 +0200 Subject: [PATCH 28/30] Cleanup: Removed presumed debug print statement. --- gluon/sqlhtml.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index 4012fc06..657ef814 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -2001,8 +2001,6 @@ class SQLFORM(FORM): elif 'keywords' in request.get_vars: keywords = request.get_vars.keywords - print 'keyworkds are', keywords - def fetch_count(dbset): ##FIXME for google:datastore cache_count is ignored ## if it's not an integer From fc38f460eb085233e9abac44b3295f5c57fca541 Mon Sep 17 00:00:00 2001 From: niphlod Date: Tue, 21 Oct 2014 21:33:43 +0200 Subject: [PATCH 29/30] fixes issue 1998 --- scripts/sessions2trash.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/sessions2trash.py b/scripts/sessions2trash.py index e07e731b..e98dd9cc 100755 --- a/scripts/sessions2trash.py +++ b/scripts/sessions2trash.py @@ -32,7 +32,6 @@ from __future__ import with_statement import sys import os -print os.path.join(*__file__.split(os.sep)[:-2] or ['.']) sys.path.append(os.path.join(*__file__.split(os.sep)[:-2] or ['.'])) from gluon import current From 98f245655bab65b222ca6a316e31647985a663ee Mon Sep 17 00:00:00 2001 From: niphlod Date: Wed, 22 Oct 2014 22:04:52 +0200 Subject: [PATCH 30/30] some freezers publish also __file__ in globals() --- handlers/web2py_on_gevent.py | 6 +++--- web2py.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/handlers/web2py_on_gevent.py b/handlers/web2py_on_gevent.py index 701e05e2..62be419c 100644 --- a/handlers/web2py_on_gevent.py +++ b/handlers/web2py_on_gevent.py @@ -5,10 +5,10 @@ import sys import os import optparse -if '__file__' in globals(): - path = os.path.dirname(os.path.abspath(__file__)) -elif hasattr(sys, 'frozen'): +if hasattr(sys, 'frozen'): path = os.path.dirname(os.path.abspath(sys.executable)) +elif '__file__' in globals(): + path = os.path.dirname(os.path.abspath(__file__)) else: path = os.getcwd() os.chdir(path) diff --git a/web2py.py b/web2py.py index 59c02f9d..42ec1c22 100755 --- a/web2py.py +++ b/web2py.py @@ -4,10 +4,10 @@ import os import sys -if '__file__' in globals(): - path = os.path.dirname(os.path.abspath(__file__)) -elif hasattr(sys, 'frozen'): +if hasattr(sys, 'frozen'): path = os.path.dirname(os.path.abspath(sys.executable)) # for py2exe +elif '__file__' in globals(): + path = os.path.dirname(os.path.abspath(__file__)) else: # should never happen path = os.getcwd() os.chdir(path)