diff --git a/CHANGELOG b/CHANGELOG index bd6fb9cd..31b821fc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,7 @@ - Support for Google App Engine projections, thanks Christian - Field(... 'upload', default=path) now accepts a path to a local file as default value, if user does not upload a file. Relative path looks inside current application folder, thanks Marin - executesql(...,fields=,columns=) allows parsing of results in Rows, thanks Anthony +- Rows.find(lambda row: bool(), limitby=(0,1)) ### Auth improvements @@ -80,6 +81,7 @@ ### Other Improvements +- gluon/contrib/webclient.py makes it easy to create functional tests for app - DIV(..).elements(...replace=...), thanks Anthony - new layout based on Twitter Bootstrap - New generic views: generic.ics (Mac Mail Calendar) and generic.map (Google Maps) diff --git a/Makefile b/Makefile index 4eace126..6329b95a 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ update: wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py echo "remember that pymysql was tweaked" src: - echo 'Version 2.00.1 ('`date +%Y-%m-%d\ %H:%M:%S`') rc4' > VERSION + echo 'Version 2.0.6 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION ### rm -f all junk files make clean ### clean up baisc apps @@ -127,5 +127,5 @@ push: tag: git tag -l '$(S)' hg tag -l '$(S)' - make commit + make commit S='$(S)' make push diff --git a/VERSION b/VERSION index e88b45a9..1150a441 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.1 (2012-08-29 07:27:23) rc4 +Version 2.0.6 (2012-09-02 12:26:12) stable diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 1fb8cf9e..523af721 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -51,12 +51,12 @@ def log_progress(app,mode='EDIT',filename=None,progress=0): progress_file = os.path.join(apath(app, r=request), 'progress.log') now = str(request.now)[:19] if not os.path.exists(progress_file): - open(progress_file,'w').write('[%s] START\n' % now) + safe_open(progress_file,'w').write('[%s] START\n' % now) if filename: - open(progress_file,'a').write('[%s] %s %s: %s\n' % (now,mode,filename,progress)) + safe_open(progress_file,'a').write('[%s] %s %s: %s\n' % (now,mode,filename,progress)) def safe_open(a,b): - if DEMO_MODE and 'w' in b: + if DEMO_MODE and ('w' in b or 'a' in b): class tmp: def write(self,data): pass return tmp() @@ -140,8 +140,8 @@ def check_version(): return SPAN('You should upgrade to version %s' % version_number) else: return sp_button(URL('upgrade_web2py'), T('upgrade now')) \ - + XML(' %s' % version_number) - + + XML(' %s.%s.%s' \ + % version_number[:3]) def logout(): """ Logout handler """ @@ -455,11 +455,13 @@ def delete(): def enable(): app = get_app() filename = os.path.join(apath(app, r=request),'DISABLED') - if os.path.exists(filename): + if is_gae: + return SPAN(T('Not supported'),_style='color:yellow') + elif os.path.exists(filename): os.unlink(filename) return SPAN(T('Disable'),_style='color:green') else: - open(filename,'wb').write(time.ctime()) + safe_open(filename,'wb').write(time.ctime()) return SPAN(T('Enable'),_style='color:red') def peek(): diff --git a/applications/admin/languages/default.py b/applications/admin/languages/default.py index a9bd0be9..aca67965 100644 --- a/applications/admin/languages/default.py +++ b/applications/admin/languages/default.py @@ -33,6 +33,7 @@ 'created by': 'created by', 'crontab': 'crontab', 'currently running': 'currently running', +'currently saved or': 'currently saved or', 'database administration': 'database administration', 'Debug': 'Debug', 'defines tables': 'defines tables', @@ -43,16 +44,22 @@ 'Detailed traceback description': 'Detailed traceback description', 'direction: ltr': 'direction: ltr', 'Disable': 'Disable', +'docs': 'docs', 'download layouts': 'download layouts', 'download plugins': 'download plugins', 'Edit': 'Edit', 'Edit application': 'Edit application', +'edit views:': 'edit views:', +'Editing file "%s"': 'Editing file "%s"', 'Error snapshot': 'Error snapshot', 'Error ticket': 'Error ticket', 'Errors': 'Errors', 'Exception instance attributes': 'Exception instance attributes', 'exposes': 'exposes', +'exposes:': 'exposes:', 'extends': 'extends', +'file does not exist': 'file does not exist', +'file saved on %s': 'file saved on %s', 'filter': 'filter', 'Frames': 'Frames', 'Get from URL:': 'Get from URL:', @@ -63,8 +70,12 @@ 'inspect attributes': 'inspect attributes', 'Install': 'Install', 'Installed applications': 'Installed applications', +'invalid password.': 'invalid password.', +'Key bindings': 'Key bindings', +'Language files (static strings) updated': 'Language files (static strings) updated', 'languages': 'languages', 'Languages': 'Languages', +'Last saved on:': 'Last saved on:', 'loading...': 'loading...', 'locals': 'locals', 'Login': 'Login', @@ -72,12 +83,13 @@ 'Logout': 'Logout', 'Models': 'Models', 'models': 'models', -'modules': 'modules', 'Modules': 'Modules', +'modules': 'modules', 'New application wizard': 'New application wizard', 'New simple application': 'New simple application', 'Overwrite installed app': 'Overwrite installed app', 'Pack all': 'Pack all', +'Peeking at file': 'Peeking at file', 'Plugins': 'Plugins', 'plugins': 'plugins', 'Plural-Forms:': 'Plural-Forms:', @@ -87,11 +99,17 @@ 'Reload routes': 'Reload routes', 'request': 'request', 'response': 'response', +'restore': 'restore', +'revert': 'revert', 'rules are not defined': 'rules are not defined', 'rules:': 'rules:', "Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')", 'Running on %s': 'Running on %s', +'Save': 'Save', +'Save via Ajax': 'Save via Ajax', +'Saved file hash:': 'Saved file hash:', 'session': 'session', +'session expired': 'session expired', 'shell': 'shell', 'Site': 'Site', 'Start wizard': 'Start wizard', @@ -108,7 +126,9 @@ 'These files are served without processing, your images go here': 'These files are served without processing, your images go here', 'Ticket ID': 'Ticket ID', 'Ticket Missing': 'Ticket Missing', +'to previous version.': 'to previous version.', 'To create a plugin, name a file/folder plugin_[name]': 'To create a plugin, name a file/folder plugin_[name]', +'toggle breakpoint': 'toggle breakpoint', 'Traceback': 'Traceback', 'Translation strings for the application': 'Translation strings for the application', 'Uninstall': 'Uninstall', diff --git a/applications/admin/models/access.py b/applications/admin/models/access.py index 1669c526..22d9e03c 100644 --- a/applications/admin/models/access.py +++ b/applications/admin/models/access.py @@ -12,6 +12,9 @@ if request.env.web2py_runtime_gae: session_db = DAL('gae') session.connect(request, response, db=session_db) hosts = (http_host, ) + is_gae = True +else: + is_gae = False if request.env.http_x_forwarded_for or request.is_https: session.secure() @@ -27,7 +30,7 @@ try: raise HTTP(200, T('admin disabled because no admin password')) except IOError: import gluon.fileutils - if request.env.web2py_runtime_gae: + if is_gae: if gluon.fileutils.check_credentials(request): session.authorized = True session.last_time = time.time() diff --git a/applications/admin/views/default/design.html b/applications/admin/views/default/design.html index 1f1b0887..1934fc1d 100644 --- a/applications/admin/views/default/design.html +++ b/applications/admin/views/default/design.html @@ -223,16 +223,12 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi {{=editpluralsfile('languages',pfile,dict(nplurals=p[0]))}} - {{=peekfile('languages',pfile,dict(id=id))}}, + {{=peekfile('languages',pfile,dict(id=id))}} {{else:}} - {{=T("are not used yet")}}, + {{=T("are not used yet")}} {{pass}} {{pass}} - {{=T("rules:")}} - - {{=peekfile('gluon/contrib/rules', p[2], dict(app=app, id=id), p[3] if p[3]!='ok' else None)}} - {{pass}} ) diff --git a/gluon/contrib/rules/__init__.py b/applications/examples/languages/README similarity index 100% rename from gluon/contrib/rules/__init__.py rename to applications/examples/languages/README diff --git a/applications/welcome/languages/default.py b/applications/welcome/languages/default.py index 7fddedd2..5e6e8125 100644 --- a/applications/welcome/languages/default.py +++ b/applications/welcome/languages/default.py @@ -4,13 +4,119 @@ '!langname!': 'English (US)', '%s %%(shop)': '%s %%(shop)', '%s %%(shop[0])': '%s %%(shop[0])', +'%s %%{quark[0]}': '%s %%{quark[0]}', '%s %%{shop[0]}': '%s %%{shop[0]}', '%s %%{shop}': '%s %%{shop}', +'%Y-%m-%d': '%Y-%m-%d', '%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', '@markmin\x01**Hello World**': '**Hello World**', +'About': 'About', +'Access Control': 'Access Control', +'Administrative Interface': 'Administrative Interface', +'Ajax Recipes': 'Ajax Recipes', +'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?', +'Buy this book': 'Buy this book', +'Cannot be empty': 'Cannot be empty', +'Check to delete': 'Check to delete', +'Client IP': 'Client IP', +'Community': 'Community', +'Components and Plugins': 'Components and Plugins', +'Controller': 'Controller', +'Copyright': 'Copyright', +'Created By': 'Created By', +'Created On': 'Created On', +'customize me!': 'customize me!', +'Database': 'Database', +'DB Model': 'DB Model', +'Demo': 'Demo', +'Deployment Recipes': 'Deployment Recipes', +'Description': 'Description', +'Documentation': 'Documentation', +"Don't know what to do?": "Don't know what to do?", +'Download': 'Download', +'E-mail': 'E-mail', +'Email and SMS': 'Email and SMS', 'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g', 'enter date and time as %(format)s': 'enter date and time as %(format)s', +'Errors': 'Errors', +'FAQ': 'FAQ', +'First name': 'First name', +'Forms and Validators': 'Forms and Validators', +'Free Applications': 'Free Applications', +'Group %(group_id)s created': 'Group %(group_id)s created', +'Group ID': 'Group ID', +'Group uniquely assigned to user %(id)s': 'Group uniquely assigned to user %(id)s', +'Groups': 'Groups', 'Hello World': 'Hello World', 'Hello World ## comment': 'Hello World ', 'Hello World## comment': 'Hello World', +'Home': 'Home', +'How did you get here?': 'How did you get here?', +'Introduction': 'Introduction', +'Invalid email': 'Invalid email', +'Is Active': 'Is Active', +'Last name': 'Last name', +'Layout': 'Layout', +'Layout Plugins': 'Layout Plugins', +'Layouts': 'Layouts', +'Live Chat': 'Live Chat', +'Logged in': 'Logged in', +'Logged out': 'Logged out', +'Login': 'Login', +'Logout': 'Logout', +'Lost Password': 'Lost Password', +'Lost password?': 'Lost password?', +'Menu Model': 'Menu Model', +'Modified By': 'Modified By', +'Modified On': 'Modified On', +'My Sites': 'My Sites', +'Name': 'Name', +'Object or table name': 'Object or table name', +'Online examples': 'Online examples', +'Origin': 'Origin', +'Other Plugins': 'Other Plugins', +'Other Recipes': 'Other Recipes', +'Overview': 'Overview', +'Password': 'Password', +"Password fields don't match": "Password fields don't match", +'please input your password again': 'please input your password again', +'Plugins': 'Plugins', +'Powered by': 'Powered by', +'Preface': 'Preface', +'Profile': 'Profile', +'Python': 'Python', +'Quick Examples': 'Quick Examples', +'Recipes': 'Recipes', +'Record ID': 'Record ID', +'Register': 'Register', +'Registration identifier': 'Registration identifier', +'Registration key': 'Registration key', +'Registration successful': 'Registration successful', +'Remember me (for 30 days)': 'Remember me (for 30 days)', +'Reset Password key': 'Reset Password key', +'Role': 'Role', +'Semantic': 'Semantic', +'Services': 'Services', +'Stylesheet': 'Stylesheet', +'Support': 'Support', +'The Core': 'The Core', +'The output of the file is a dictionary that was rendered by the view %s': 'The output of the file is a dictionary that was rendered by the view %s', +'The Views': 'The Views', +'This App': 'This App', +'Timestamp': 'Timestamp', +'Twitter': 'Twitter', +'User %(id)s Logged-in': 'User %(id)s Logged-in', +'User %(id)s Logged-out': 'User %(id)s Logged-out', +'User %(id)s Registered': 'User %(id)s Registered', +'User ID': 'User ID', +'value already in database or empty': 'value already in database or empty', +'Verify Password': 'Verify Password', +'Videos': 'Videos', +'View': 'View', +'Welcome': 'Welcome', +'Welcome to web2py!': 'Welcome to web2py!', +'Which called the function %s located in the file %s': 'Which called the function %s located in the file %s', +'You are successfully running web2py': 'You are successfully running web2py', +'You can modify this application and adapt it to your needs': 'You can modify this application and adapt it to your needs', +'You visited the url %s': 'You visited the url %s', } diff --git a/applications/welcome/languages/plural-en.py b/applications/welcome/languages/plural-en.py index d0ea585e..562d96f1 100644 --- a/applications/welcome/languages/plural-en.py +++ b/applications/welcome/languages/plural-en.py @@ -6,6 +6,7 @@ 'is': ['are'], 'man': ['men'], 'person': ['people'], +'quark': ['quarks'], 'shop': ['shops'], 'this': ['these'], 'was': ['were'], diff --git a/gluon/cache.py b/gluon/cache.py index 3499ac14..23626f40 100644 --- a/gluon/cache.py +++ b/gluon/cache.py @@ -40,7 +40,6 @@ __all__ = ['Cache', 'lazy_cache'] DEFAULT_TIME_EXPIRE = 300 - class CacheAbstract(object): """ Abstract class for cache implementations. diff --git a/gluon/cfs.py b/gluon/cfs.py index b3669f4b..d40737b3 100644 --- a/gluon/cfs.py +++ b/gluon/cfs.py @@ -14,6 +14,7 @@ FOR INTERNAL USE ONLY import os import thread +import logging from fileutils import read_file cfs = {} # for speed-up @@ -36,13 +37,13 @@ def getcfs(key, filename, filter=None): try: t = os.stat(filename).st_mtime except OSError: - return filter() + return filter() if callable(filter) else '' cfs_lock.acquire() item = cfs.get(key, None) cfs_lock.release() if item and item[0] == t: return item[1] - if not filter: + if not callable(filter): data = read_file(filename) else: data = filter() diff --git a/gluon/compileapp.py b/gluon/compileapp.py index 5a186c08..3b737bea 100644 --- a/gluon/compileapp.py +++ b/gluon/compileapp.py @@ -91,6 +91,15 @@ def _TEST(): _TEST() """ +CACHED_REGEXES = {} + +def re_compile(regex): + try: + return CACHED_REGEXES[regex] + except KeyError: + compiled_regex = CACHED_REGEXES[regex] = re.compile(regex) + return compiled_regex + class mybuiltin(object): """ NOTE could simple use a dict and populate it, @@ -355,7 +364,11 @@ def build_environment(request, response, session, store_current=True): environment.update((k,getattr(v, k)) for k in v.__all__) if not request.env: request.env = Storage() - + # Enable standard conditional models (i.e., /*.py, /[controller]/*.py, and + # /[controller]/[function]/*.py) + response.models_to_run = [r'^\w+\.py$', r'^%s/\w+\.py$' % request.controller, + r'^%s/%s/\w+\.py$' % (request.controller, request.function)] + t = environment['T'] = translator(request) c = environment['cache'] = Cache(request) if store_current: @@ -485,9 +498,13 @@ def run_models_in(environment): path = pjoin(folder, 'models') models = listdir(path, '^\w+\.py$',0,sort=False) compiled=False - paths = (path, pjoin(path,c), pjoin(path,c,f)) + n = len(path) + 1 for model in models: - if not os.path.split(model)[0] in paths and c!='appadmin': + regex = environment['response'].models_to_run + if isinstance(regex, list): + regex = re_compile('|'.join(regex)) + file = model[n:].replace(os.path.sep, '/').replace('.pyc', '.py') + if not regex.search(file) and c!= 'appadmin': continue elif compiled: code = read_pyc(model) @@ -580,10 +597,13 @@ def run_view_in(environment): folder = request.folder path = pjoin(folder, 'compiled') badv = 'invalid view (%s)' % view - patterns = response.generic_patterns or [] - regex = re.compile('|'.join(map(fnmatch.translate, patterns))) - short_action = '%(controller)s/%(function)s.%(extension)s' % request - allow_generic = patterns and regex.search(short_action) + if response.generic_patterns: + patterns = response.generic_patterns + regex = re_compile('|'.join(map(fnmatch.translate, patterns))) + short_action = '%(controller)s/%(function)s.%(extension)s' % request + allow_generic = regex.search(short_action) + else: + allow_generic = False if not isinstance(view, str): ccode = parse_template(view, pjoin(folder, 'views'), context=environment) diff --git a/gluon/contrib/markmin/markmin2html.py b/gluon/contrib/markmin/markmin2html.py index 8e6431bc..e1f365c0 100755 --- a/gluon/contrib/markmin/markmin2html.py +++ b/gluon/contrib/markmin/markmin2html.py @@ -1,4 +1,4 @@ -#!/bin/env python +#!/usr/bin/env python # -*- coding: utf-8 -*- # created by Massimo Di Pierro # recreated by Vladyslav Kozlovskyy @@ -44,8 +44,8 @@ from markmin2pdf import markmin2pdf # requires pdflatex print markmin2pdf(m) `` ==================== -# This is a test block with new features: - +# This is a test block + with new features: This is a blockquote with a list with tables in it: ----------- @@ -71,7 +71,9 @@ a list with tables in it: -----------:blockquoteclass[blockquoteid] This this a new paragraph -with a table. Table has header, footer, sections, odd and even rows: +with a followed table. +Table has header, footer, sections, +odd and even rows: ------------------------------- **Title 1**|**Title 2**|**Title 3** ============================== @@ -98,6 +100,8 @@ Now lists can be multilevel: You can continue item text on next strings +. paragraph in an item + ++. Ordered item 1 of sublevel 2 with a paragraph (paragraph can start with point after plus or minus @@ -143,13 +147,13 @@ line 1 line 2 line 3 `` -++++. Yet another item with code block: -`` +++++. Yet another item with code block (we need to indent \`\` to add code block as part of item): + `` line 1 line 2 line 3 `` -This item finishes with this paragraph. + This item finishes with this paragraph. ... Item in sublevel 3 can be continued with paragraphs. @@ -201,7 +205,7 @@ We wanted a markup language with the following requirements: - support table, ul, ol, code - support html5 video and audio elements (html serialization only) - can align images and resize them -- can specify class for tables and code elements +- can specify class for tables, blockquotes and code elements - can add anchors - does not use _ for markup (since it creates odd behavior) - automatically links urls @@ -420,6 +424,23 @@ generates (the ``!`!`...`!`!:custom`` block is rendered by the ``custom=lambda`` function passed to ``render``). +### Line breaks + +``[[NEWLINE]]`` tag is used to break lines: +`` +#### Multiline [[NEWLINE]] + title +paragraph [[NEWLINE]] +with breaks[[NEWLINE]]in it +`` +generates: + +#### Multiline [[NEWLINE]] + title +paragraph [[NEWLINE]] +with breaks[[NEWLINE]]in it + + ### Html5 support Markmin also supports the