diff --git a/VERSION b/VERSION index 01f6408e..d6b339bd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-10-27 14:37:32) stable +Version 2.2.1 (2012-10-30 11:09:19) stable diff --git a/gluon/cache.py b/gluon/cache.py index 6c52c418..60eaed00 100644 --- a/gluon/cache.py +++ b/gluon/cache.py @@ -147,6 +147,7 @@ class CacheInRam(CacheAbstract): def __init__(self, request=None): self.initialized = False self.request = request + self.storage = {} def initialize(self): if self.initialized: @@ -303,6 +304,7 @@ class CacheOnDisk(CacheAbstract): self.initialized = False self.request = request self.folder = folder + self.storage = {} def initialize(self): if self.initialized: diff --git a/gluon/contrib/login_methods/dropbox_account.py b/gluon/contrib/login_methods/dropbox_account.py index 8bfc7079..597320ee 100644 --- a/gluon/contrib/login_methods/dropbox_account.py +++ b/gluon/contrib/login_methods/dropbox_account.py @@ -99,13 +99,21 @@ class DropboxAccount(object): redirect('https://www.dropbox.com/logout') return next + def get_client(self): + access_token = current.session.dropbox_access_token + self.sess.set_token(access_token[0], access_token[1]) + self.client = client.DropboxClient(self.sess) + def put(self, filename, file): + if not hasattr(self,'client'): self.get_client() return json.loads(self.client.put_file(filename, file))['bytes'] def get(self, filename, file): + if not hasattr(self,'client'): self.get_client() return self.client.get_file(filename) def dir(self, path): + if not hasattr(self,'client'): self.get_client() return json.loads(self.client.metadata(path)) diff --git a/gluon/dal.py b/gluon/dal.py index fafe0727..1082aa1f 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -26,6 +26,7 @@ including: - DB2 - Interbase - Ingres +- Informix - SapDB (experimental) - Cubrid (experimental) - CouchDB (experimental) @@ -179,7 +180,7 @@ if PYTHON_VERSION == 2: bytes, unicode = str, unicode else: import pickle - from io import StringIO as StringIO + from io import StringIO as StringIO import copyreg long = int hashlib_md5 = lambda s: hashlib.md5(bytes(s,'utf8')) @@ -255,6 +256,7 @@ REGEX_STORE_PATTERN = re.compile('\.(?P\w{1,5})$') REGEX_QUOTES = re.compile("'[^']*'") REGEX_ALPHANUMERIC = re.compile('^[0-9a-zA-Z]\w*$') REGEX_PASSWORD = re.compile('\://([^:@]*)\:') +REGEX_NOPASSWD = re.compile('(?<=\:)([^:@/]+)(?=@.+)') # list of drivers will be built on the fly # and lists only what is available @@ -2105,7 +2107,7 @@ class SQLiteAdapter(BaseAdapter): dbpath = pjoin( self.folder.decode(path_encoding).encode('utf8'), dbpath) else: - dbpath = pjoin(self.folder, dbpath) + dbpath = pjoin(self.folder, dbpath) if not 'check_same_thread' in driver_args: driver_args['check_same_thread'] = False if not 'detect_types' in driver_args: @@ -6660,6 +6662,35 @@ class DAL(object): """ BaseAdapter.set_folder(folder) + @staticmethod + def get_instances(): + """ + Returns a dictionary with uri as key with timings and defined tables + {'sqlite://storage.sqlite': { + 'dbstats': [(select auth_user.email from auth_user, 0.02009)], + 'dbtables': { + 'defined': ['auth_cas', 'auth_event', 'auth_group', + 'auth_membership', 'auth_permission', 'auth_user'], + 'lazy': '[]' + } + } + } + """ + dbs = getattr(THREAD_LOCAL,'db_instances',{}).items() + infos = {} + for db_uid, db_group in dbs: + for db in db_group: + if not db._uri: + continue + k = REGEX_NOPASSWD.sub('******',db._uri) + infos[k] = dict(dbstats = [(row[0], row[1]) for row in db._timings], + dbtables = {'defined': + sorted(list(set(db.tables) - + set(db._LAZY_TABLES.keys()))), + 'lazy': sorted(db._LAZY_TABLES.keys())} + ) + return infos + @staticmethod def distributed_transaction_begin(*instances): if not instances: diff --git a/gluon/globals.py b/gluon/globals.py index 7f588357..60cc86a6 100644 --- a/gluon/globals.py +++ b/gluon/globals.py @@ -48,7 +48,6 @@ except ImportError: have_minify = False regex_session_id = re.compile('^([\w\-]+/)?[\w\-\.]+$') -regex_nopasswd = re.compile('(?<=\:)([^:@/]+)(?=@.+)') __all__ = ['Request', 'Response', 'Session'] @@ -127,7 +126,10 @@ class Request(Storage): If request comes in over HTTP, redirect it to HTTPS and secure the session. """ - if not global_settings.cronjob and not self.is_https: + cmd_opts = global_settings.cmd_options + #checking if this is called within the scheduler or within the shell + #in addition to checking if it's not a cronjob + if not cmd_opts.shell and not cmd_opts.scheduler and not global_settings.cronjob and not self.is_https: current.session.forget() redirect(URL(scheme='https', args=self.args, vars=self.vars)) @@ -434,22 +436,16 @@ class Response(Storage): BUTTON = TAG.button admin = URL("admin", "default", "design", args=current.request.application) - from gluon.dal import THREAD_LOCAL - if hasattr(THREAD_LOCAL, 'instances'): - dbstats = [TABLE(*[TR(PRE(row[0]), '%.2fms' % (row[1] * 1000)) - for row in i.db._timings]) - for i in THREAD_LOCAL.instances] - dbtables = dict([(regex_nopasswd.sub('******', i.uri), - {'defined': - sorted(list(set(i.db.tables) - - set(i.db._LAZY_TABLES.keys()))) or - '[no defined tables]', - 'lazy': sorted(i.db._LAZY_TABLES.keys()) or - '[no lazy tables]'}) - for i in THREAD_LOCAL.instances]) - else: - dbstats = [] # if no db or on GAE - dbtables = {} + from gluon.dal import DAL + dbstats = [] + dbtables = {} + infos = DAL.get_instances() + for k,v in infos.iteritems(): + dbstats.append(TABLE(*[TR(PRE(row[0]),'%.2fms' % + (row[1]*1000)) + for row in v['dbstats']])) + dbtables[k] = dict(defined=v['dbtables']['defined'] or '[no defined tables]', + lazy=v['dbtables']['lazy'] or '[no lazy tables]') u = web2py_uuid() backtotop = A('Back to top', _href="#totop-%s" % u) return DIV( @@ -507,7 +503,7 @@ class Session(Storage): request = current.request if response is None: response = current.response - if separate == True: + if separate is True: separate = lambda session_name: session_name[-2:] self._unlock(response) if not masterapp: diff --git a/gluon/http.py b/gluon/http.py index 831c04d6..bd1f1411 100644 --- a/gluon/http.py +++ b/gluon/http.py @@ -140,7 +140,7 @@ class HTTP(BaseException): return self.message -def redirect(location, how=303, client_side=False): +def redirect(location='', how=303, client_side=False): if location: from gluon import current loc = location.replace('\r', '%0D').replace('\n', '%0A') @@ -150,3 +150,8 @@ def redirect(location, how=303, client_side=False): raise HTTP(how, 'You are being redirected here' % loc, Location=loc) + else: + from gluon import current + if client_side and current.request.ajax: + raise HTTP(200, **{'web2py-component-command': 'window.location.reload(true)'}) + \ No newline at end of file diff --git a/gluon/tools.py b/gluon/tools.py index e70723dd..b6fb9ace 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -1365,13 +1365,13 @@ class Auth(object): archive_names='%(tablename)s_archive', current_record='current_record'): """ - to enable full record vernionioning (including auth tables): + to enable full record versioning (including auth tables): auth = Auth(db) auth.define_tables(signature=True) # define our own tables db.define_table('mything',Field('name'),auth.signature) - auth.enable_record_vernining(tables=db) + auth.enable_record_versioning(tables=db) tables can be the db (all table) or a list of tables. only tables with modified_by and modified_on fiels (as created @@ -1386,7 +1386,7 @@ class Auth(object): Important: If you use auth.enable_record_versioning, do not use auth.archive or you will end up with duplicates. - auth.archive does explicitely what enable_record_versioning + auth.archive does explicitly what enable_record_versioning does automatically. """ @@ -1489,7 +1489,7 @@ class Auth(object): IS_NOT_IN_DB(db, '%s.username' % settings.table_user_name)] if not settings.username_case_sensitive: is_unique_username.insert(1, IS_LOWER()) - table = db.define_table( + db.define_table( settings.table_user_name, Field('first_name', length=128, default='', label=self.messages.label_first_name, @@ -1522,7 +1522,7 @@ class Auth(object): fake_migrate=fake_migrate, format='%(username)s')) else: - table = db.define_table( + db.define_table( settings.table_user_name, Field('first_name', length=128, default='', label=self.messages.label_first_name, @@ -1555,7 +1555,7 @@ class Auth(object): if not settings.table_group_name in db.tables: extra_fields = settings.extra_fields.get( settings.table_group_name, []) + signature_list - table = db.define_table( + db.define_table( settings.table_group_name, Field('role', length=512, default='', label=self.messages.label_role, @@ -1573,7 +1573,7 @@ class Auth(object): if not settings.table_membership_name in db.tables: extra_fields = settings.extra_fields.get( settings.table_membership_name, []) + signature_list - table = db.define_table( + db.define_table( settings.table_membership_name, Field('user_id', reference_table_user, label=self.messages.label_user_id), @@ -1587,7 +1587,7 @@ class Auth(object): if not settings.table_permission_name in db.tables: extra_fields = settings.extra_fields.get( settings.table_permission_name, []) + signature_list - table = db.define_table( + db.define_table( settings.table_permission_name, Field('group_id', reference_table_group, label=self.messages.label_group_id), @@ -1605,7 +1605,7 @@ class Auth(object): settings.table_permission_name, migrate), fake_migrate=fake_migrate)) if not settings.table_event_name in db.tables: - table = db.define_table( + db.define_table( settings.table_event_name, Field('time_stamp', 'datetime', default=current.request.now, @@ -1629,7 +1629,7 @@ class Auth(object): now = current.request.now if settings.cas_domains: if not settings.table_cas_name in db.tables: - table = db.define_table( + db.define_table( settings.table_cas_name, Field('user_id', reference_table_user, default=None, label=self.messages.label_user_id), @@ -1773,8 +1773,6 @@ class Auth(object): """ logins user as specified by usernname (or email) and password """ - request = current.request - session = current.session table_user = self.table_user() if self.settings.login_userfield: userfield = self.settings.login_userfield @@ -2669,7 +2667,6 @@ class Auth(object): redirect(self.settings.login_url) db = self.db table_user = self.table_user() - usern = self.settings.table_user_name s = db(table_user.id == self.user.id) request = current.request @@ -2717,7 +2714,7 @@ class Auth(object): next = self.url(args=request.args) else: next = replace_id(next, form) - redirect(next) + redirect(next) return form def profile( @@ -2990,10 +2987,12 @@ class Auth(object): return self.id_group(self.user_group_role(user_id)) def user_group_role(self, user_id=None): + if not self.settings.create_user_groups: + return None if user_id: user = self.table_user()[user_id] else: - user = self.user + user = self.user return self.settings.create_user_groups % user def has_membership(self, group_id=None, user_id=None, role=None): @@ -4139,12 +4138,13 @@ class Service(object): return '' return value if args and args[0] in self.run_procedures: + import types r = universal_caller(self.run_procedures[args[0]], *args[1:], **dict(request.vars)) s = cStringIO.StringIO() if hasattr(r, 'export_to_csv_file'): r.export_to_csv_file(s) - elif r and isinstance(r[0], (dict, Storage)): + elif r and not isinstance(r, types.GeneratorType) and isinstance(r[0], (dict, Storage)): import csv writer = csv.writer(s) writer.writerow(r[0].keys()) @@ -4656,7 +4656,7 @@ class Wiki(object): html = page.body # @///function -> http://..../function html = replace_at_urls(html, URL) - # http://...jpg -> or embed html = replace_autolinks(html, lambda link: expand_one(link, {})) # @{component:name} -> html = replace_components(html, self.env) @@ -4898,14 +4898,15 @@ class Wiki(object): % self.force_prefix redirect(URL(args=('_edit', self.force_prefix + slug))) db.wiki_page.can_read.default = [Wiki.everybody] - db.wiki_page.can_edit.default = [auth.user_group_role()] + user_group_role = auth.user_group_role() + db.wiki_page.can_edit.default = [user_group_role] db.wiki_page.title.default = title_guess db.wiki_page.slug.default = slug if slug == 'wiki-menu': db.wiki_page.body.default = \ '- Menu Item > @////index\n- - Submenu > http://web2py.com' else: - db.wiki_page.body.default = '## %s\n\npage content' % title_guess + db.wiki_page.body.default = '## %s\n\npage content\n\n[[new page @////new_page]]\n' % title_guess vars = current.request.post_vars if vars.body: vars.body = vars.body.replace('://%s' % self.host, '://HOSTNAME') @@ -4924,12 +4925,20 @@ class Wiki(object): pagecontent.css('font-family', 'Monaco,Menlo,Consolas,"Courier New",monospace'); var prevbutton = $(''); + var mediabutton = $(''); var preview = $('
').hide(); + var previewmedia = $('
'); var table = $('form'); - prevbutton.insertBefore(table); preview.insertBefore(table); - prevbutton.on('click', function(e) { - e.preventDefault(); + prevbutton.insertBefore(table); + mediabutton.insertBefore(table); + previewmedia.insertBefore(table); + mediabutton.toggle(function() { + web2py_component('%(urlmedia)s', 'previewmedia'); + }, function() { + previewmedia.empty(); + }); + prevbutton.click(function() { if (prevbutton.hasClass('nopreview')) { prevbutton.addClass('preview').removeClass( 'nopreview').html('Edit Source'); @@ -4942,7 +4951,7 @@ class Wiki(object): } }) }) - """ % dict(url=URL(args=('_preview'))) + """ % dict(url=URL(args=('_preview')), urlmedia=URL(extension='load',args=('_editmedia'),vars=dict(embedded=1))) return dict(content=TAG[''](form, SCRIPT(script))) def editmedia(self, slug): @@ -4958,9 +4967,21 @@ class Wiki(object): row.filename.split('.')[-1])) self.auth.db.wiki_media.wiki_page.default = page.id self.auth.db.wiki_media.wiki_page.writable = False + links = [] + csv = True + if current.request.vars.embedded: + script = "var c = $('#wiki_page_body'); c.val(c.val() + $('%s').text()); return false;" + fragment = self.auth.db.wiki_media.id.represent + csv = False + links=[ + lambda row: + A('copy into source', _href='#', _onclick=script % (fragment(row.id, row))) + ] content = SQLFORM.grid( self.auth.db.wiki_media.wiki_page == page.id, orderby=self.auth.db.wiki_media.title, + links = links, + csv = csv, args=['_editmedia', slug], user_signature=False) return dict(content=content) diff --git a/gluon/utils.py b/gluon/utils.py index 52f8a58b..d2ee474d 100644 --- a/gluon/utils.py +++ b/gluon/utils.py @@ -37,7 +37,7 @@ try: except ImportError: try: from .aes import AES - except ImportError: + except (ImportError, ValueError): from contrib.aes import AES try: diff --git a/gluon/validators.py b/gluon/validators.py index af3f6629..0280126a 100644 --- a/gluon/validators.py +++ b/gluon/validators.py @@ -778,6 +778,8 @@ class IS_FLOAT_IN_RANGE(Validator): return (value, self.error_message) def formatter(self, value): + if value is None: + return None return str2dec(value).replace('.', self.dot) @@ -882,6 +884,8 @@ class IS_DECIMAL_IN_RANGE(Validator): return (value, self.error_message) def formatter(self, value): + if value is None: + return None return str2dec(value).replace('.', self.dot) @@ -2170,6 +2174,8 @@ class IS_DATE(Validator): return (value, translate(self.error_message) % self.extremes) def formatter(self, value): + if value is None: + return None format = self.format year = value.year y = '%.4i' % year @@ -2228,6 +2234,8 @@ class IS_DATETIME(Validator): return (value, translate(self.error_message) % self.extremes) def formatter(self, value): + if value is None: + return None format = self.format year = value.year y = '%.4i' % year diff --git a/scripts/sessions2trash.py b/scripts/sessions2trash.py index 3995014e..8609c480 100755 --- a/scripts/sessions2trash.py +++ b/scripts/sessions2trash.py @@ -95,8 +95,9 @@ class SessionSetDb(SessionSet): """Return list of SessionDb instances for existing sessions.""" sessions = [] table = current.response.session_db_table - for row in table._db(table.id > 0).select(): - sessions.append(SessionDb(row)) + if table: + for row in table._db(table.id > 0).select(): + sessions.append(SessionDb(row)) return sessions diff --git a/scripts/sync_languages.py b/scripts/sync_languages.py index ff36c779..b3212d5b 100755 --- a/scripts/sync_languages.py +++ b/scripts/sync_languages.py @@ -7,14 +7,10 @@ import sys import shutil import os -from gluon.languages import findT, utf8_repr +from gluon.languages import findT sys.path.insert(0, '.') -file = sys.argv[1] -apps = sys.argv[2:] - - def sync_language(d, data): ''' this function makes sure a translated string will be prefered over an untranslated string when syncing languages between apps. when both are translated, it prefers the @@ -37,35 +33,46 @@ def sync_language(d, data): return d -d = {} -for app in apps: - path = 'applications/%s/' % app - findT(path, file) - langfile = open(os.path.join(path, 'languages', '%s.py' % file)) +def sync_main(file, apps): + d = {} + for app in apps: + path = 'applications/%s/' % app + findT(path, file) + langfile = open(os.path.join(path, 'languages', '%s.py' % file)) + try: + data = eval(langfile.read()) + finally: + langfile.close() + + d = sync_language(d, data) + + + path = 'applications/%s/' % apps[-1] + file1 = os.path.join(path, 'languages', '%s.py' % file) + + f = open(file1, 'w') try: - data = eval(langfile.read()) + f.write('# coding: utf8\n') + f.write('{\n') + keys = d.keys() + keys.sort() + for key in keys: + f.write("'''%s''':'''%s''',\n" % (key.replace("'", "\\'"), str(d[key].replace("'", "\\'")))) + f.write('}\n') finally: - langfile.close() + f.close() + + oapps = reversed(apps[:-1]) + for app in oapps: + path2 = 'applications/%s/' % app + file2 = os.path.join(path2, 'languages', '%s.py' % file) + if file1 != file2: + shutil.copyfile(file1, file2) - d = sync_language(d, data) +if __name__ == "__main__": -path = 'applications/%s/' % apps[-1] -file1 = os.path.join(path, 'languages', '%s.py' % file) + file = sys.argv[1] + apps = sys.argv[2:] -f = open(file1, 'w') -try: - f.write('# coding: utf8\n') - f.write('{\n') - keys = d.keys() - keys.sort() - for key in keys: - f.write('%s:%s,\n' % (utf8_repr(key), utf8_repr(str(d[key])))) - f.write('}\n') -finally: - f.close() - -oapps = reversed(apps[:-1]) -for app in oapps: - path2 = 'applications/%s/' % app - file2 = os.path.join(path2, 'languages', '%s.py' % file) - shutil.copyfile(file1, file2) + sync_main(file, apps) + \ No newline at end of file