From b9120ddce4f254a017e58cca0007083b46d4d9c0 Mon Sep 17 00:00:00 2001 From: Massimo Date: Thu, 10 Jan 2013 13:01:12 -0600 Subject: [PATCH 01/26] fixed issue 1268, better grid search options, thanks Paolo Angulo --- VERSION | 2 +- gluon/globals.py | 6 ++++-- gluon/sqlhtml.py | 32 +++++++++++++++++++++++++------- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/VERSION b/VERSION index 73372cfc..c2bfd314 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.09.16.56.34 +Version 2.4.1-alpha.2+timestamp.2013.01.10.12.59.43 diff --git a/gluon/globals.py b/gluon/globals.py index c67348c1..11f3c7e5 100644 --- a/gluon/globals.py +++ b/gluon/globals.py @@ -375,7 +375,7 @@ class Response(Storage): wrapped = streamer(stream, chunk_size=chunk_size) return wrapped - def download(self, request, db, chunk_size=DEFAULT_CHUNK_SIZE, attachment=True): + def download(self, request, db, chunk_size=DEFAULT_CHUNK_SIZE, attachment=True, download_filename=None): """ example of usage in controller:: @@ -403,9 +403,11 @@ class Response(Storage): raise HTTP(404) headers = self.headers headers['Content-Type'] = contenttype(name) + if download_filename == None: + download_filename = filename if attachment: headers['Content-Disposition'] = \ - 'attachment; filename="%s"' % filename.replace('"','\"') + 'attachment; filename="%s"' % download_filename.replace('"','\"') return self.stream(stream, chunk_size=chunk_size, request=request) def json(self, data, default=None): diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index 973185c4..2f73fd6f 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -1623,7 +1623,7 @@ class SQLFORM(FORM): 'integer': ['=', '!=', '<', '>', '<=', '>=', 'in', 'not in'], 'double': ['=', '!=', '<', '>', '<=', '>='], 'id': ['=', '!=', '<', '>', '<=', '>=', 'in', 'not in'], - 'reference': ['=', '!=', '<', '>', '<=', '>=', 'in', 'not in'], + 'reference': ['=', '!='], 'boolean': ['=', '!=']} if fields[0]._db._adapter.dbengine == 'google:datastore': search_options['string'] = ['=', '!=', '<', '>', '<=', '>='] @@ -1641,15 +1641,33 @@ class SQLFORM(FORM): field.label, str) and T(field.label) or field.label selectfields.append(OPTION(label, _value=str(field))) operators = SELECT(*[OPTION(T(option), _value=option) for option in options]) + _id = "%s_%s" % (value_id,name) if field.type == 'boolean': + value_input = SQLFORM.widgets.boolean.widget(field,field.default,_id=_id) + elif field.type == 'double': + value_input = SQLFORM.widgets.double.widget(field,field.default,_id=_id) + elif field.type == 'time': + value_input = SQLFORM.widgets.time.widget(field,field.default,_id=_id) + elif field.type == 'date': + value_input = SQLFORM.widgets.date.widget(field,field.default,_id=_id) + elif field.type == 'datetime': + value_input = SQLFORM.widgets.datetime.widget(field,field.default,_id=_id) + elif (field.type.startswith('reference ') or + field.type.startswith('list:reference ')) and \ + hasattr(field.requires,'options'): value_input = SELECT( - OPTION(T("True"), _value="T"), - OPTION(T("False"), _value="F"), - _id="%s_%s" % (value_id,name)) + *[OPTION(v, _value=k) + for k,v in field.requires.options()], + **dict(_id=_id)) + elif field.type == 'integer' or \ + field.type.startswith('reference ') or \ + field.type.startswith('list:integer') or \ + field.type.startswith('list:reference '): + value_input = SQLFORM.widgets.integer.widget(field,field.default,_id=_id) else: - value_input = INPUT(_type='text', - _id="%s_%s" % (value_id,name), - _class=field.type) + value_input = INPUT( + _type='text', _id=_id, _class=field.type) + new_button = INPUT( _type="button", _value=T('New'), _class="btn", _onclick="%s_build_query('new','%s')" % (prefix,field)) From 8a1b37e8ec8db530e4fdf694e982ee8832de3c4a Mon Sep 17 00:00:00 2001 From: Massimo Date: Thu, 10 Jan 2013 13:04:38 -0600 Subject: [PATCH 02/26] IMAP DAL enhancements, thanks Alan --- VERSION | 2 +- gluon/dal.py | 49 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/VERSION b/VERSION index c2bfd314..2dd0f451 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.10.12.59.43 +Version 2.4.1-alpha.2+timestamp.2013.01.10.13.04.10 diff --git a/gluon/dal.py b/gluon/dal.py index 2e7a1569..3c66f9d0 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -5670,6 +5670,7 @@ class IMAPAdapter(NoSQLAdapter): self.driver_args = driver_args self.adapter_args = adapter_args self.mailbox_size = None + self.static_names = None self.charset = sys.getfilesystemencoding() # imap class self.imap4 = None @@ -5848,11 +5849,17 @@ class IMAPAdapter(NoSQLAdapter): return charset def reset_mailboxes(self): - self.connection.mailbox_names = None + "Forces the adapter to retrieve mailbox names from the server" + self.connection.mailbox_names = self.static_names = None self.get_mailboxes() def get_mailboxes(self): """ Query the mail database for mailbox names """ + if self.static_names: + # statically defined mailbox names + self.connection.mailbox_names = self.static_names + return self.static_names.keys() + mailboxes_list = self.connection.list() self.connection.mailbox_names = dict() mailboxes = list() @@ -5864,7 +5871,8 @@ class IMAPAdapter(NoSQLAdapter): sub_items = item.split("\"") sub_items = [sub_item for sub_item in sub_items \ if len(sub_item.strip()) > 0] - mailbox = sub_items[len(sub_items) - 1] + # mailbox = sub_items[len(sub_items) -1] + mailbox = sub_items[-1] # remove unwanted characters and store original names # Don't allow leading non alphabetic characters mailbox_name = re.sub('^[_0-9]*', '', re.sub('[^_\w]','',re.sub('[/ ]','_',mailbox))) @@ -5896,7 +5904,7 @@ class IMAPAdapter(NoSQLAdapter): else: return False - def define_tables(self): + def define_tables(self, mailbox_names=None): """ Auto create common IMAP fileds @@ -5908,11 +5916,16 @@ class IMAPAdapter(NoSQLAdapter): Returns a dictionary with tablename, server native mailbox name pairs. """ + if mailbox_names: + # optional statically declared mailboxes + self.static_names = mailbox_names if not isinstance(self.connection.mailbox_names, dict): self.get_mailboxes() - mailboxes = self.connection.mailbox_names.keys() - for mailbox_name in mailboxes: - self.db.define_table("%s" % mailbox_name, + + names = self.connection.mailbox_names.keys() + + for name in names: + self.db.define_table("%s" % name, Field("uid", "string", writable=False), Field("answered", "boolean"), Field("created", "datetime", writable=False), @@ -5935,8 +5948,8 @@ class IMAPAdapter(NoSQLAdapter): # Set a special _mailbox attribute for storing # native mailbox names - self.db[mailbox_name].mailbox = \ - self.connection.mailbox_names[mailbox_name] + self.db[name].mailbox = \ + self.connection.mailbox_names[name] # Set the db instance mailbox collections self.db.mailboxes = self.connection.mailbox_names @@ -5969,10 +5982,14 @@ class IMAPAdapter(NoSQLAdapter): if isinstance(query, Query): tablename = self.get_table(query) mailbox = self.connection.mailbox_names.get(tablename, None) - if mailbox is not None: + if mailbox is None: + raise ValueError("Mailbox name not found: %s" % mailbox) + else: # select with readonly - selected = self.connection.select(mailbox, True) - self.mailbox_size = int(selected[1][0]) + result, selected = self.connection.select(mailbox, True) + if result != "OK": + raise Exception("IMAP error: %s" % selected) + self.mailbox_size = int(selected[0]) search_query = "(%s)" % str(query).strip() search_result = self.connection.uid("search", None, search_query) # Normal IMAP response OK is assumed (change this) @@ -6014,10 +6031,12 @@ class IMAPAdapter(NoSQLAdapter): fetch_results.append(fr) else: # error retrieving the flags for this message - pass + raise Exception("IMAP error retrieving flags: %s" % fdata) else: # error retrieving the message body - pass + raise Exception("IMAP error retrieving the body: %s" % data) + else: + raise Exception("IMAP search error: %s" % search_result[1]) elif isinstance(query, (Expression, basestring)): raise NotImplementedError() else: @@ -6193,6 +6212,8 @@ class IMAPAdapter(NoSQLAdapter): result, data = self.connection.store(*command) if result == "OK": rowcount += 1 + else: + raise Exception("IMAP storing error: %s" % data) return rowcount def _count(self, query, distinct=None): @@ -6224,6 +6245,8 @@ class IMAPAdapter(NoSQLAdapter): result, data = self.connection.store(number, "+FLAGS", "(\\Deleted)") if result == "OK": counter += 1 + else: + raise Exception("IMAP store error: %s" % data) if counter > 0: result, data = self.connection.expunge() return counter From df653f9f78bff3c4cc97aab31aa91bac4ad1a6ee Mon Sep 17 00:00:00 2001 From: Massimo Date: Thu, 10 Jan 2013 14:29:50 -0600 Subject: [PATCH 03/26] No more templates in wiki, security vulnearbility --- VERSION | 2 +- gluon/tools.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/VERSION b/VERSION index 2dd0f451..0fc639e9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.10.13.04.10 +Version 2.4.1-alpha.2+timestamp.2013.01.10.14.29.15 diff --git a/gluon/tools.py b/gluon/tools.py index 0244fee9..de570913 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -4970,7 +4970,7 @@ class Wiki(object): slug.startswith(self.force_prefix)): current.session.flash = 'slug must have "%s" prefix' \ % self.force_prefix - redirect(URL(args=('_edit', self.force_prefix + slug))) + redirect(URL(args=('_create'))) db.wiki_page.can_read.default = [Wiki.everybody] db.wiki_page.can_edit.default = [auth.user_group_role()] db.wiki_page.title.default = title_guess @@ -4978,8 +4978,8 @@ class Wiki(object): if slug == 'wiki-menu': db.wiki_page.body.default = \ '- Menu Item > @////index\n- - Submenu > http://web2py.com' - else: - db.wiki_page.body.default = db(db.wiki_page.id==from_template).select(db.wiki_page.body)[0].body if int(from_template) > 0 else '## %s\n\npage content' % title_guess + #else: + # db.wiki_page.body.default = db(db.wiki_page.id==from_template).select(db.wiki_page.body)[0].body if int(from_template) > 0 else '## %s\n\npage content' % title_guess vars = current.request.post_vars if vars.body: vars.body = vars.body.replace('://%s' % self.host, '://HOSTNAME') @@ -5071,13 +5071,13 @@ class Wiki(object): slugs=db(db.wiki_page.id>0).select(db.wiki_page.id,db.wiki_page.slug) options=[OPTION(row.slug,_value=row.id) for row in slugs] options.insert(0, OPTION('',_value='')) - form = SQLFORM.factory(Field("slug", default=current.request.args(1), + form = SQLFORM.factory(Field("slug", default=current.request.args(1) or self.force_prefix, requires=(IS_SLUG(), IS_NOT_IN_DB(db,db.wiki_page.slug))), - Field("from_template", "reference wiki_page", - requires=IS_EMPTY_OR(IS_IN_DB(db, db.wiki_page, '%(slug)s')), - comment=current.T("Choose Template or empty for new Page")), - _class="well span6") + #Field("from_template", "reference wiki_page", + # requires=IS_EMPTY_OR(IS_IN_DB(db, db.wiki_page, '%(slug)s')), + # comment=current.T("Choose Template or empty for new Page")), + _class="well span6") form.element("[type=submit]").attributes["_value"] = current.T("Create Page from Slug") if form.process().accepted: From 683f8ebdc7dfd9b7305c2e07862828d3a6fed553 Mon Sep 17 00:00:00 2001 From: Massimo Date: Thu, 10 Jan 2013 14:54:21 -0600 Subject: [PATCH 04/26] changed pages grid in auth.wiki --- VERSION | 2 +- gluon/tools.py | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/VERSION b/VERSION index 0fc639e9..b2aa09b2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.10.14.29.15 +Version 2.4.1-alpha.2+timestamp.2013.01.10.14.53.47 diff --git a/gluon/tools.py b/gluon/tools.py index de570913..2be50b3b 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -4761,7 +4761,7 @@ class Wiki(object): Field('slug', requires=[IS_SLUG(), IS_NOT_IN_DB(db, 'wiki_page.slug')], - readable=False, writable=False), + writable=False), Field('title', unique=True), Field('body', 'text', notnull=True), Field('tags', 'list:string'), @@ -5088,21 +5088,26 @@ class Wiki(object): def pages(self): if not self.can_manage(): return self.not_authorized() - self.auth.db.wiki_page.id.represent = lambda id, row: SPAN( - '@////%s' % row.slug) + self.auth.db.wiki_page.slug.represent = lambda slug, row: SPAN( + '@////%s' % slug) self.auth.db.wiki_page.title.represent = lambda title, row: \ A(title, _href=URL(args=row.slug)) + wiki_table = self.auth.db.wiki_page content = SQLFORM.grid( - self.auth.db.wiki_page, + wiki_table, + fields = [wiki_table.slug, + wiki_table.title, wiki_table.tags, + wiki_table.can_read, wiki_table.can_edit], links=[ lambda row: - A('edit', _href=URL(args=('_edit', row.slug))), + A('edit', _href=URL(args=('_edit', row.slug)),_class='btn'), lambda row: - A('media', _href=URL(args=('_editmedia', row.slug)))], + A('media', _href=URL(args=('_editmedia', row.slug)),_class='btn')], details=False, editable=False, deletable=False, create=False, orderby=self.auth.db.wiki_page.title, args=['_pages'], user_signature=False) + return dict(content=content) def media(self, id): From 563239147f460854694adc0a5e263bc5c61064d9 Mon Sep 17 00:00:00 2001 From: Massimo Date: Thu, 10 Jan 2013 15:13:29 -0600 Subject: [PATCH 05/26] fixed preview of auth.wiki pages --- VERSION | 2 +- gluon/tools.py | 31 +++++++++++++++---------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/VERSION b/VERSION index b2aa09b2..9041eeab 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.10.14.53.47 +Version 2.4.1-alpha.2+timestamp.2013.01.10.15.12.56 diff --git a/gluon/tools.py b/gluon/tools.py index 2be50b3b..b2f146f3 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -4993,20 +4993,19 @@ class Wiki(object): redirect(URL(args=slug)) script = """ $(function() { - if (!$('#wiki_page_body').length) return; - var pagecontent = $('#wiki_page_body'); + if (!jQuery('#wiki_page_body').length) return; + var pagecontent = jQuery('#wiki_page_body'); pagecontent.css('font-family', 'Monaco,Menlo,Consolas,"Courier New",monospace'); - var prevbutton = $(''); - var mediabutton = $(''); - var preview = $('
').hide(); - var previewmedia = $('
'); - var table = $('form'); - var bodylabel = $('#wiki_page_body__label'); - preview.insertBefore(pagecontent); - prevbutton.insertAfter(bodylabel); - mediabutton.insertBefore(table); - previewmedia.insertBefore(table); + var prevbutton = jQuery(''); + var mediabutton = jQuery(''); + var preview = jQuery('
').hide(); + var previewmedia = jQuery('
'); + var form = pagecontent.closest('form'); + preview.insertBefore(form); + prevbutton.insertBefore(form); + mediabutton.insertBefore(form); + previewmedia.insertBefore(form); mediabutton.toggle(function() { web2py_component('%(urlmedia)s', 'previewmedia'); }, function() { @@ -5017,12 +5016,12 @@ class Wiki(object): if (prevbutton.hasClass('nopreview')) { prevbutton.addClass('preview').removeClass( 'nopreview').html('Edit Source'); - web2py_ajax_page('post', '%(url)s', {body : $('#wiki_page_body').val()}, 'preview'); - pagecontent.fadeOut('fast', function() {preview.fadeIn()}); + web2py_ajax_page('post', '%(url)s', {body : jQuery('#wiki_page_body').val()}, 'preview'); + form.fadeOut('fast', function() {preview.fadeIn()}); } else { prevbutton.addClass( 'nopreview').removeClass('preview').html('Preview'); - preview.fadeOut('fast', function() {pagecontent.fadeIn()}); + preview.fadeOut('fast', function() {form.fadeIn()}); } }) }) @@ -5046,7 +5045,7 @@ class Wiki(object): csv = True create = True if current.request.vars.embedded: - script = "var c = $('#wiki_page_body'); c.val(c.val() + $('%s').text()); return false;" + script = "var c = jQuery('#wiki_page_body'); c.val(c.val() + jQuery('%s').text()); return false;" fragment = self.auth.db.wiki_media.id.represent csv = False create = False From 7a94b2dbd6323bcf5dc2e03bd0de6da64709d6e0 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 10 Jan 2013 23:12:01 -0600 Subject: [PATCH 06/26] links to user have _rel='nofollow' --- VERSION | 2 +- gluon/tools.py | 35 +++++++++++++++++++---------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/VERSION b/VERSION index 9041eeab..142dad40 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.10.15.12.56 +Version 2.4.1-alpha.2+timestamp.2013.01.10.23.11.16 diff --git a/gluon/tools.py b/gluon/tools.py index b2f146f3..be485211 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -1256,6 +1256,9 @@ class Auth(object): def navbar(self, prefix='Welcome', action=None, separators=(' [ ', ' | ', ' ] '), user_identifier=DEFAULT, referrer_actions=DEFAULT, mode='default'): + def Anr(*a,**b): + b['_rel']='nofollow' + return A(*a,**b) referrer_actions = [] if not referrer_actions else referrer_actions request = current.request asdropdown = (mode == 'dropdown') @@ -1286,19 +1289,19 @@ class Auth(object): user_identifier = user_identifier % self.user if not user_identifier: user_identifier = '' - logout = A(T('Logout'), _href='%s/logout?_next=%s' % + logout = Anr(T('Logout'), _href='%s/logout?_next=%s' % (action, urllib.quote(self.settings.logout_next))) - profile = A(T('Profile'), _href=href('profile')) - password = A(T('Password'), _href=href('change_password')) + profile = Anr(T('Profile'), _href=href('profile')) + password = Anr(T('Password'), _href=href('change_password')) bar = SPAN( prefix, user_identifier, s1, logout, s3, _class='auth_navbar') if asdropdown: - logout = LI(A(I(_class='icon-off'), ' ' + T('Logout'), _href='%s/logout?_next=%s' % + logout = LI(Anr(I(_class='icon-off'), ' ' + T('Logout'), _href='%s/logout?_next=%s' % (action, urllib.quote(self.settings.logout_next)))) # the space before T('Logout') is intentional. It creates a gap between icon and text - profile = LI(A(I(_class='icon-user'), ' ' + + profile = LI(Anr(I(_class='icon-user'), ' ' + T('Profile'), _href=href('profile'))) - password = LI(A(I(_class='icon-lock'), ' ' + + password = LI(Anr(I(_class='icon-lock'), ' ' + T('Password'), _href=href('change_password'))) bar = UL(logout, _class='dropdown-menu') # logout will be the last item in list @@ -1312,21 +1315,21 @@ class Auth(object): bar.insert(-1, s2) bar.insert(-1, password) else: - login = A(T('Login'), _href=href('login')) - register = A(T('Register'), _href=href('register')) - retrieve_username = A( + login = Anr(T('Login'), _href=href('login')) + register = Anr(T('Register'), _href=href('register')) + retrieve_username = Anr( T('Forgot username?'), _href=href('retrieve_username')) - lost_password = A( + lost_password = Anr( T('Lost password?'), _href=href('request_reset_password')) bar = SPAN(s1, login, s3, _class='auth_navbar') if asdropdown: - login = LI(A(I(_class='icon-off'), ' ' + T('Login'), _href=href('login'))) # the space before T('Login') is intentional. It creates a gap between icon and text - register = LI(A(I(_class='icon-user'), + login = LI(Anr(I(_class='icon-off'), ' ' + T('Login'), _href=href('login'))) # the space before T('Login') is intentional. It creates a gap between icon and text + register = LI(Anr(I(_class='icon-user'), ' ' + T('Register'), _href=href('register'))) - retrieve_username = LI(A(I(_class='icon-edit'), ' ' + T( + retrieve_username = LI(Anr(I(_class='icon-edit'), ' ' + T( 'Forgot username?'), _href=href('retrieve_username'))) - lost_password = LI(A(I(_class='icon-lock'), ' ' + T( + lost_password = LI(Anr(I(_class='icon-lock'), ' ' + T( 'Lost password?'), _href=href('request_reset_password'))) bar = UL(login, _class='dropdown-menu') # login will be the last item in list @@ -1349,10 +1352,10 @@ class Auth(object): if asdropdown: bar.insert(-1, LI('', _class='divider')) if self.user_id: - bar = LI(A(prefix, user_identifier, _href='#'), + bar = LI(Anr(prefix, user_identifier, _href='#'), bar, _class='dropdown') else: - bar = LI(A(T('Login'), _href='#'), + bar = LI(Anr(T('Login'), _href='#'), bar, _class='dropdown') return bar From ea07c9dcd81cf29b8101044c45368f188e5f2946 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 10 Jan 2013 23:15:46 -0600 Subject: [PATCH 07/26] imapdb(query).select(imapdb.INBOX.sender, imapdb.INBOX.subject, imapdb.INBOX.seen), issue 1269, thanks Alan --- VERSION | 2 +- gluon/dal.py | 52 ++++++++++++++++++++++++++++------------------------ 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/VERSION b/VERSION index 142dad40..5f7f56c7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.10.23.11.16 +Version 2.4.1-alpha.2+timestamp.2013.01.10.23.15.01 diff --git a/gluon/dal.py b/gluon/dal.py index 3c66f9d0..08e6cb7d 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -5623,11 +5623,22 @@ class IMAPAdapter(NoSQLAdapter): # mapped names (which native # mailbox has what table name) - db.mailboxes # tablename, server native name pairs + imapdb.mailboxes # tablename, server native name pairs # To retrieve a table native mailbox name use: - db..mailbox + imapdb.
.mailbox + ### New features v2.4.1: + + # Declare mailboxes statically with tablename, name pairs + # This avoids the extra server names retrieval + + imapdb.define_tables({"inbox": "INBOX"}) + + # Selects without content/attachments/email columns will only + # fetch header and flags + + imapdb(q).select(imapdb.INBOX.sender, imapdb.INBOX.subject) """ types = { @@ -5848,11 +5859,6 @@ class IMAPAdapter(NoSQLAdapter): charset = message.get_content_charset() return charset - def reset_mailboxes(self): - "Forces the adapter to retrieve mailbox names from the server" - self.connection.mailbox_names = self.static_names = None - self.get_mailboxes() - def get_mailboxes(self): """ Query the mail database for mailbox names """ if self.static_names: @@ -5919,6 +5925,8 @@ class IMAPAdapter(NoSQLAdapter): if mailbox_names: # optional statically declared mailboxes self.static_names = mailbox_names + else: + self.static_names = None if not isinstance(self.connection.mailbox_names, dict): self.get_mailboxes() @@ -5965,7 +5973,7 @@ class IMAPAdapter(NoSQLAdapter): query = self.common_filter(query, [self.get_query_mailbox(query),]) return str(query) - def select(self,query,fields,attributes): + def select(self, query, fields, attributes): """ Search and Fetch records and return web2py rows """ # move this statement elsewhere (upper-level) @@ -5998,18 +6006,22 @@ class IMAPAdapter(NoSQLAdapter): # ten records (change for non-experimental implementation) # However, light responses are not guaranteed with this # approach, just fewer messages. - # TODO: change limitby single to 2-tuple argument limitby = attributes.get('limitby', None) messages_set = search_result[1][0].split() # descending order messages_set.reverse() if limitby is not None: - # TODO: asc/desc attributes + # TODO: orderby, asc/desc, limitby from complete message set messages_set = messages_set[int(limitby[0]):int(limitby[1])] - # Partial fetches are not used since the email - # library does not seem to support it (it converts - # partial messages to mangled message instances) - imap_fields = "(RFC822)" + + # keep the requests small for header/flags + if any([(field.name in ["content", + "attachments", "email"]) for + field in fields]): + imap_fields = "(RFC822 FLAGS)" + else: + imap_fields = "(RFC822.HEADER FLAGS)" + if len(messages_set) > 0: # create fetch results object list # fetch each remote message and store it in memmory @@ -6025,13 +6037,8 @@ class IMAPAdapter(NoSQLAdapter): "raw_message": data[0][1]} fr["multipart"] = fr["email"].is_multipart() # fetch flags for the message - ftyp, fdata = self.connection.uid("fetch", uid, "(FLAGS)") - if ftyp == "OK": - fr["flags"] = self.driver.ParseFlags(fdata[0]) - fetch_results.append(fr) - else: - # error retrieving the flags for this message - raise Exception("IMAP error retrieving flags: %s" % fdata) + fr["flags"] = self.driver.ParseFlags(data[1]) + fetch_results.append(fr) else: # error retrieving the message body raise Exception("IMAP error retrieving the body: %s" % data) @@ -6090,9 +6097,6 @@ class IMAPAdapter(NoSQLAdapter): # If there is no encoding found in the message header # force utf-8 replacing characters (change this to # module's defaults). Applies to .sender, .to, .cc and .bcc fields - ############################################################################# - # TODO: External function to manage encoding and decoding of message strings - ############################################################################# item_dict["%s.sender" % tablename] = self.encode_text(message["From"], charset) if "%s.to" % tablename in fieldnames: item_dict["%s.to" % tablename] = self.encode_text(message["To"], charset) From 96f4c944c1211b3ec7b0fe97b48809c9b722358f Mon Sep 17 00:00:00 2001 From: mdipierro Date: Fri, 11 Jan 2013 09:02:22 -0600 Subject: [PATCH 08/26] fixed issue with hiding password, thanks Niphlod --- VERSION | 2 +- gluon/dal.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 5f7f56c7..b1800cab 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.10.23.15.01 +Version 2.4.1-alpha.2+timestamp.2013.01.11.09.01.31 diff --git a/gluon/dal.py b/gluon/dal.py index 08e6cb7d..b7bd6f78 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -447,7 +447,7 @@ def pluralize(singular, rules=PLURALIZE_RULES): if plural: return plural def hide_password(uri): - return REGEX_PASSWORD.sub('://******:',uri) + return REGEX_NOPASSWD.sub('******',uri) def OR(a,b): return a|b @@ -6884,7 +6884,7 @@ class DAL(object): for db in db_group: if not db._uri: continue - k = REGEX_NOPASSWD.sub('******',db._uri) + k = hide_password(db._uri) infos[k] = dict(dbstats = [(row[0], row[1]) for row in db._timings], dbtables = {'defined': sorted(list(set(db.tables) - From e4526f472589f9c76947d6b7bb0e8a22613d3315 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Fri, 11 Jan 2013 13:38:03 -0600 Subject: [PATCH 09/26] fixed typo in ru.py, issue 1271, thanks Dmitry.Mosin --- VERSION | 2 +- applications/admin/languages/ru.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index b1800cab..1a542db2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.11.09.01.31 +Version 2.4.1-alpha.2+timestamp.2013.01.11.13.37.06 diff --git a/applications/admin/languages/ru.py b/applications/admin/languages/ru.py index 60689f0b..ed4cb893 100644 --- a/applications/admin/languages/ru.py +++ b/applications/admin/languages/ru.py @@ -134,7 +134,7 @@ 'Editing Language file': 'Правка языкового файла', 'Editing Plural Forms File': 'Editing Plural Forms File', 'Enterprise Web Framework': 'Enterprise Web Framework', -'Error logs for "%(app)s"': 'Журнал ошибок для "%(app)"', +'Error logs for "%(app)s"': 'Журнал ошибок для "%(app)s"', 'Error snapshot': 'Error snapshot', 'Error ticket': 'Error ticket', 'Errors': 'Ошибка', From 4c15575194272c599d50bd49d6534d3ca218eba5 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 12 Jan 2013 14:04:15 -0600 Subject: [PATCH 10/26] row.as_son, rows.as_json, rows.as_xml, thanks Alan --- VERSION | 2 +- gluon/dal.py | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 1a542db2..29bbc971 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.11.13.37.06 +Version 2.4.1-alpha.2+timestamp.2013.01.12.14.03.31 diff --git a/gluon/dal.py b/gluon/dal.py index b7bd6f78..3b001bb8 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -6689,6 +6689,40 @@ class Row(object): del d[k] return d + def as_json(self, mode='object', default=None, **kwargs): + """ + serializes the table to a JSON list of objects + + kwargs are passed to .as_dict method + """ + mode = mode.lower() + if not mode in ['object', 'array']: + raise SyntaxError('Invalid JSON serialization mode: %s' % mode) + + multi = any([isinstance(v, self.__class__) for v in self.values()]) + item = dict() + + if multi: + for k, v in self.as_dict(**kwargs).iteritems(): + item.update(v) + else: + item = self.as_dict(**kwargs) + + if mode != 'object': + item = item.values() + + if have_serializers: + return serializers.json(item, + default=default or + serializers.custom_json) + else: + try: + import json as simplejson + except ImportError: + import gluon.contrib.simplejson as simplejson + return simplejson.dumps(item) + + ################################################################################ # Everything below should be independent of the specifics of the database # and should work for RDBMs and some NoSQL databases @@ -9591,7 +9625,10 @@ class Rows(object): import sqlhtml return sqlhtml.SQLTABLE(self).xml() - def json(self, mode='object', default=None): + def as_xml(self,row_name='row',rows_name='rows'): + return self.xml(strict=True, row_name=row_name, rows_name=rows_name) + + def as_json(self, mode='object', default=None): """ serializes the table to a JSON list of objects """ @@ -9633,6 +9670,7 @@ class Rows(object): import gluon.contrib.simplejson as simplejson return simplejson.dumps(items) + json = as_json ################################################################################ # dummy function used to define some doctests From fcf7b1a0255e870712948b21074e11d3dda67742 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 12 Jan 2013 14:09:05 -0600 Subject: [PATCH 11/26] row.as_json simplification thanks Alan --- VERSION | 2 +- gluon/dal.py | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/VERSION b/VERSION index 29bbc971..916dfd8f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.12.14.03.31 +Version 2.4.1-alpha.2+timestamp.2013.01.12.14.08.26 diff --git a/gluon/dal.py b/gluon/dal.py index 3b001bb8..0d7ecfef 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -6689,28 +6689,22 @@ class Row(object): del d[k] return d - def as_json(self, mode='object', default=None, **kwargs): + def as_json(self, default=None, **kwargs): """ serializes the table to a JSON list of objects - kwargs are passed to .as_dict method + only "object" mode supported + TODO: return array mode with query column order """ - mode = mode.lower() - if not mode in ['object', 'array']: - raise SyntaxError('Invalid JSON serialization mode: %s' % mode) - multi = any([isinstance(v, self.__class__) for v in self.values()]) item = dict() if multi: - for k, v in self.as_dict(**kwargs).iteritems(): + for v in self.as_dict(**kwargs).values(): item.update(v) else: item = self.as_dict(**kwargs) - if mode != 'object': - item = item.values() - if have_serializers: return serializers.json(item, default=default or From 3eba79d6c5ba3b2d1602da34ffb476d839b55732 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 12 Jan 2013 14:12:19 -0600 Subject: [PATCH 12/26] fixed issue 1271, admin/languages/ru.py, thanks Dmitry.Mosin --- VERSION | 2 +- applications/admin/languages/ru.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 916dfd8f..ffa0a317 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.12.14.08.26 +Version 2.4.1-alpha.2+timestamp.2013.01.12.14.11.35 diff --git a/applications/admin/languages/ru.py b/applications/admin/languages/ru.py index ed4cb893..0dbd7022 100644 --- a/applications/admin/languages/ru.py +++ b/applications/admin/languages/ru.py @@ -92,7 +92,7 @@ 'Database': 'База данных', 'database': 'база данных', 'database %s select': 'Выбор базы данных %s ', -'database administration': 'администраторирование базы данных', +'database administration': 'администрирование базы данных', 'Date and Time': 'Дата и время', 'db': 'бд', 'DB Model': 'Модель БД', From 05fdf963af861977b01afb6b1db6516ec04612ff Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 13 Jan 2013 10:44:07 -0600 Subject: [PATCH 13/26] admin/languages/sr-*.py thanks Sasa Kelecevic --- VERSION | 2 +- applications/admin/languages/sr-cr.py | 212 ++++++++++++++++++++++++++ applications/admin/languages/sr-lt.py | 212 ++++++++++++++++++++++++++ 3 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 applications/admin/languages/sr-cr.py create mode 100644 applications/admin/languages/sr-lt.py diff --git a/VERSION b/VERSION index ffa0a317..71e0c806 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.12.14.11.35 +Version 2.4.1-alpha.2+timestamp.2013.01.13.10.43.10 diff --git a/applications/admin/languages/sr-cr.py b/applications/admin/languages/sr-cr.py new file mode 100644 index 00000000..a0acdbd9 --- /dev/null +++ b/applications/admin/languages/sr-cr.py @@ -0,0 +1,212 @@ +# coding: utf8 +{ +'!langcode!': 'sr-cr', +'!langname!': 'Српски (Ћирилица)', +'%Y-%m-%d': '%d-%m-%Y', +'%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S', +'(requires internet access)': '(захтијева приступ интернету)', +'(something like "it-it")': '(нешто као "it-it")', +'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(датотека **gluon/contrib/plural_rules/%s.py** није пронађена)', +'About': 'Информације', +'About application': 'О апликацији', +'Additional code for your application': 'Додатни код за апликацију', +'admin disabled because unable to access password file': 'администрација онемогућена јер не могу приступити датотеци са лозинком', +'Admin language': 'Језик администратора', +'administrative interface': 'административни интерфејс', +'Administrator Password:': 'Лозинка администратора:', +'and rename it:': 'и преименуј у:', +'Application name:': 'Назив апликације:', +'are not used': 'није кориштено', +'are not used yet': 'није још кориштено', +'Are you sure you want to delete this object?': 'Да ли сте сигурни да желите обрисати?', +'arguments': 'arguments', +'at char %s': 'код слова %s', +'at line %s': 'на линији %s', +'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.', +'back': 'назад', +'Basics': 'Основе', +'Begin': 'Почетак', +'cache, errors and sessions cleaned': 'кеш, грешке и сесије су обрисани', +'can be a git repo': 'може бити git repo', +'cannot upload file "%(filename)s"': 'не мофу отпремити датотеку "%(filename)s"', +'Change admin password': 'Промијени лзинку администратора', +'check all': 'check all', +'Check for upgrades': 'Провјери могућност надоградње', +'Checking for upgrades...': 'Провјеравам могућност надоградње...', +'Clean': 'Прочисти', +'Click row to expand traceback': 'Click row to expand traceback', +'code': 'код', +'collapse/expand all': 'сакрити/приказати све', +'Compile': 'Компајлирај', +'Controllers': 'Контролери', +'controllers': 'контролери', +'Count': 'Count', +'Create': 'Креирај', +'create file with filename:': 'Креирај датотеку под називом:', +'Create rules': 'Креирај правила', +'created by': 'израдио', +'crontab': 'crontab', +'currently running': 'тренутно покренут', +'currently saved or': 'тренутно сачувано или', +'database administration': 'администрација базе података', +'Debug': 'Debug', +'defines tables': 'дефинише табеле', +'delete': 'обриши', +'Delete': 'Обриши', +'delete all checked': 'delete all checked', +'Delete this file (you will be asked to confirm deletion)': 'Обриши ову даатотеку (бићете упитани за потврду брисања)', +'Deploy': 'Постави', +'Deploy on Google App Engine': 'Постави на Google App Engine', +'Deploy to OpenShift': 'Постави на OpenShift', +'Detailed traceback description': 'Detailed traceback description', +'direction: ltr': 'direction: ltr', +'Disable': 'Искључи', +'docs': 'документација', +'download layouts': 'преузми layouts', +'download plugins': 'преузми plugins', +'Edit': 'Уређивање', +'edit all': 'уреди све', +'Edit application': 'Уреди апликацију', +'edit controller': 'уреди контролер', +'edit views:': 'уреди views:', +'Editing file "%s"': 'Уређивање датотеке "%s"', +'Editing Language file': 'Уређивање језичке датотеке', +'Error': 'Грешка', +'Error logs for "%(app)s"': 'Преглед грешака за "%(app)s"', +'Error snapshot': 'Error snapshot', +'Error ticket': 'Error ticket', +'Errors': 'Грешке', +'Exception instance attributes': 'Exception instance attributes', +'Expand Abbreviation': 'Expand Abbreviation', +'exposes': 'exposes', +'exposes:': 'exposes:', +'extends': 'проширује', +'failed to compile file because:': 'нисам могао да компајлирам због:', +'File': 'Датотека', +'file does not exist': 'датотека не постоји', +'file saved on %s': 'датотека сачувана на %s', +'filter': 'филтер', +'Find Next': 'Пронађи сљедећи', +'Find Previous': 'Пронађи претходни', +'Frames': 'Frames', +'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.', +'Generate': 'Generate', +'Get from URL:': 'Преузми са странице:', +'Git Pull': 'Git Pull', +'Git Push': 'Git Push', +'Go to Matching Pair': 'Go to Matching Pair', +'go!': 'крени!', +'Help': 'Помоћ', +'Hide/Show Translated strings': 'Сакрити/Приказати преведене ријечи', +'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.': '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.', +'includes': 'укључује', +'inspect attributes': 'inspect attributes', +'Install': 'Инсталирај', +'Installed applications': 'Инсталиране апликације', +'invalid password.': 'погрешна лозинка.', +'Key bindings': 'Пречице', +'Key bindings for ZenCoding Plugin': 'Пречице за ZenCoding Plugin', +'Language files (static strings) updated': 'Језичке датотеке су ажуриране', +'languages': 'језици', +'Languages': 'Језици', +'Last saved on:': 'Посљедња измјена:', +'License for': 'Лиценца за', +'loading...': 'преузимам...', +'locals': 'locals', +'Login': 'Пријава', +'Login to the Administrative Interface': 'Пријава за административни интерфејс', +'Logout': 'Излаз', +'Match Pair': 'Match Pair', +'Merge Lines': 'Споји линије', +'models': 'models', +'Models': 'Models', +'Modules': 'Modules', +'modules': 'modules', +'New Application Wizard': 'Чаробњак за нове апликације', +'New application wizard': 'Чаробњак за нове апликације', +'New simple application': 'Нова једноставна апликација', +'Next Edit Point': 'Next Edit Point', +'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder', +'online designer': 'онлајн дизајнер', +'Original/Translation': 'Оргинал/Превод', +'Overwrite installed app': 'Пребриши постојећу апликацију', +'Pack all': 'Запакуј све', +'Peeking at file': 'Peeking at file', +'Plugins': 'Plugins', +'plugins': 'plugins', +'Plural-Forms:': 'Plural-Forms:', +'Powered by': 'Омогућио', +'Previous Edit Point': 'Previous Edit Point', +'Private files': 'Private files', +'private files': 'private files', +'Project Progress': 'Напредак пројекта', +'Reload routes': 'Обнови преусмјерења', +'Removed Breakpoint on %s at line %s': 'Removed Breakpoint on %s at line %s', +'Replace': 'Замијени', +'Replace All': 'Замијени све', +'request': 'request', +'response': 'response', +'restart': 'restart', +'restore': 'restore', +'revert': 'revert', +'rules are not defined': 'правила нису дефинисана', +'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': 'Покренути на %s', +'Save': 'Сачувај', +'Save via Ajax': 'сачувај via Ajax', +'Saved file hash:': 'Сачувано као хаш:', +'session': 'сесија', +'session expired': 'сесија истекла', +'Set Breakpoint on %s at line %s: %s': 'Set Breakpoint on %s at line %s: %s', +'shell': 'shell', +'Site': 'Сајт', +'skip to generate': 'skip to generate', +'Start a new app': 'Покрени нову апликацију', +'Start searching': 'Покрени претрагу', +'Start wizard': 'Покрени чаробњака', +'static': 'static', +'Static files': 'Static files', +'Step': 'Корак', +'Submit': 'Прихвати', +'successful': 'успјешан', +'test': 'тест', +'Testing application': 'Тестирање апликације', +'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': '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', +'There are no models': 'There are no models', +'There are no plugins': 'There are no plugins', +'There are no private files': 'There are no private files', +'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', +'Ticket ID': 'Ticket ID', +'Ticket Missing': 'Ticket nedostaje', +'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', +'Toggle Fullscreen': 'Toggle Fullscreen', +'Traceback': 'Traceback', +'Translation strings for the application': 'Ријечи у апликацији које треба превести', +'Try the mobile interface': 'Пробај мобилни интерфејс', +'try view': 'try view', +'uncheck all': 'uncheck all', +'Uninstall': 'Деинсталирај', +'update': 'ажурирај', +'update all languages': 'ажурирај све језике', +'upload': 'Отпреми', +'Upload a package:': 'Преузми пакет:', +'Upload and install packed application': 'Преузми и инсталирај запаковану апликацију', +'upload file:': 'преузми датотеку:', +'upload plugin file:': 'преузми плагин датотеку:', +'variables': 'variables', +'Version': 'Верзија', +'Version %s.%s.%s (%s) %s': 'Верзија %s.%s.%s (%s) %s', +'Versioning': 'Versioning', +'views': 'views', +'Views': 'Views', +'Web Framework': 'Web Framework', +'web2py is up to date': 'web2py је ажуран', +'web2py Recent Tweets': 'web2py Recent Tweets', +'Wrap with Abbreviation': 'Wrap with Abbreviation', +} diff --git a/applications/admin/languages/sr-lt.py b/applications/admin/languages/sr-lt.py new file mode 100644 index 00000000..7ab1bda6 --- /dev/null +++ b/applications/admin/languages/sr-lt.py @@ -0,0 +1,212 @@ +# coding: utf8 +{ +'!langcode!': 'sr-lt', +'!langname!': 'Srpski (Latinica)', +'%Y-%m-%d': '%d-%m-%Y', +'%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S', +'(requires internet access)': '(zahtijeva pristup internetu)', +'(something like "it-it")': '(nešto kao "it-it")', +'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(datoteka **gluon/contrib/plural_rules/%s.py** nije pronađena)', +'About': 'Informacije', +'About application': 'O aplikaciji', +'Additional code for your application': 'Dodatni kod za aplikaciju', +'admin disabled because unable to access password file': 'administracija onemogućena jer ne mogu pristupiti datoteci sa lozinkom', +'Admin language': 'Jezik administratora', +'administrative interface': 'administrativni interfejs', +'Administrator Password:': 'Lozinka administratora:', +'and rename it:': 'i preimenuj u:', +'Application name:': 'Naziv aplikacije:', +'are not used': 'nije korišteno', +'are not used yet': 'nije još korišteno', +'Are you sure you want to delete this object?': 'Da li ste sigurni da želite obrisati?', +'arguments': 'arguments', +'at char %s': 'kod slova %s', +'at line %s': 'na liniji %s', +'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.', +'back': 'nazad', +'Basics': 'Osnove', +'Begin': 'Početak', +'cache, errors and sessions cleaned': 'keš, greške i sesije su obrisani', +'can be a git repo': 'može biti git repo', +'cannot upload file "%(filename)s"': 'ne mogu otpremiti datoteku "%(filename)s"', +'Change admin password': 'Promijeni lozinku administratora', +'check all': 'check all', +'Check for upgrades': 'Provjeri mogućnost nadogradnje', +'Checking for upgrades...': 'Provjeravam mogućnost nadogradnje...', +'Clean': 'Pročisti', +'Click row to expand traceback': 'Click row to expand traceback', +'code': 'kod', +'collapse/expand all': 'sakriti/prikazati sve', +'Compile': 'Kompajliraj', +'Controllers': 'Kontroleri', +'controllers': 'kontroleri', +'Count': 'Count', +'Create': 'Kreiraj', +'create file with filename:': 'Kreiraj datoteku pod nazivom:', +'Create rules': 'Kreiraj pravila', +'created by': 'izradio', +'crontab': 'crontab', +'currently running': 'trenutno pokrenut', +'currently saved or': 'trenutno sačuvano ili', +'database administration': 'administracija baze podataka', +'Debug': 'Debug', +'defines tables': 'definiše tabele', +'delete': 'obriši', +'Delete': 'Obriši', +'delete all checked': 'delete all checked', +'Delete this file (you will be asked to confirm deletion)': 'Obriši ovu datoteku (bićete upitani za potvrdu brisanja)', +'Deploy': 'Postavi', +'Deploy on Google App Engine': 'Postavi na Google App Engine', +'Deploy to OpenShift': 'Postavi na OpenShift', +'Detailed traceback description': 'Detailed traceback description', +'direction: ltr': 'direction: ltr', +'Disable': 'Isključi', +'docs': 'dokumentacija', +'download layouts': 'preuzmi layouts', +'download plugins': 'preuzmi plugins', +'Edit': 'Uređivanje', +'edit all': 'uredi sve', +'Edit application': 'Uredi aplikaciju', +'edit controller': 'uredi controller', +'edit views:': 'uredi views:', +'Editing file "%s"': 'Uređivanje datoteke "%s"', +'Editing Language file': 'Uređivanje jezičke datoteke', +'Error': 'Greška', +'Error logs for "%(app)s"': 'Pregled grešaka za "%(app)s"', +'Error snapshot': 'Error snapshot', +'Error ticket': 'Error ticket', +'Errors': 'Greške', +'Exception instance attributes': 'Exception instance attributes', +'Expand Abbreviation': 'Expand Abbreviation', +'exposes': 'exposes', +'exposes:': 'exposes:', +'extends': 'proširuje', +'failed to compile file because:': 'nisam mogao da kompajliram zbog:', +'File': 'Datoteka', +'file does not exist': 'datoteka ne postoji', +'file saved on %s': 'datoteka sačuvana na %s', +'filter': 'filter', +'Find Next': 'Pronađi sljedeći', +'Find Previous': 'Pronađi prethodni', +'Frames': 'Frames', +'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.', +'Generate': 'Generate', +'Get from URL:': 'Preuzmi sa stranice:', +'Git Pull': 'Git Pull', +'Git Push': 'Git Push', +'Go to Matching Pair': 'Go to Matching Pair', +'go!': 'kreni!', +'Help': 'Pomoć', +'Hide/Show Translated strings': 'Sakriti/Prikazati prevedene riječi', +'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.': '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.', +'includes': 'uključuje', +'inspect attributes': 'inspect attributes', +'Install': 'Instaliraj', +'Installed applications': 'Instalirane aplikacije', +'invalid password.': 'pogrešna lozinka.', +'Key bindings': 'Prečice', +'Key bindings for ZenCoding Plugin': 'Prečice za for ZenCoding Plugin', +'Language files (static strings) updated': 'Jezičke datoteke su ažurirane', +'languages': 'jezici', +'Languages': 'Jezici', +'Last saved on:': 'Posljednja izmjena:', +'License for': 'Licenca za', +'loading...': 'preuzimam...', +'locals': 'locals', +'Login': 'Prijava', +'Login to the Administrative Interface': 'Prijava za administrativni interfejs', +'Logout': 'Izlaz', +'Match Pair': 'Match Pair', +'Merge Lines': 'Spoji linije', +'models': 'models', +'Models': 'Models', +'Modules': 'Modules', +'modules': 'modules', +'New Application Wizard': 'Čarobnjak za nove aplikacije', +'New application wizard': 'Čarobnjak za nove aplikacije', +'New simple application': 'Nova jednostavna aplikacija', +'Next Edit Point': 'Next Edit Point', +'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder', +'online designer': 'onlajn dizajner', +'Original/Translation': 'Original/Prevod', +'Overwrite installed app': 'Prebriši postojeću aplikaciju', +'Pack all': 'Zapakuj sve', +'Peeking at file': 'Peeking at file', +'Plugins': 'Plugins', +'plugins': 'plugins', +'Plural-Forms:': 'Plural-Forms:', +'Powered by': 'Omogućio', +'Previous Edit Point': 'Previous Edit Point', +'Private files': 'Private files', +'private files': 'private files', +'Project Progress': 'Napredak projekta', +'Reload routes': 'Obnovi preusmjerenja', +'Removed Breakpoint on %s at line %s': 'Removed Breakpoint on %s at line %s', +'Replace': 'Zamijeni', +'Replace All': 'Zamijeni sve', +'request': 'request', +'response': 'response', +'restart': 'restart', +'restore': 'restore', +'revert': 'revert', +'rules are not defined': 'pravila nisu definisana', +'rules:': 'pravila:', +"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': 'Pokrenuto na %s', +'Save': 'Sačuvaj', +'Save via Ajax': 'Sačuvaj via Ajax', +'Saved file hash:': 'Sačuvano kao haš:', +'session': 'sesija', +'session expired': 'sesija istekla', +'Set Breakpoint on %s at line %s: %s': 'Set Breakpoint on %s at line %s: %s', +'shell': 'shell', +'Site': 'Sajt', +'skip to generate': 'skip to generate', +'Start a new app': 'Pokreni novu aplikaciju', +'Start searching': 'Pokreni pretragu', +'Start wizard': 'Pokreni čarobnjaka', +'static': 'static', +'Static files': 'Static files', +'Step': 'Korak', +'Submit': 'Prihvati', +'successful': 'uspješan', +'test': 'test', +'Testing application': 'Testing application', +'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': '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', +'There are no models': 'There are no models', +'There are no plugins': 'There are no plugins', +'There are no private files': 'There are no private files', +'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', +'Ticket ID': 'Ticket ID', +'Ticket Missing': 'Ticket nedostaje', +'to previous version.': 'na prethodnu verziju.', +'To create a plugin, name a file/folder plugin_[name]': 'To create a plugin, name a file/folder plugin_[name]', +'toggle breakpoint': 'toggle breakpoint', +'Toggle Fullscreen': 'Toggle Fullscreen', +'Traceback': 'Traceback', +'Translation strings for the application': 'Riječi u aplikaciji koje treba prevesti', +'Try the mobile interface': 'Probaj mobilni interfejs', +'try view': 'try view', +'uncheck all': 'uncheck all', +'Uninstall': 'Deinstaliraj', +'update': 'ažuriraj', +'update all languages': 'ažuriraj sve jezike', +'upload': 'Otpremi', +'Upload a package:': 'Preuzmi paket:', +'Upload and install packed application': 'Preuzmi i instaliraj zapakovanu aplikaciju', +'upload file:': 'preuzmi datoteku:', +'upload plugin file:': 'preuzmi plugin datoteku:', +'variables': 'variables', +'Version': 'Verzija', +'Version %s.%s.%s (%s) %s': 'Verzija %s.%s.%s (%s) %s', +'Versioning': 'Versioning', +'views': 'views', +'Views': 'Views', +'Web Framework': 'Web Framework', +'web2py is up to date': 'web2py je ažuran', +'web2py Recent Tweets': 'web2py Recent Tweets', +'Wrap with Abbreviation': 'Wrap with Abbreviation', +} From b1f51c30a6a8f3fcd07948e832fe8bc4d578f556 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 13 Jan 2013 13:08:41 -0600 Subject: [PATCH 14/26] Row.as_xml and Rows.as_csv, issue 1275, thank you Alan --- VERSION | 2 +- gluon/dal.py | 146 +++++++++++++++++++++++++++++---------------------- 2 files changed, 84 insertions(+), 64 deletions(-) diff --git a/VERSION b/VERSION index 71e0c806..ca03c054 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.13.10.43.10 +Version 2.4.1-alpha.2+timestamp.2013.01.13.13.07.43 diff --git a/gluon/dal.py b/gluon/dal.py index 0d7ecfef..0f8b3967 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -6689,32 +6689,87 @@ class Row(object): del d[k] return d - def as_json(self, default=None, **kwargs): + def as_xml(self, row_name="row", colnames=None, indent=' '): + def f(row,field,indent=' '): + if isinstance(row,Row): + spc = indent+' \n' + items = [f(row[x],x,indent+' ') for x in row] + return '%s<%s>\n%s\n%s' % ( + indent, + field, + spc.join(item for item in items if item), + indent, + field) + elif not callable(row): + if REGEX_ALPHANUMERIC.match(field): + return '%s<%s>%s' % (indent,field,row,field) + else: + return '%s%s' % \ + (indent,field,row) + else: + return None + return f(self, row_name, indent=indent) + + def as_json(self, mode="object", default=None, colnames=None, + serialize=True, **kwargs): """ serializes the table to a JSON list of objects kwargs are passed to .as_dict method - only "object" mode supported + only "object" mode supported for single row + + serialize = False used by Rows.as_json TODO: return array mode with query column order """ + + def inner_loop(record, col): + (t, f) = col.split('.') + res = None + if not REGEX_TABLE_DOT_FIELD.match(col): + key = col + res = record._extra[col] + else: + key = f + if isinstance(record.get(t, None), Row): + res = record[t][f] + else: + res = record[f] + if mode == 'object': + return (key, res) + else: + return res + multi = any([isinstance(v, self.__class__) for v in self.values()]) - item = dict() + mode = mode.lower() + if not mode in ['object', 'array']: + raise SyntaxError('Invalid JSON serialization mode: %s' % mode) - if multi: - for v in self.as_dict(**kwargs).values(): - item.update(v) + if mode=='object' and colnames: + item = dict([inner_loop(self, col) for col in colnames]) + elif colnames: + item = [inner_loop(self, col) for col in colnames] else: - item = self.as_dict(**kwargs) + if not mode == 'object': + raise SyntaxError('Invalid JSON serialization mode: %s' % mode) - if have_serializers: - return serializers.json(item, - default=default or - serializers.custom_json) + if multi: + item = dict() + [item.update(**v.as_dict(**kwargs)) for v in self.values()] + else: + item = self.as_dict(**kwargs) + + if serialize: + if have_serializers: + return serializers.json(item, + default=default or + serializers.custom_json) + else: + try: + import json as simplejson + except ImportError: + import gluon.contrib.simplejson as simplejson + return simplejson.dumps(item) else: - try: - import json as simplejson - except ImportError: - import gluon.contrib.simplejson as simplejson - return simplejson.dumps(item) + return item ################################################################################ @@ -9592,30 +9647,14 @@ class Rows(object): """ serializes the table using sqlhtml.SQLTABLE (if present) """ + if strict: ncols = len(self.colnames) - def f(row,field,indent=' '): - if isinstance(row,Row): - spc = indent+' \n' - items = [f(row[x],x,indent+' ') for x in row] - return '%s<%s>\n%s\n%s' % ( - indent, - field, - spc.join(item for item in items if item), - indent, - field) - elif not callable(row): - if REGEX_ALPHANUMERIC.match(field): - return '%s<%s>%s' % (indent,field,row,field) - else: - return '%s%s' % \ - (indent,field,row) - else: - return None - return '<%s>\n%s\n' % ( - rows_name, - '\n'.join(f(row,row_name) for row in self), - rows_name) + return '<%s>\n%s\n' % (rows_name, + '\n'.join(row.as_xml(row_name=row_name, + colnames=self.colnames) for + row in self), rows_name) + import sqlhtml return sqlhtml.SQLTABLE(self).xml() @@ -9626,33 +9665,12 @@ class Rows(object): """ serializes the table to a JSON list of objects """ - mode = mode.lower() - if not mode in ['object', 'array']: - raise SyntaxError('Invalid JSON serialization mode: %s' % mode) - def inner_loop(record, col): - (t, f) = col.split('.') - res = None - if not REGEX_TABLE_DOT_FIELD.match(col): - key = col - res = record._extra[col] - else: - key = f - if isinstance(record.get(t, None), Row): - res = record[t][f] - else: - res = record[f] - if mode == 'object': - return (key, res) - else: - return res + items = [record.as_json(mode=mode, default=default, + serialize=False, + colnames=self.colnames) for + record in self] - if mode == 'object': - items = [dict([inner_loop(record, col) for col in - self.colnames]) for record in self] - else: - items = [[inner_loop(record, col) for col in self.colnames] - for record in self] if have_serializers: return serializers.json(items, default=default or @@ -9664,6 +9682,8 @@ class Rows(object): import gluon.contrib.simplejson as simplejson return simplejson.dumps(items) + # for consistent naming yet backwards compatible + as_csv = __str__ json = as_json ################################################################################ From 69fa34b3c5d0a564dc489484e3a5fb90fd110fab Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 13 Jan 2013 13:15:38 -0600 Subject: [PATCH 15/26] minor fix in parse_as_rest --- VERSION | 2 +- gluon/dal.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index ca03c054..5dfa5b06 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.13.13.07.43 +Version 2.4.1-alpha.2+timestamp.2013.01.13.13.14.47 diff --git a/gluon/dal.py b/gluon/dal.py index 0f8b3967..5cdd8adb 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -7273,6 +7273,8 @@ def index(): i = 0 while i Date: Mon, 14 Jan 2013 09:18:43 -0600 Subject: [PATCH 16/26] parse_as_rest patch, thanks Denes --- VERSION | 2 +- gluon/dal.py | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 5dfa5b06..0833870b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.13.13.14.47 +Version 2.4.1-alpha.2+timestamp.2013.01.14.09.18.01 diff --git a/gluon/dal.py b/gluon/dal.py index 5cdd8adb..8e58674c 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -7351,15 +7351,19 @@ def index(): ref = tag[tag.find('[')+1:-1] if '.' in ref and otable: table,field = ref.split('.') - # print table,field + selfld = '_id' + if db[table][field].type.startswith('reference '): + refs = [ x.name for x in db[otable] if x.type == db[table][field].type ] + if refs: + selfld = refs[0] if nested_select: try: - dbset=db(db[table][field].belongs(dbset._select(db[otable]._id))) + dbset=db(db[table][field].belongs(dbset._select(db[otable][selfld]))) except ValueError: return Row({'status':400,'pattern':pattern, 'error':'invalid path','response':None}) else: - items = [item.id for item in dbset.select(db[otable]._id)] + items = [item.id for item in dbset.select(db[otable][selfld])] dbset=db(db[table][field].belongs(items)) else: table = ref From 10af0102f49c48da5d908f9c68b26bf214f52411 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 14 Jan 2013 09:42:59 -0600 Subject: [PATCH 17/26] fixed issue 1273, default int as_dict, thanks Alan --- VERSION | 2 +- gluon/dal.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 0833870b..83c8712d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.14.09.18.01 +Version 2.4.1-alpha.2+timestamp.2013.01.14.09.41.53 diff --git a/gluon/dal.py b/gluon/dal.py index 8e58674c..b754468f 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -9577,6 +9577,22 @@ class Rows(object): :param storage_to_dict: when True returns a dict, otherwise a list(default True) :param datetime_to_str: convert datetime fields as strings (default True) """ + + # test for multiple rows + multi = False + f = self.first() + if f: + multi = any([isinstance(v, f.__class__) for v in f.values()]) + if (not "." in key) and multi: + # No key provided, default to int indices + def new_key(): + i = 0 + while True: + yield i + i += 1 + key_generator = new_key() + key = lambda r: key_generator.next() + rows = self.as_list(compact, storage_to_dict, datetime_to_str, custom_types) if isinstance(key,str) and key.count('.')==1: (table, field) = key.split('.') From 9dbf0bbbcd7fca84e85ba6a86f621744d08ef654 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 14 Jan 2013 09:44:47 -0600 Subject: [PATCH 18/26] fixed issue 1276, preserving subject encoding in IMAP adapter, thanks Alan --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 83c8712d..25d7f643 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.14.09.41.53 +Version 2.4.1-alpha.2+timestamp.2013.01.14.09.43.37 From c60fffc34a3f8b3d541b08d7a86667c6b9ad62e0 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 14 Jan 2013 15:26:46 -0600 Subject: [PATCH 19/26] gluon/scheduler.py --- VERSION | 2 +- gluon/scheduler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 25d7f643..6c991fa7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.14.09.43.37 +Version 2.4.1-alpha.2+timestamp.2013.01.14.15.25.58 diff --git a/gluon/scheduler.py b/gluon/scheduler.py index 57a2937a..a3da2d35 100644 --- a/gluon/scheduler.py +++ b/gluon/scheduler.py @@ -579,7 +579,7 @@ class Scheduler(MetaScheduler): break except: db.rollback() - logger.error('TICKER(%s): error popping tasks', self.worker_name) + logger.error('%s: error popping tasks', self.worker_name) x += 1 time.sleep(0.5) From c1a534bee26339358cd031ea991cfe00ecb10c28 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 14 Jan 2013 15:30:01 -0600 Subject: [PATCH 20/26] fixing issue 1276 again, thanks Alan --- VERSION | 2 +- gluon/dal.py | 49 ++++++++++++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/VERSION b/VERSION index 6c991fa7..479e2215 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.14.15.25.58 +Version 2.4.1-alpha.2+timestamp.2013.01.14.15.29.20 diff --git a/gluon/dal.py b/gluon/dal.py index b754468f..a6bb48fb 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -5841,16 +5841,22 @@ class IMAPAdapter(NoSQLAdapter): else: return None + @staticmethod + def header_represent(f, r): + from email.header import decode_header + text, encoding = decode_header(f)[0] + return text + def encode_text(self, text, charset, errors="replace"): """ convert text for mail to unicode""" if text is None: text = "" else: if isinstance(text, str): - if charset is not None: - text = unicode(text, charset, errors) - else: + if charset is None: text = unicode(text, "utf-8", errors) + else: + text = unicode(text, charset, errors) else: raise Exception("Unsupported mail text type %s" % type(text)) return text.encode("utf-8") @@ -5952,6 +5958,7 @@ class IMAPAdapter(NoSQLAdapter): Field("mime", "string", writable=False), Field("email", "string", writable=False, readable=False), Field("attachments", "list:string", writable=False, readable=False), + Field("encoding") # main charset detected ) # Set a special _mailbox attribute for storing @@ -5959,6 +5966,11 @@ class IMAPAdapter(NoSQLAdapter): self.db[name].mailbox = \ self.connection.mailbox_names[name] + # decode quoted printable + self.db[name].to.represent = self.db[name].cc.represent = \ + self.db[name].bcc.represent = self.db[name].sender.represent = \ + self.db[name].subject.represent = self.header_represent + # Set the db instance mailbox collections self.db.mailboxes = self.connection.mailbox_names return self.db.mailboxes @@ -5981,12 +5993,11 @@ class IMAPAdapter(NoSQLAdapter): query = self.common_filter(query, [self.get_query_mailbox(query),]) import email - import email.header - decode_header = email.header.decode_header # get records from imap server with search + fetch # convert results to a dictionary tablename = None fetch_results = list() + if isinstance(query, Query): tablename = self.get_table(query) mailbox = self.connection.mailbox_names.get(tablename, None) @@ -6087,6 +6098,8 @@ class IMAPAdapter(NoSQLAdapter): # pending: search flags states trough the email message # instances for correct output + # preserve subject encoding (ASCII/quoted printable) + if "%s.id" % tablename in fieldnames: item_dict["%s.id" % tablename] = n if "%s.created" % tablename in fieldnames: @@ -6097,17 +6110,17 @@ class IMAPAdapter(NoSQLAdapter): # If there is no encoding found in the message header # force utf-8 replacing characters (change this to # module's defaults). Applies to .sender, .to, .cc and .bcc fields - item_dict["%s.sender" % tablename] = self.encode_text(message["From"], charset) + item_dict["%s.sender" % tablename] = message["From"] if "%s.to" % tablename in fieldnames: - item_dict["%s.to" % tablename] = self.encode_text(message["To"], charset) + item_dict["%s.to" % tablename] = message["To"] if "%s.cc" % tablename in fieldnames: if "Cc" in message.keys(): - item_dict["%s.cc" % tablename] = self.encode_text(message["Cc"], charset) + item_dict["%s.cc" % tablename] = message["Cc"] else: item_dict["%s.cc" % tablename] = "" if "%s.bcc" % tablename in fieldnames: if "Bcc" in message.keys(): - item_dict["%s.bcc" % tablename] = self.encode_text(message["Bcc"], charset) + item_dict["%s.bcc" % tablename] = message["Bcc"] else: item_dict["%s.bcc" % tablename] = "" if "%s.deleted" % tablename in fieldnames: @@ -6121,17 +6134,13 @@ class IMAPAdapter(NoSQLAdapter): if "%s.seen" % tablename in fieldnames: item_dict["%s.seen" % tablename] = "\\Seen" in flags if "%s.subject" % tablename in fieldnames: - subject = message["Subject"] - decoded_subject = decode_header(subject) - text = decoded_subject[0][0] - encoding = decoded_subject[0][1] - if encoding in (None, ""): - encoding = charset - item_dict["%s.subject" % tablename] = self.encode_text(text, encoding) + item_dict["%s.subject" % tablename] = message["Subject"] if "%s.answered" % tablename in fieldnames: item_dict["%s.answered" % tablename] = "\\Answered" in flags if "%s.mime" % tablename in fieldnames: item_dict["%s.mime" % tablename] = message.get_content_type() + if "%s.encoding" % tablename in fieldnames: + item_dict["%s.encoding" % tablename] = charset # Here goes the whole RFC822 body as an email instance # for controller side custom processing @@ -6139,7 +6148,8 @@ class IMAPAdapter(NoSQLAdapter): # >> email.message_from_string(raw string) # returns a Message object for enhanced object processing if "%s.email" % tablename in fieldnames: - item_dict["%s.email" % tablename] = self.encode_text(raw_message, charset) + # WARNING: no encoding performed (raw message) + item_dict["%s.email" % tablename] = raw_message # Size measure as suggested in a Velocity Reviews post # by Tim Williams: "how to get size of email attachment" @@ -6152,8 +6162,9 @@ class IMAPAdapter(NoSQLAdapter): attachments.append(part.get_payload(decode=True)) if "%s.content" % tablename in fieldnames: if "text" in part.get_content_maintype(): - payload = self.encode_text(part.get_payload(decode=True), charset) - content.append(payload) + part_charset = self.get_charset(part) + payload = part.get_payload(decode=True) + content.append(self.encode_text(payload, part_charset)) if "%s.size" % tablename in fieldnames: if part is not None: size += len(str(part)) From 1c69eb4ce4c4faa6b66856164b3ce50a69f87cf9 Mon Sep 17 00:00:00 2001 From: Massimo Date: Tue, 15 Jan 2013 13:08:27 -0600 Subject: [PATCH 21/26] fixed git errors issues, thanks Alan, wonderful work --- VERSION | 2 +- applications/admin/controllers/default.py | 63 +++++++++++++---------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/VERSION b/VERSION index 479e2215..7a22c5a4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.14.15.29.20 +Version 2.4.1-alpha.2+timestamp.2013.01.15.13.07.36 diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index ba67a9f5..502ab404 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -15,7 +15,13 @@ from glob import glob import shutil import platform try: - from git import * + import git + GIT_ERRORS = (git.GitCommandError, git.InvalidGitRepositoryError, + git.NoSuchPathError) + if git.__version__ >= '0.3.1': + GIT_ERRORS += (git.CacheError, git.CheckoutError, + git.ODBError, git.ParseError, + git.UnmergedEntriesError) have_git = True except ImportError: have_git = False @@ -240,10 +246,10 @@ def site(): redirect(URL(r=request)) target = os.path.join(apath(r=request), form_update.vars.name) try: - new_repo = Repo.clone_from(form_update.vars.url, target) + new_repo = git.Repo.clone_from(form_update.vars.url, target) session.flash = T('new application "%s" imported', form_update.vars.name) - except GitCommandError, err: + except git.GitCommandError, err: session.flash = T('Invalid git repository specified.') redirect(URL(r=request)) @@ -1726,29 +1732,31 @@ def git_pull(): {T('Cancel'): URL('site')}) if dialog.accepted: try: - repo = Repo(os.path.join(apath(r=request), app)) + repo = git.Repo(os.path.join(apath(r=request), app)) origin = repo.remotes.origin origin.fetch() origin.pull() session.flash = T("Application updated via git pull") redirect(URL('site')) - except CheckoutError, message: - session.flash = T("Pull failed, certain files could not be checked out. Check logs for details.") - redirect(URL('site')) - except UnmergedEntriesError: - session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.") - redirect(URL('site')) + except GIT_ERRORS, e: + error_type = type(e) + if 'CheckoutError' in error_type: + session.flash = T("Pull failed, certain files could not be checked out. Check logs for details.") + redirect(URL('site')) + elif 'UnmergedEntriesError' in error_type: + session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.") + redirect(URL('site')) + elif 'GitCommandError' in error_type: + session.flash = T( + "Pull failed, git exited abnormally. See logs for details.") + redirect(URL('site')) + else: + session.flash = T( + "Git error: %s %s" % (error_type, str(e))) + redirect(URL('site')) except AssertionError: session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.") redirect(URL('site')) - except GitCommandError, status: - session.flash = T( - "Pull failed, git exited abnormally. See logs for details.") - redirect(URL('site')) - except Exception, e: - session.flash = T( - "Pull failed, git exited abnormally. See logs for details.") - redirect(URL('site')) elif 'cancel' in request.vars: redirect(URL('site')) return dict(app=app, dialog=dialog) @@ -1766,7 +1774,7 @@ def git_push(): form.process() if form.accepted: try: - repo = Repo(os.path.join(apath(r=request), app)) + repo = git.Repo(os.path.join(apath(r=request), app)) index = repo.index index.add([apath(r=request) + app + '/*']) new_commit = index.commit(form.vars.changelog) @@ -1775,11 +1783,14 @@ def git_push(): session.flash = T( "Git repo updated with latest application changes.") redirect(URL('site')) - except UnmergedEntriesError: - session.flash = T("Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.") - redirect(URL('site')) - except Exception, e: - session.flash = T( - "Push failed, git exited abnormally. See logs for details.") - redirect(URL('site')) + except GIT_ERRORS, e: + error_type = type(e) + if "UnmergedEntriesError" in error_type: + session.flash = T("Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.") + redirect(URL('site')) + else: + session.flash = T( + "Git error: %s %s" % (error_type, str(e))) + redirect(URL('site')) return dict(app=app, form=form) + From cea9145400f074194d013cce45ca57b715984c3a Mon Sep 17 00:00:00 2001 From: Massimo Date: Tue, 15 Jan 2013 13:09:59 -0600 Subject: [PATCH 22/26] IMAP attachments in .select(), thanks Alan --- VERSION | 2 +- gluon/dal.py | 81 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 48 insertions(+), 35 deletions(-) diff --git a/VERSION b/VERSION index 7a22c5a4..67536eeb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.15.13.07.36 +Version 2.4.1-alpha.2+timestamp.2013.01.15.13.09.31 diff --git a/gluon/dal.py b/gluon/dal.py index a6bb48fb..a443db4b 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -5565,7 +5565,8 @@ class IMAPAdapter(NoSQLAdapter): subject string mime string The mime header declaration email string The complete RFC822 message** - attachments list:string Each non text decoded part as string + 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 message string @@ -5957,8 +5958,8 @@ class IMAPAdapter(NoSQLAdapter): Field("subject", "string", writable=False), Field("mime", "string", writable=False), Field("email", "string", writable=False, readable=False), - Field("attachments", "list:string", writable=False, readable=False), - Field("encoding") # main charset detected + Field("attachments", list, writable=False, readable=False), + Field("encoding") ) # Set a special _mailbox attribute for storing @@ -6070,11 +6071,11 @@ class IMAPAdapter(NoSQLAdapter): else: allfields = False if allfields: - fieldnames = ["%s.%s" % (tablename, field) for field in self.search_fields.keys()] + colnames = ["%s.%s" % (tablename, field) for field in self.search_fields.keys()] else: - fieldnames = ["%s.%s" % (tablename, field.name) for field in fields] + colnames = ["%s.%s" % (tablename, field.name) for field in fields] - for k in fieldnames: + for k in colnames: imapfields_dict[k] = k imapqry_list = list() @@ -6100,46 +6101,46 @@ class IMAPAdapter(NoSQLAdapter): # preserve subject encoding (ASCII/quoted printable) - if "%s.id" % tablename in fieldnames: + if "%s.id" % tablename in colnames: item_dict["%s.id" % tablename] = n - if "%s.created" % tablename in fieldnames: + if "%s.created" % tablename in colnames: item_dict["%s.created" % tablename] = self.convert_date(message["Date"]) - if "%s.uid" % tablename in fieldnames: + if "%s.uid" % tablename in colnames: item_dict["%s.uid" % tablename] = uid - if "%s.sender" % tablename in fieldnames: + if "%s.sender" % tablename in colnames: # If there is no encoding found in the message header # force utf-8 replacing characters (change this to # module's defaults). Applies to .sender, .to, .cc and .bcc fields item_dict["%s.sender" % tablename] = message["From"] - if "%s.to" % tablename in fieldnames: + if "%s.to" % tablename in colnames: item_dict["%s.to" % tablename] = message["To"] - if "%s.cc" % tablename in fieldnames: + if "%s.cc" % tablename in colnames: if "Cc" in message.keys(): item_dict["%s.cc" % tablename] = message["Cc"] else: item_dict["%s.cc" % tablename] = "" - if "%s.bcc" % tablename in fieldnames: + if "%s.bcc" % tablename in colnames: if "Bcc" in message.keys(): item_dict["%s.bcc" % tablename] = message["Bcc"] else: item_dict["%s.bcc" % tablename] = "" - if "%s.deleted" % tablename in fieldnames: + if "%s.deleted" % tablename in colnames: item_dict["%s.deleted" % tablename] = "\\Deleted" in flags - if "%s.draft" % tablename in fieldnames: + if "%s.draft" % tablename in colnames: item_dict["%s.draft" % tablename] = "\\Draft" in flags - if "%s.flagged" % tablename in fieldnames: + if "%s.flagged" % tablename in colnames: item_dict["%s.flagged" % tablename] = "\\Flagged" in flags - if "%s.recent" % tablename in fieldnames: + if "%s.recent" % tablename in colnames: item_dict["%s.recent" % tablename] = "\\Recent" in flags - if "%s.seen" % tablename in fieldnames: + if "%s.seen" % tablename in colnames: item_dict["%s.seen" % tablename] = "\\Seen" in flags - if "%s.subject" % tablename in fieldnames: + if "%s.subject" % tablename in colnames: item_dict["%s.subject" % tablename] = message["Subject"] - if "%s.answered" % tablename in fieldnames: + if "%s.answered" % tablename in colnames: item_dict["%s.answered" % tablename] = "\\Answered" in flags - if "%s.mime" % tablename in fieldnames: + if "%s.mime" % tablename in colnames: item_dict["%s.mime" % tablename] = message.get_content_type() - if "%s.encoding" % tablename in fieldnames: + if "%s.encoding" % tablename in colnames: item_dict["%s.encoding" % tablename] = charset # Here goes the whole RFC822 body as an email instance @@ -6147,7 +6148,7 @@ class IMAPAdapter(NoSQLAdapter): # The message is stored as a raw string # >> email.message_from_string(raw string) # returns a Message object for enhanced object processing - if "%s.email" % tablename in fieldnames: + if "%s.email" % tablename in colnames: # WARNING: no encoding performed (raw message) item_dict["%s.email" % tablename] = raw_message @@ -6157,19 +6158,31 @@ class IMAPAdapter(NoSQLAdapter): # To retrieve the server size for representation would add a new # fetch transaction to the process for part in message.walk(): - if "%s.attachments" % tablename in fieldnames: - if not "text" in part.get_content_maintype(): - attachments.append(part.get_payload(decode=True)) - if "%s.content" % tablename in fieldnames: - if "text" in part.get_content_maintype(): - part_charset = self.get_charset(part) + maintype = part.get_content_maintype() + if ("%s.attachments" % tablename in colnames) or \ + ("%s.content" % tablename in colnames): + if "%s.attachments" % tablename in colnames: + if not ("text" in maintype): + payload = part.get_payload(decode=True) + if payload: + attachment = { + "payload": payload, + "filename": part.get_filename(), + "encoding": part.get_content_charset(), + "mime": part.get_content_type(), + "disposition": part["Content-Disposition"]} + attachments.append(attachment) + if "%s.content" % tablename in colnames: payload = part.get_payload(decode=True) - content.append(self.encode_text(payload, part_charset)) - if "%s.size" % tablename in fieldnames: + part_charset = self.get_charset(part) + if "text" in maintype: + if payload: + content.append(self.encode_text(payload, part_charset)) + if "%s.size" % tablename in colnames: if part is not None: size += len(str(part)) item_dict["%s.content" % tablename] = bar_encode(content) - item_dict["%s.attachments" % tablename] = bar_encode(attachments) + item_dict["%s.attachments" % tablename] = attachments item_dict["%s.size" % tablename] = size imapqry_list.append(item_dict) @@ -6177,12 +6190,12 @@ class IMAPAdapter(NoSQLAdapter): # creation (sends an array or lists) for item_dict in imapqry_list: imapqry_array_item = list() - for fieldname in fieldnames: + for fieldname in colnames: imapqry_array_item.append(item_dict[fieldname]) imapqry_array.append(imapqry_array_item) # parse result and return a rows object - colnames = fieldnames + colnames = colnames processor = attributes.get('processor',self.parse) return processor(imapqry_array, fields, colnames) From 97385d331009f535353cadc0de3575dba7a2fd0e Mon Sep 17 00:00:00 2001 From: Massimo Date: Tue, 15 Jan 2013 13:19:54 -0600 Subject: [PATCH 23/26] another parse_as_rest patch, thanks Denes --- VERSION | 2 +- gluon/dal.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 67536eeb..a56b2937 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.15.13.09.31 +Version 2.4.1-alpha.2+timestamp.2013.01.15.13.19.22 diff --git a/gluon/dal.py b/gluon/dal.py index a443db4b..11f47fe4 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -7378,8 +7378,10 @@ def index(): selfld = '_id' if db[table][field].type.startswith('reference '): refs = [ x.name for x in db[otable] if x.type == db[table][field].type ] - if refs: - selfld = refs[0] + else: + refs = [ x.name for x in db[table]._referenced_by if x.tablename==otable ] + if refs: + selfld = refs[0] if nested_select: try: dbset=db(db[table][field].belongs(dbset._select(db[otable][selfld]))) From 16b37b1061c73a37a17979dbf4acba2f568c89ec Mon Sep 17 00:00:00 2001 From: Massimo Date: Tue, 15 Jan 2013 13:26:14 -0600 Subject: [PATCH 24/26] minor refactoring to avoid compatibility problems --- VERSION | 2 +- gluon/widget.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index a56b2937..7c49317e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.15.13.19.22 +Version 2.4.1-alpha.2+timestamp.2013.01.15.13.25.43 diff --git a/gluon/widget.py b/gluon/widget.py index df279372..86ee37ed 100644 --- a/gluon/widget.py +++ b/gluon/widget.py @@ -510,8 +510,7 @@ class web2pyDialog(object): if not options.taskbar: thread.start_new_thread(start_browser, - (get_url(ip, proto=proto, port=port),), - dict(startup=True)) + (get_url(ip, proto=proto, port=port), True)) self.password.configure(state='readonly') [ip.configure(state='disabled') for ip in self.ips.values()] From ad684bfdfcf10bf80bc81f92795031a24dc0f228 Mon Sep 17 00:00:00 2001 From: Massimo Date: Tue, 15 Jan 2013 15:54:29 -0600 Subject: [PATCH 25/26] scheduler patch, thanks Niphlod --- VERSION | 2 +- gluon/scheduler.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 7c49317e..616d55a6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.15.13.25.43 +Version 2.4.1-alpha.2+timestamp.2013.01.15.15.53.29 diff --git a/gluon/scheduler.py b/gluon/scheduler.py index a3da2d35..a866acc5 100644 --- a/gluon/scheduler.py +++ b/gluon/scheduler.py @@ -556,7 +556,7 @@ class Scheduler(MetaScheduler): self.die() def wrapped_assign_tasks(self, db): - db.commit() # ?don't know if it's useful, let's be completely sure + db.commit() #db.commit() only for Mysql x = 0 while x < 10: try: @@ -571,6 +571,7 @@ class Scheduler(MetaScheduler): def wrapped_pop_task(self): db = self.db + db.commit() #another nifty db.commit() only for Mysql x = 0 while x < 10: try: From a4e926b6374094d59251b15c4964ecd8be62417f Mon Sep 17 00:00:00 2001 From: mdipierro Date: Tue, 15 Jan 2013 22:23:00 -0600 Subject: [PATCH 26/26] fixed recent bug in dal, thanks Denes --- VERSION | 2 +- gluon/dal.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 616d55a6..952a16a9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.4.1-alpha.2+timestamp.2013.01.15.15.53.29 +Version 2.4.1-alpha.2+timestamp.2013.01.15.22.22.06 diff --git a/gluon/dal.py b/gluon/dal.py index 11f47fe4..b686d6f5 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -8204,7 +8204,7 @@ class Table(object): return fields def _insert(self, **fields): - fields = self._default(fields) + fields = self._defaults(fields) return self._db._adapter._insert(self, self._listify(fields)) def insert(self, **fields):