Compare commits

...

69 Commits

Author SHA1 Message Date
mdipierro
d566e4f444 simplified grid console logic and style 2012-09-04 17:58:52 -05:00
mdipierro
2174677f6f R-2.0.7 2012-09-04 15:32:27 -05:00
mdipierro
fb544a832c fixed issue 976 with quoting in pg8000 2012-09-04 15:09:02 -05:00
mdipierro
d9121967e7 fixed problem with headers in components 2012-09-04 15:07:09 -05:00
mdipierro
f2247bc5b6 prevent exception in admin, thanks Marin 2012-09-04 14:47:13 -05:00
mdipierro
ee0cffc944 fixed update with compute fields (again) and ecomponents in markmin 2012-09-04 14:43:38 -05:00
mdipierro
a45e08c43b try fetchall except, back in executesql, thanks Carlos 2012-09-03 16:15:06 -05:00
mdipierro
9c931025cc executesql(fetch=False) 2012-09-03 14:35:01 -05:00
mdipierro
07a97d62b8 minor changes in web2py.css 2012-09-03 10:18:15 -05:00
mdipierro
65fec492f0 fixed input-xlarge class issue, thanks Anthony 2012-09-03 08:55:54 -05:00
mdipierro
da7d3c6dbd updated markmin docs 2012-09-03 08:54:45 -05:00
mdipierro
73c66c142d fixed error in admin login 2012-09-03 08:47:10 -05:00
mdipierro
29c513e5a3 faster custom_import, thanks Michele 2012-09-03 08:17:05 -05:00
mdipierro
72bb9d3513 executesql allows fields and/or colnames to be independently specfied, thanks Ahtony and Niphlod 2012-09-03 08:12:40 -05:00
mdipierro
90c0e0ff7e CACHED_REGEXES_MAX_SIZE, thanks Anthony 2012-09-03 08:09:49 -05:00
mdipierro
7b24ce3f41 fixed backward compatibility issue in dal with __int__, thanks Dominic 2012-09-02 22:34:24 -05:00
mdipierro
d61748466e conditional session connect only 2012-09-02 22:30:33 -05:00
mdipierro
c17c28e42b routes_in = [('/welcome','/welcome',dict(web2py_disable_session = True))] 2012-09-02 15:09:32 -05:00
mdipierro
524a65d0a9 routes_in = (regex, value, custom_env) 2012-09-02 15:03:25 -05:00
mdipierro
51d8932252 allow navbar without define_tables, thanks Anthony 2012-09-02 14:52:52 -05:00
mdipierro
7b655465bb faster re.compile in validators 2012-09-02 12:26:16 -05:00
mdipierro
71c44e62b8 conditional models with cached re.compile, thanks Anthony 2012-09-02 12:12:59 -05:00
mdipierro
2295b93f32 capitalized regex in dal.py 2012-09-02 11:58:56 -05:00
mdipierro
04d0b82268 less regex in dal.py 2012-09-02 11:50:35 -05:00
mdipierro
dffb2eada2 removed one re.compile in rewrite filter_url 2012-09-02 11:12:46 -05:00
mdipierro
c109fa727c optional re.compile of generic_patterns 2012-09-02 11:05:19 -05:00
mdipierro
91c9c6fb28 R-2.0.6 2012-09-01 22:35:40 -05:00
mdipierro
e4f8896b7f fixed bug in tickets2emails, thanks Niphlod 2012-09-01 22:33:46 -05:00
mdipierro
a558af3b09 fixed bug in language file that corrupts files on language update, thanks kverdecia2 2012-09-01 22:32:12 -05:00
mdipierro
918d3bd6df fixed typo, thanks Mart 2012-09-01 07:01:38 -05:00
mdipierro
e4b6fba5ca R-2.0.5 2012-08-31 16:28:40 -05:00
mdipierro
41caa71ab0 scheduler validators, thanks Niphlod 2012-08-31 16:15:59 -05:00
mdipierro
f8786e5b6d 2.0.5 2012-08-31 16:11:56 -05:00
mdipierro
78b5f4f8aa better timezone logic 2012-08-31 16:04:13 -05:00
mdipierro
a0e4154f26 better timezone logic 2012-08-31 16:00:23 -05:00
mdipierro
3f7749cf20 R-2.0.4 2012-08-31 15:38:37 -05:00
mdipierro
dba7247b44 minor cleanup 2012-08-31 14:27:50 -05:00
mdipierro
43ac53f6a1 adding README, again 2012-08-31 13:37:30 -05:00
mdipierro
e82982d25d fixed problem with case in migrations 2012-08-31 13:32:22 -05:00
mdipierro
665a5cdee2 SQL(debug=True) logs migration info 2012-08-31 11:08:20 -05:00
mdipierro
b81e4b60e5 support for self reference with non standard id 2012-08-31 10:21:37 -05:00
mdipierro
a017996aba yet better markmin has [[NEWLINE]], thanks Vladyslav 2012-08-31 09:02:54 -05:00
mdipierro
92ad68aad7 try... execpt plural_rules 2012-08-31 00:31:51 -05:00
mdipierro
77618dd18b added plural rules again 2012-08-31 00:29:09 -05:00
mdipierro
f0d075bbd6 fixed postgresql uri bug 2012-08-31 00:11:33 -05:00
mdipierro
156c2c294e fixed plural rules with pkgutil 2012-08-30 23:55:14 -05:00
mdipierro
c6037b0355 windows and osx cannot find the rules folder, for now, ignore it 2012-08-30 23:07:51 -05:00
mdipierro
5edde2638e another driver selection issue 2012-08-30 22:36:10 -05:00
mdipierro
ba70a77932 improved logic in dal driver selection (fixed) 2012-08-30 22:19:59 -05:00
mdipierro
1554a831ac improved logic in dal driver selection 2012-08-30 22:10:46 -05:00
mdipierro
efbabbafa3 fixed 'fdb' is not defined issue, thanks villas 2012-08-30 20:24:56 -05:00
mdipierro
017fd0dd14 fixed typo in index_name 2012-08-30 20:19:01 -05:00
mdipierro
79e7d974ad Rows.find(f,limitby=(0,10)) 2012-08-30 20:07:34 -05:00
mdipierro
1cf2f84d24 added empty applications/examples/languages/README else folder not version controller in git and hg 2012-08-30 17:36:19 -05:00
mdipierro
c844759c63 fixed issue 964, thanks Michael and Jonathan 2012-08-30 17:04:07 -05:00
mdipierro
b3be11953d R-2.0.3 2012-08-30 15:38:41 -05:00
mdipierro
4ee59cfaaf fixed bug in smartgrid header, thanks Adi 2012-08-30 15:36:36 -05:00
mdipierro
21cf4f9024 cacheable select in grid, thanks Anthony 2012-08-30 15:26:58 -05:00
mdipierro
53889ece3d fixed bug in pluralization, thanks Vladyslav 2012-08-30 15:20:36 -05:00
mdipierro
910f4c0f13 new default firebird driver, thanks mariuz 2012-08-30 15:10:45 -05:00
mdipierro
10555af3e6 fixing missing use_username issue, thanks Annet 2012-08-30 15:01:22 -05:00
mdipierro
ec92b8fff1 fixed path find for pluralization rules 2012-08-30 14:54:40 -05:00
mdipierro
25f349a3a9 logging rotation in example, thanks Jonathan 2012-08-30 14:49:50 -05:00
mdipierro
9f763c677c fixed AttributeError: 'Expression' object has no attribute '_table' issue 2012-08-30 14:42:32 -05:00
mdipierro
1444c5b476 fixed janrain login with GAE 2012-08-30 08:31:52 -05:00
mdipierro
3c0ed7b8b9 fixed bug in markmin 2012-08-30 08:17:28 -05:00
mdipierro
528cf0ed0f fixed grin without login, thanks Liam 2012-08-30 08:14:10 -05:00
mdipierro
a8580673b6 update link fixed, thanks Niphlod 2012-08-30 08:09:33 -05:00
mdipierro
04fe5199f1 allow tasks without timeout 2012-08-30 08:05:09 -05:00
62 changed files with 1044 additions and 651 deletions

View File

@@ -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

View File

@@ -29,14 +29,14 @@ update:
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
echo "remember that pymysql was tweaked"
src:
echo 'Version 2.0.2 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION
echo 'Version 2.0.7 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
rm -f routes.py
rm -f applications/*/sessions/*
rm -f applications/*/sessions/*
rm -f applications/*/errors/* | echo 'too many files'
rm -f applications/*/cache/*
rm -f applications/*/cache/*
rm -f applications/admin/databases/*
rm -f applications/welcome/databases/*
rm -f applications/examples/databases/*
@@ -127,5 +127,5 @@ push:
tag:
git tag -l '$(S)'
hg tag -l '$(S)'
make commit
make commit S='$(S)'
make push

View File

@@ -1 +1 @@
Version 2.0.2 (2012-08-29 22:00:56) stable
Version 2.0.7 (2012-09-04 17:58:46) stable

View File

@@ -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(' <strong class="upgrade_version">%s</strong>' % version_number)
+ XML(' <strong class="upgrade_version">%s.%s.%s</strong>' \
% version_number[:3])
def logout():
""" Logout handler """
@@ -244,11 +244,15 @@ def site():
DIV(T('Unable to download app because:'),PRE(str(e)))
redirect(URL(r=request))
fname = form_update.vars.url
elif form_update.accepted and form_update.vars.file:
fname = request.vars.file.filename
f = request.vars.file.file
else:
session.flash = 'No file uploaded and no URL specified'
redirect(URL(r=request))
if f:
appname = cleanpath(form_update.vars.name)
installed = app_install(appname, f,

View File

@@ -16,6 +16,8 @@
'are not used yet': 'are not used yet',
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
'arguments': 'arguments',
'at char %s': 'at char %s',
'at line %s': 'at line %s',
'back': '<<back',
'can be a git repo': 'can be a git repo',
'Change admin password': 'Change admin password',
@@ -33,6 +35,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 +46,23 @@
'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',
'failed to compile file because:': 'failed to compile file because:',
'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 +73,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,10 +86,11 @@
'Logout': 'Logout',
'Models': 'Models',
'models': 'models',
'modules': 'modules',
'Modules': 'Modules',
'modules': 'modules',
'New application wizard': 'New application wizard',
'New simple application': 'New simple application',
'online designer': 'online designer',
'Overwrite installed app': 'Overwrite installed app',
'Pack all': 'Pack all',
'Peeking at file': 'Peeking at file',
@@ -88,11 +103,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',
@@ -109,7 +130,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',

View File

@@ -55,7 +55,7 @@ function doClickSave() {
prepareDataForSave('saved_on', jQuery("input[name='saved_on']").val()),
prepareDataForSave('saved_on', jQuery("input[name='saved_on']").val()),
prepareDataForSave('from_ajax','true')));
// console.info(area.textarea.value);
// console.info(area.textarea.value);
jQuery("input[name='saved_on']").attr('style','background-color:yellow');
jQuery("input[name='saved_on']").val('saving now...')
jQuery.ajax({
@@ -65,13 +65,13 @@ function doClickSave() {
dataType: "json",
data: dataForPost[0],
timeout: 5000,
beforeSend: function(xhr) {
beforeSend: function(xhr) {
xhr.setRequestHeader('web2py-component-location',document.location);
xhr.setRequestHeader('web2py-component-element','doClickSave');},
success: function(json,text,xhr){
success: function(json,text,xhr){
// show flash message (if any)
var flash=xhr.getResponseHeader('web2py-component-flash');
// show flash message (if any)
var flash=xhr.getResponseHeader('web2py-component-flash');
if (flash) jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
else jQuery('.flash').hide();

View File

@@ -2,7 +2,9 @@
<h2>web2py&trade; {{=T('Web Framework')}}</h2>
<h3>{{=T('Login to the Administrative Interface')}}</h3>
<div>
{{if request.is_https or request.is_local:}}
<div class="form">
<form action="{{=URL(r=request)}}" method="post">
<div><input type="hidden" name="send" value="{{=send}}"/></div>
<table>
@@ -11,4 +13,6 @@
</table>
</form>
</div>
{{else:}}
<p class="help">{{=T('ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.')}}</p>
{{pass}}

View File

@@ -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]))}}
</span>
<span class="file">
{{=peekfile('languages',pfile,dict(id=id))}},
{{=peekfile('languages',pfile,dict(id=id))}}
</span>
{{else:}}
<b>{{=T("are not used yet")}}</b>,
<b>{{=T("are not used yet")}}</b>
{{pass}}
{{pass}}
{{=T("rules:")}}
<span class="file{{=' error' if p[3]!='ok' else ''}}">
{{=peekfile('gluon/contrib/rules', p[2], dict(app=app, id=id), p[3] if p[3]!='ok' else None)}}
</span>
{{pass}}
)
</td>

File diff suppressed because one or more lines are too long

View File

@@ -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',
}

View File

@@ -60,6 +60,7 @@
'edit profile': 'modifica profilo',
'Edit This App': 'Modifica questa applicazione',
'Email and SMS': 'Email and SMS',
'Email non valida': 'Email non valida',
'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g',
'Errors': 'Errors',
'export as csv file': 'esporta come file CSV',
@@ -95,6 +96,7 @@
'Layouts': 'Layouts',
'Live Chat': 'Live Chat',
'Logged in': 'Logged in',
'Logged out': 'Logged out',
'login': 'accesso',
'Login': 'Login',
'logout': 'uscita',
@@ -113,6 +115,7 @@
'new record inserted': 'nuovo record inserito',
'next 100 rows': 'prossime 100 righe',
'No databases in this application': 'Nessun database presente in questa applicazione',
'Non può essere vuoto': 'Non può essere vuoto',
'not authorized': 'non autorizzato',
'Object or table name': 'Object or table name',
'Online examples': 'Vedere gli esempi',
@@ -166,12 +169,14 @@
'This App': 'This App',
'This is a copy of the scaffolding application': "Questa è una copia dell'applicazione di base (scaffold)",
'Timestamp': 'Ora (timestamp)',
'too short': 'too short',
'Twitter': 'Twitter',
'unable to parse csv file': 'non riesco a decodificare questo file CSV',
'Update': 'Update',
'Update:': 'Aggiorna:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).',
'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': 'ID Utente',
'Verify Password': 'Verify Password',

View File

@@ -6,6 +6,7 @@
'is': ['are'],
'man': ['men'],
'person': ['people'],
'quark': ['quarks'],
'shop': ['shops'],
'this': ['these'],
'was': ['were'],

View File

@@ -15,9 +15,9 @@ td,th {text-align:left; padding:2px 5px 2px 5px}
th {vertical-align:middle; border-right:1px solid white}
td {vertical-align:top}
form table tr td label {text-align:left}
p,table,ol,ul {padding:0; margin: 0.5em 0}
p,table,ol,ul {padding:0; margin: 0.75em 0}
p {text-align:justify}
ol, ul {list-style-position:inside}
ol, ul {list-style-position:outside; margin-left:2em}
li {margin-bottom:0.5em}
span,input,select,textarea,button,label,a {display:inline}
img {border:0}
@@ -39,13 +39,6 @@ input[type=text],input[type=password],select{width:300px; margin-right:5px}
/* Sticky footer begin */
.wrapper {
min-height:100%;
height:auto !important;
height:100%;
margin:0 auto -8em; /* set last value to footer height plus footer vertical padding */
}
.main {
padding:20px 0 50px 0;
}

View File

@@ -129,7 +129,7 @@
</section><!--/main-->
<!-- Footer ================================================== -->
<footer class="footer">
<footer class="footer" id="footer">
{{block footer}} <!-- this is default footer -->
<div class="footer-content">
<div class="copyright pull-left">{{=T('Copyright')}} &#169; {{=request.now.year}}</div>

View File

@@ -40,7 +40,6 @@ __all__ = ['Cache', 'lazy_cache']
DEFAULT_TIME_EXPIRE = 300
class CacheAbstract(object):
"""
Abstract class for cache implementations.

View File

@@ -91,6 +91,18 @@ def _TEST():
_TEST()
"""
CACHED_REGEXES = {}
CACHED_REGEXES_MAX_SIZE = 1000
def re_compile(regex):
try:
return CACHED_REGEXES[regex]
except KeyError:
if len(CACHED_REGEXES) >= CACHED_REGEXES_MAX_SIZE:
CACHED_REGEXES.clear()
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 +367,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 +501,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)
@@ -581,10 +601,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)

View File

@@ -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 <video> and <audio> html5 tags using the notation:
@@ -430,7 +451,7 @@ Markmin also supports the <video> and <audio> html5 tags using the notation:
[[message [title] link video]]
[[message [title] link audio]]
``
where ``message`` will be shown in brousers without HTML5 video/audio tags support.
where ``message`` will be shown in browsers without HTML5 video/audio tags support.
### Latex and other extensions
@@ -512,18 +533,19 @@ DISABLED_META = '\x08'
LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" />'
regex_URL=re.compile(r'@/(?P<a>\w*)/(?P<c>\w*)/(?P<f>\w*(\.\w+)?)(/(?P<args>[\w\.\-/]+))?')
regex_env=re.compile(r'@\{(?P<a>[\w\-\.]+?)(\:(?P<b>.*?))?\}')
regex_expand_meta = re.compile('('+META+'|'+DISABLED_META+')')
regex_expand_meta = re.compile('('+META+'|'+DISABLED_META+'|````)')
regex_dd=re.compile(r'\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('('+META+'|'+DISABLED_META+r')|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',re.S)
regex_code = re.compile('('+META+'|'+DISABLED_META+r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',re.S)
regex_strong=re.compile(r'\*\*(?P<t>[^\s*]+( +[^\s*]+)*)\*\*')
regex_del=re.compile(r'~~(?P<t>[^\s*]+( +[^\s*]+)*)~~')
regex_em=re.compile(r"''(?P<t>[^\s']+(?: +[^\s']+)*)''")
regex_num=re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$")
regex_list=re.compile('^(?:(#{1,6}|\.+ |\++ |\++\. |\-+ |\-+\. )\s*)?(.*)$')
regex_list=re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s+)?(.*)$')
regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
regex_tq=re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
regex_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+)', re.M)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[^\s\'\"\]\}\)]+)',re.M)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.]+)',re.M)
regex_link=re.compile(r'('+LINK+r')|\[\[(?P<s>.+?)\]\]')
regex_link_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$')
regex_media_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio)(?:\s+(?P<w>\d+px))?\s*$')
@@ -579,7 +601,8 @@ def render(text,
autolinks='default',
protolinks='default',
class_prefix='',
id_prefix='markmin_'):
id_prefix='markmin_',
pretty_print=False):
"""
Arguments:
- text is the text to be processed
@@ -791,9 +814,18 @@ def render(text,
>>> render('[[id1 [span **messag** in ''markmin''] ]] ... [[**link** to id [link\\\'s title] #mark1]]')
'<p><div class="anchor" id="markmin_id1">span <strong>messag</strong> in markmin</div> ... <a href="#markmin_mark1" title="link\\\'s title"><strong>link</strong> to id</a></p>'
>>> render('# Multiline[[NEWLINE]]\\n title\\nParagraph[[NEWLINE]]\\nwith breaks[[NEWLINE]]\\nin it')
'<h1>Multiline<br /> title</h1><p>Paragraph<br /> with breaks<br /> in it</p>'
>>> render("anchor with name 'NEWLINE': [[NEWLINE [ ] ]]")
'<p>anchor with name \\'NEWLINE\\': <div class="anchor" id="markmin_NEWLINE"></div></p>'
>>> render("anchor with name 'NEWLINE': [[NEWLINE [newline] ]]")
'<p>anchor with name \\'NEWLINE\\': <div class="anchor" id="markmin_NEWLINE">newline</div></p>'
"""
if autolinks=="default": autolinks = autolinks_simple
if protolinks=="default": protolinks = protolinks_simple
pp='\n' if pretty_print else ''
text = str(text or '')
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
@@ -816,9 +848,12 @@ def render(text,
segments = []
def mark_code(m):
g = m.group(0)
if m.group() in ( META, DISABLED_META ):
if g in (META, DISABLED_META ):
segments.append((None, None, None, g))
return m.group()
elif g == '````':
segments.append((None, None, None, ''))
return m.group()
else:
c = m.group('c') or ''
p = m.group('p') or ''
@@ -849,13 +884,13 @@ def render(text,
#############################################################
# normalize spaces
#############################################################
strings=[t.strip() for t in text.split('\n')]
strings=text.split('\n')
def parse_title(t, s): #out, lev, etags, tag, s):
hlevel=str(len(t))
out.extend(etags[::-1])
out.append("<h%s>%s"%(hlevel,s))
etags[:]=["</h%s>"%hlevel]
etags[:]=["</h%s>%s"%(hlevel,pp)]
lev=0
ltags[:]=[]
tlev[:]=[]
@@ -880,8 +915,8 @@ def render(text,
out.append(etags.pop())
ltags.pop()
for i in xrange(lent-lev):
out.append('<'+tag+'>')
etags.append('</'+tag+'>')
out.append('<'+tag+'>'+pp)
etags.append('</'+tag+'>'+pp)
lev+=1
ltags.append(lev)
tlev.append(tag)
@@ -892,8 +927,8 @@ def render(text,
ltags.pop()
out.append(etags.pop())
tlev[-1]=tag
out.append('<'+tag+'>')
etags.append('</'+tag+'>')
out.append('<'+tag+'>'+pp)
etags.append('</'+tag+'>'+pp)
ltags.append(lev)
else:
if ltags.count(lev)>1:
@@ -901,7 +936,7 @@ def render(text,
ltags.pop()
mtag='l'
out.append('<li>')
etags.append('</li>')
etags.append('</li>'+pp)
ltags.append(lev)
if s[:1] == '-':
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
@@ -954,7 +989,7 @@ def render(text,
return (s, mtag, lineno)
lineno+=1
s = strings[lineno]
s = strings[lineno].strip()
if s:
if '|' in s:
# table
@@ -967,7 +1002,7 @@ def render(text,
# parse table:
while lineno < strings_len:
s = strings[lineno]
s = strings[lineno].strip()
if s[:1] == '=':
if s.count('=')==len(s) and len(s)>3: # header or footer
if not thead: # if thead list is empty:
@@ -994,7 +1029,7 @@ def render(text,
if regex_num.match(f)
else '',
f.strip()
) for f in s.split('|')])+'</tr>')
) for f in s.split('|')])+'</tr>'+pp)
rownum+=1
lineno+=1
@@ -1002,15 +1037,15 @@ def render(text,
t_id = ' id="%s%s"'%(id_prefix, t_id) if t_id else ''
s = ''
if thead:
s += '<thead>'+''.join([l for l in thead])+'</thead>'
s += '<thead>'+pp+''.join([l for l in thead])+'</thead>'+pp
if not tbody: # tbody strings are in tout list
tbody = tout
tout = []
if tbody: # if tbody list is not empty:
s += '<tbody>'+''.join([l for l in tbody])+'</tbody>'
s += '<tbody>'+pp+''.join([l for l in tbody])+'</tbody>'+pp
if tout: # tfoot is not empty:
s += '<tfoot>'+''.join([l for l in tout])+'</tfoot>'
s = '<table%s%s>%s</table>' % (t_cls, t_id, s)
s += '<tfoot>'+pp+''.join([l for l in tout])+'</tfoot>'+pp
s = '<table%s%s>%s%s</table>%s' % (t_cls, t_id, pp, s, pp)
mtag='t'
else:
# parse blockquote:
@@ -1021,7 +1056,7 @@ def render(text,
# search blockquote closing line:
while lineno < strings_len:
s = strings[lineno]
s = strings[lineno].strip()
if not t_mode:
m = regex_tq.match(s)
if m:
@@ -1031,7 +1066,7 @@ def render(text,
break
if regex_bq_headline.match(s):
if lineno+1 < strings_len and strings[lineno+1]:
if lineno+1 < strings_len and strings[lineno+1].strip():
t_mode = True
lineno+=1
continue
@@ -1044,7 +1079,7 @@ def render(text,
t_cls = ' class="%s%s"'%(class_prefix,t_cls) if t_cls and t_cls != 'id' else ''
t_id = ' id="%s%s"'%(id_prefix,t_id) if t_id else ''
s = '<blockquote%s%s>%s</blockquote>' \
s = '<blockquote%s%s>%s</blockquote>%s' \
% (t_cls,
t_id,
render('\n'.join(strings[bq_begin:lineno]),
@@ -1057,7 +1092,9 @@ def render(text,
autolinks,
protolinks,
class_prefix,
id_prefix)
id_prefix,
pretty_print),
pp
)
mtag='q'
else:
@@ -1068,53 +1105,58 @@ def render(text,
if sep == 'p':
pbeg = "<p>"
pend = "</p>"
pend = "</p>"+pp
br = ''
else:
pbeg = pend = ''
br = "<br />" if sep=='br' else ''
br = "<br />"+pp if sep=='br' else ''
lev = 0 # рівень вкладеності списків
c0 = '' # перший символ поточного рядка
out = [] # результуючий список рядків
etags = [] # завершуючі таги
ltags = [] # номер рівня відповідний завершуючому тагу
tlev = [] # таг рівня ('ul' або 'ol')
mtag = '' # marked tag (~last tag) ('l','.','h','p','t'). Used for set <br/>
# and for avoid <p></p> around tables and blockquotes
lev = 0 # nesting level of lists
c0 = '' # first character of current line
out = [] # list of processed lines
etags = [] # trailing tags
ltags = [] # level# correspondent to trailing tag
tlev = [] # list of tags for each level ('ul' or 'ol')
mtag = '' # marked tag (~last tag) ('l','.','h','p','t'). Used to set <br/>
# and to avoid <p></p> around tables and blockquotes
lineno = 0
strings_len = len(strings)
while lineno < strings_len:
s = strings[lineno]
s0 = strings[lineno][:1]
s = strings[lineno].strip()
""" # + - . ---------------------
## ++ -- .. ------- field | field | field <-title
### +++ --- ... quote =====================
#### ++++ ---- .... ------- field | field | field <-body
##### +++++ ----- ..... ---------------------:class[id]
"""
pc0=c0 # перший символ попереднього рядка
pc0=c0 # first character of previous line
c0=s[:1]
if c0: # for non empty strings
if c0 in "#+-.": # first character is one of: # + - .
match = regex_list.search(s)
(t,p,s) = match.group(1), None, match.group(2)
t = (t or '').strip()
if t.endswith('.'): t, p = t[:-1], '.'
# t - tag ("###", "+++", "---", "...")
(t1,t2,p,ss) = regex_list.findall(s)[0]
# t1 - tag ("###")
# t2 - tag ("+++", "---", "...")
# p - paragraph point ('.')->for "++." or "--."
# s - other part of string
if t:
# ss - other part of string
if t1 or t2:
# headers and lists:
if c0 == '#': # headers
(lev, mtag) = parse_title(t, s)
(lev, mtag) = parse_title(t1, ss)
lineno+=1
continue
elif c0 == '+': # ordered list
(lev, mtag, lineno)= parse_list(t, p, s, 'ol', lev, mtag, lineno)
(lev, mtag, lineno)= parse_list(t2, p, ss, 'ol', lev, mtag, lineno)
lineno+=1
continue
elif c0 == '-': # unordered list
(lev, mtag, lineno) = parse_list(t, p, s, 'ul', lev, mtag, lineno)
else: # c0 == '.' # paragraph in lists
(lev, mtag, lineno) = parse_point(t, s, lev, mtag, lineno)
lineno+=1
continue
(lev, mtag, lineno) = parse_list(t2, p, ss, 'ul', lev, mtag, lineno)
lineno+=1
continue
elif lev>0: # and c0 == '.' # paragraph in lists
(lev, mtag, lineno) = parse_point(t2, ss, lev, mtag, lineno)
lineno+=1
continue
else:
if c0 == '-': # table or blockquote?
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
@@ -1123,7 +1165,7 @@ def render(text,
# new paragraph
pc0=''
if pc0 == '':
if pc0 == '' or (mtag != 'p' and s0 not in (' ','\t')):
# paragraph
out.extend(etags[::-1])
etags=[]
@@ -1171,12 +1213,12 @@ def render(text,
style = p_begin = p_end = ''
if p == 'center':
p_begin = '<p style="text-align:center">'
p_end = '</p>'
p_end = '</p>'+pp
elif p in ('left','right'):
style = ' style="float:%s"' % p
if p in ('video','audio'):
t = render(t, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix)
autolinks, protolinks, class_prefix, id_prefix, pretty_print)
return '<%(p)s controls="controls"%(title)s%(width)s><source src="%(k)s" />%(t)s</%(p)s>' \
% dict(p=p, title=title, width=width, k=k, t=t)
alt = ' alt="%s"'%escape(t).replace(META, DISABLED_META) if t else ''
@@ -1197,13 +1239,16 @@ def render(text,
title = ' title="%s"' % a.replace(META, DISABLED_META) if a else ''
target = ' target="_blank"' if p == 'popup' else ''
t = render(t, {}, {}, 'br', URL, environment, latex, autolinks,
protolinks, class_prefix, id_prefix) if t else k
protolinks, class_prefix, id_prefix, pretty_print) if t else k
return '<a href="%(k)s"%(title)s%(target)s>%(t)s</a>' \
% dict(k=k, title=title, target=target, t=t)
if t == 'NEWLINE' and not a:
return '<br />'+pp
return '<div class="anchor" id="%s">%s</div>' % (escape(id_prefix+t),
render(a, {},{},'br', URL,
environment, latex, autolinks,
protolinks, class_prefix, id_prefix))
render(a, {},{},'br', URL,
environment, latex, autolinks,
protolinks, class_prefix,
id_prefix, pretty_print))
parts = text.split(LINK)
text = parts[0]
@@ -1230,9 +1275,9 @@ def render(text,
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if p:
return extra[b](code,p)
return str(extra[b](code,p))
else:
return extra[b](code)
return str(extra[b](code))
elif b=='cite':
return '['+','.join('<a href="#%s" class="%s">%s</a>' \
% (d,b,d) \
@@ -1241,21 +1286,22 @@ def render(text,
return LATEX % code.replace('"','\"').replace('\n',' ')
elif b in html_colors:
return '<span style="color: %s">%s</span>' \
% (b, render(code,{},{},'br',URL,environment,latex,autolinks, protolinks))
% (b, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
elif b in ('c', 'color') and p:
c=p.split(':')
fg='color: %s;' % c[0] if c[0] else ''
bg='background-color: %s;' % c[1] if len(c)>1 and c[1] else ''
return '<span style="%s%s">%s</span>' \
% (fg, bg, render(code,{},{},'br', URL, environment, latex, autolinks, protolinks))
% (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
cls = ' class="%s%s"'%(class_prefix,b) if b and b != 'id' else ''
id = ' id="%s%s"'%(id_prefix,escape(p)) if p else ''
beg=(code[:1]=='\n')
end=[None,-1][code[-1:]=='\n']
if beg and end:
return '<pre><code%s%s>%s</code></pre>' % (cls, id, escape(code[1:-1]))
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, escape(code[1:-1]), pp)
return '<code%s%s>%s</code>' % (cls, id, escape(code[beg:end]))
text = regex_expand_meta.sub(expand_meta, text)
text = text.translate(ttab_out)
@@ -1264,7 +1310,7 @@ def render(text,
f = environment.get(match.group('a'), match.group(0))
if callable(f):
try:
f = f(match.group('b'))
f = f(match.group('b'))
except Exception, e:
f = 'ERROR: %s' % e
return str(f)
@@ -1272,10 +1318,17 @@ def render(text,
return text
def markmin2html(text, extra={}, allowed={}, sep='p',
autolinks='default',protolinks='default'):
return render(text, extra, allowed, sep,
autolinks=autolinks, protolinks=protolinks)
def markmin2html(text, extra={}, allowed={}, sep='p',
autolinks='default', protolinks='default',
class_prefix='', id_prefix='markmin_', pretty_print=False):
return render(text, extra, allowed, sep,
autolinks=autolinks, protolinks=protolinks,
class_prefix=class_prefix, id_prefix=id_prefix,
pretty_print=pretty_print)
def run_doctests():
import doctest
doctest.testmod()
if __name__ == '__main__':
import sys
@@ -1312,7 +1365,9 @@ if __name__ == '__main__':
pre { background-color: #E0E0E0; padding: 5px; }
</style>""")[1:]
print html % dict(title="Markmin markup language", style=style, body=markmin2html(__doc__))
print html % dict(title="Markmin markup language",
style=style,
body=markmin2html(__doc__, pretty_print=True))
elif sys.argv[1:2] == ['-t']:
from timeit import Timer
loops=1000
@@ -1338,7 +1393,8 @@ if __name__ == '__main__':
else:
markmin_style = ""
print html % dict(title=sys.argv[1], style=markmin_style, body=markmin2html(markmin_text))
print html % dict(title=sys.argv[1], style=markmin_style,
body=markmin2html(markmin_text, pretty_print=True))
finally:
fargv.close()
@@ -1348,4 +1404,5 @@ if __name__ == '__main__':
print " -t - timeit __doc__ (for testing purpuse only)"
print " file.markmin [file.css] - process file.markmin + built in file.css (optional)"
print " file.markmin [@path_to/css] - process file.markmin + link path_to/css (optional)"
doctest.testmod()
run_doctests()

View File

@@ -0,0 +1 @@

View File

@@ -39,6 +39,7 @@ class WebClient(object):
session_regex = SESSION_REGEX):
self.app = app
self.postbacks = postbacks
self.forms = {}
self.history = []
self.cookies = {}
self.default_headers = default_headers

View File

@@ -66,12 +66,14 @@ class _BaseImporter(object):
help the management of this aspect.
"""
def __init__(self):
self._STANDARD_PYTHON_IMPORTER = _STANDARD_PYTHON_IMPORTER
def __call__(self, name, globals=None, locals=None,
fromlist=None, level=-1):
"""
The import method itself.
"""
return _STANDARD_PYTHON_IMPORTER(name,
return self._STANDARD_PYTHON_IMPORTER(name,
globals,
locals,
fromlist,
@@ -226,7 +228,8 @@ class _Web2pyImporter(_BaseImporter):
"""
global DEBUG
super(_Web2pyImporter, self).__init__()
self.super_class = super(_Web2pyImporter, self)
self.super_class.__init__()
self.web2py_path = web2py_path
self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep
self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep)
@@ -284,16 +287,16 @@ class _Web2pyImporter(_BaseImporter):
globals, locals, fromlist, level)
else:
# import like "from x import a, b, ..."
return super(_Web2pyImporter, self) \
return self.super_class \
.__call__(modules_prefix+"."+name,
globals, locals, fromlist, level)
except ImportError, e:
try:
return super(_Web2pyImporter, self).__call__(name, globals, locals,
return self.super_class.__call__(name, globals, locals,
fromlist, level)
except ImportError, e1:
raise e
return super(_Web2pyImporter, self).__call__(name, globals, locals,
return self.super_class.__call__(name, globals, locals,
fromlist, level)
def __import__dot(self, prefix, name, globals, locals, fromlist,

File diff suppressed because it is too large Load Diff

View File

@@ -12,13 +12,14 @@ Plural subsystem is created by Vladyslav Kozlovskyy (Ukraine)
import os
import re
import pkgutil
from utf8 import Utf8
from cgi import escape
import portalocker
import logging
import marshal
import copy_reg
from fileutils import abspath, listdir
from fileutils import listdir
import settings
from cfs import getcfs
from thread import allocate_lock
@@ -31,6 +32,8 @@ __all__ = ['translator', 'findT', 'update_all_languages']
ospath = os.path
ostat = os.stat
osep = os.sep
pjoin = os.path.join
pdirname = os.path.dirname
isdir = os.path.isdir
is_gae = settings.global_settings.web2py_runtime_gae
@@ -80,7 +83,6 @@ regex_backslash = re.compile(r"\\([\\{}%])")
regex_plural = re.compile('%({.+?})')
regex_plural_dict = re.compile('^{(?P<w>[^()[\]][^()[\]]*?)\((?P<n>[^()\[\]]+)\)}$') # %%{word(varname or number)}
regex_plural_tuple = re.compile('^{(?P<w>[^[\]()]+)(?:\[(?P<i>\d+)\])?}$') # %%{word[index]} or %%{word}
regex_plural_rules = re.compile('^plural_rules-[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$')
# UTF8 helper functions
def upper_fun(s):
@@ -213,47 +215,30 @@ def read_possible_languages(appdir):
langs['en'] = ('en', 'English', 0)
return langs
def read_global_plural_rules(filename):
"""
retrieve plural rules from rules/*plural_rules-lang*.py file.
args:
filename (str): plural_rules filename
returns:
(nplurals, get_plural_id, construct_plural_form, status)
e.g.: (3, <function>, <function>, ok)
"""
env = {}
data = portalocker.read_locked(filename)
try:
exec(data) in env
status='ok'
except Exception, e:
status='Syntax error in %s (%s)' % (filename, e)
logging.error(status)
nplurals = env.get('nplurals', DEFAULT_NPLURALS)
get_plural_id = env.get('get_plural_id', DEFAULT_GET_PLURAL_ID)
construct_plural_form = env.get('construct_plural_form',
DEFAULT_CONSTRUCTOR_PLURAL_FORM)
return (nplurals, get_plural_id, construct_plural_form, status)
def read_possible_plurals():
"""
create list of all possible plural rules files
result is cached to increase speed
"""
pdir = abspath('gluon','contrib','rules')
plurals = {}
# scan rules directory for plural_rules-*.py files:
for pname in os.listdir(pdir):
if not isdir(pname) and regex_plural_rules.match(pname):
lang = pname[13:-3]
fname = ospath.join(pdir, pname)
n, f1, f2, status = read_global_plural_rules(fname)
if status == 'ok':
plurals[lang] = (lang, n, f1, f2, pname)
try:
import gluon.contrib.plural_rules as package
plurals = {}
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
if len(modname)==2:
module = __import__(package.__name__+'.'+modname)
lang = modname
pname = modname+'.py'
nplurals = getattr(module,'nplurals', DEFAULT_NPLURALS)
get_plural_id = getattr(
module,'get_plural_id',
DEFAULT_GET_PLURAL_ID)
construct_plural_form = getattr(
module,'construct_plural_form',
DEFAULT_CONSTRUCTOR_PLURAL_FORM)
plurals[lang] = (lang, nplurals, get_plural_id,
construct_plural_form, pname)
except ImportError:
logging.warn('Unable to import plural rules')
plurals['default'] = ('default',
DEFAULT_NPLURALS,
DEFAULT_GET_PLURAL_ID,
@@ -505,7 +490,7 @@ class translator(object):
if int(n)==1:
return word
elif word:
id = min(int(n)-1,1) # self.get_plural_id(abs(int(n)))
id = self.get_plural_id(abs(int(n)))
# id = 0 first plural form
# id = 1 second plural form
# etc.
@@ -815,8 +800,8 @@ def findT(path, language='en'):
"""
must be run by the admin app
"""
filename = ospath.join(path, 'languages', language + '.py')
sentences = read_dict(filename)
lang_file = ospath.join(path, 'languages', language + '.py')
sentences = read_dict(lang_file)
mp = ospath.join(path, 'models')
cp = ospath.join(path, 'controllers')
vp = ospath.join(path, 'views')
@@ -845,8 +830,9 @@ def findT(path, language='en'):
'en' if language in ('default', 'en') else language)
if not '!langname!' in sentences:
sentences['!langname!'] = (
'English' if language in ('default', 'en') else sentences['!langcode!'])
write_dict(filename, sentences)
'English' if language in ('default', 'en')
else sentences['!langcode!'])
write_dict(lang_file, sentences)
### important to allow safe session.flash=T(....)
def lazyT_unpickle(data):

View File

@@ -30,7 +30,7 @@ import string
import urllib2
from thread import allocate_lock
from fileutils import abspath, write_file, parse_version
from fileutils import abspath, write_file, parse_version, copystream
from settings import global_settings
from admin import add_path_first, create_missing_folders, create_missing_app_folders
from globals import current
@@ -84,7 +84,6 @@ from http import HTTP, redirect
from globals import Request, Response, Session
from compileapp import build_environment, run_models_in, \
run_controller_in, run_view_in
from fileutils import copystream, parse_version
from contenttype import contenttype
from dal import BaseAdapter
from settings import global_settings
@@ -92,7 +91,8 @@ from validators import CRYPT
from cache import Cache
from html import URL, xmlescape
from utils import is_valid_ip_address
from rewrite import load, url_in, thread as rwthread, try_rewrite_on_error
from rewrite import load, url_in, thread as rwthread, \
try_rewrite_on_error, fixup_missing_path_info
import newcron
__all__ = ['wsgibase', 'save_password', 'appfactory', 'HttpServer']
@@ -149,10 +149,11 @@ def copystream_progress(request, chunk_size= 10**5):
and stores progress upload status in cache.ram
X-Progress-ID:length and X-Progress-ID:uploaded
"""
if not request.env.content_length:
env = request.env
if not env.content_length:
return cStringIO.StringIO()
source = request.env.wsgi_input
size = int(request.env.content_length)
source = env.wsgi_input
size = int(env.content_length)
dest = tempfile.TemporaryFile()
if not 'X-Progress-ID' in request.vars:
copystream(source, dest, size, chunk_size)
@@ -275,7 +276,8 @@ def environ_aux(environ,request):
def parse_get_post_vars(request, environ):
# always parse variables in URL for GET, POST, PUT, DELETE, etc. in get_vars
dget = cgi.parse_qsl(request.env.query_string or '', keep_blank_values=1)
env = request.env
dget = cgi.parse_qsl(env.query_string or '', keep_blank_values=1)
for (key, value) in dget:
if key in request.get_vars:
if isinstance(request.get_vars[key], list):
@@ -291,7 +293,7 @@ def parse_get_post_vars(request, environ):
request.body = body = copystream_progress(request)
except IOError:
raise HTTP(400,"Bad Request - HTTP body is incomplete")
if (body and request.env.request_method in ('POST', 'PUT', 'BOTH')):
if (body and env.request_method in ('POST', 'PUT', 'BOTH')):
dpost = cgi.FieldStorage(fp=body,environ=environ,keep_blank_values=1)
# The same detection used by FieldStorage to detect multipart POSTs
is_multipart = dpost.type[:10] == 'multipart/'
@@ -383,21 +385,7 @@ def wsgibase(environ, responder):
# serve file if static
# ##################################################
eget = environ.get
if not eget('PATH_INFO',None) and eget('REQUEST_URI',None):
# for fcgi, get path_info and
# query_string from request_uri
items = environ['REQUEST_URI'].split('?')
environ['PATH_INFO'] = items[0]
if len(items) > 1:
environ['QUERY_STRING'] = items[1]
else:
environ['QUERY_STRING'] = ''
if not eget('HTTP_HOST',None):
environ['HTTP_HOST'] = \
eget('SERVER_NAME')+':'+eget('SERVER_PORT')
fixup_missing_path_info(environ)
(static_file, environ) = url_in(request, environ)
if static_file:
@@ -434,6 +422,7 @@ def wsgibase(environ, responder):
is_https = env.wsgi_url_scheme \
in ['https', 'HTTPS'] or env.https=='on')
request.uuid = request.compute_uuid() # requires client
request.url = environ['PATH_INFO']
# ##################################################
# access the requested application
@@ -456,9 +445,6 @@ def wsgibase(environ, responder):
elif not request.is_local and \
exists(pjoin(request.folder,'DISABLED')):
raise HTTP(503, "<html><body><h1>Temporarily down for maintenance</h1></body></html>")
request.url = URL(r=request,
args=request.args,
extension=request.raw_extension)
# ##################################################
# build missing folders
@@ -488,9 +474,9 @@ def wsgibase(environ, responder):
# load cookies
# ##################################################
if request.env.http_cookie:
if env.http_cookie:
try:
request.cookies.load(request.env.http_cookie)
request.cookies.load(env.http_cookie)
except Cookie.CookieError, e:
pass # invalid cookies
@@ -498,7 +484,8 @@ def wsgibase(environ, responder):
# try load session or create new session file
# ##################################################
session.connect(request, response)
if not env.web2py_disable_session:
session.connect(request, response)
# ##################################################
# set no-cache headers
@@ -554,7 +541,7 @@ def wsgibase(environ, responder):
# if session not in db try store session on filesystem
# this must be done after trying to commit database!
# ##################################################
session._try_store_on_disk(request, response)
# ##################################################
@@ -562,7 +549,7 @@ def wsgibase(environ, responder):
# ##################################################
if request.cid:
rheaders = response.headers
rheaders = http_response.headers
if response.flash and \
not 'web2py-component-flash' in rheaders:
rheaders['web2py-component-flash'] = \

View File

@@ -38,6 +38,7 @@ thread = threading.local() # thread-local storage for routing params
regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*')
regex_anything = re.compile(r'(?<!\\)\$anything')
regex_redirect = re.compile(r'(\d+)->(.*)')
regex_full_url = re.compile(r'^(?P<scheme>http|https|HTTP|HTTPS)\://(?P<host>[^/]*)(?P<uri>.*)')
def _router_default():
"return new copy of default base router"
@@ -134,6 +135,27 @@ ROUTER_BASE_KEYS = set(
# filter_err: helper for doctest & unittest
# regex_filter_out: doctest
def fixup_missing_path_info(environ):
eget = environ.get
path_info = eget('PATH_INFO')
request_uri = eget('REQUEST_URI')
if not path_info and request_uri:
# for fcgi, get path_info and
# query_string from request_uri
items = request_uri.split('?')
path_info = environ['PATH_INFO'] = items[0]
environ['QUERY_STRING'] = items[1] if len(items) > 1 else ''
elif not request_uri:
query_string = eget('QUERY_STRING')
if query_string:
environ['REQUEST_URI'] = '%s?%s' % (path_info,query_string)
else:
environ['REQUEST_URI'] = path_info
if not eget('HTTP_HOST'):
environ['HTTP_HOST'] = \
'%s:%s' % (eget('SERVER_NAME'),eget('SERVER_PORT'))
def url_in(request, environ):
"parse and rewrite incoming URL"
if routers:
@@ -286,8 +308,8 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
for sym in ('routes_app', 'routes_in', 'routes_out'):
if sym in symbols:
for (k, v) in symbols[sym]:
p[sym].append(compile_regex(k, v))
for items in symbols[sym]:
p[sym].append(compile_regex(*items))
for sym in ('routes_onerror', 'routes_apps_raw',
'error_handler','error_message', 'error_message_ticket',
'default_application','default_controller', 'default_function',
@@ -349,7 +371,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
log_rewrite('URL rewrite is on. configuration in %s' % path)
def compile_regex(k, v):
def compile_regex(k, v, env=None):
"""
Preprocess and compile the regular expressions in routes_app/in/out
The resulting regex will match a pattern of the form:
@@ -383,7 +405,7 @@ def compile_regex(k, v):
# same for replacement pattern, but with \g
for item in regex_at.findall(v):
v = v.replace(item, r'\g<%s>' % item[1:])
return (re.compile(k, re.DOTALL), v)
return (re.compile(k, re.DOTALL), v, env or {})
def load_routers(all_apps):
"load-time post-processing of routers"
@@ -497,8 +519,9 @@ def regex_uri(e, regexes, tag, default=None):
(e.get('REMOTE_ADDR','localhost'),
e.get('wsgi.url_scheme', 'http').lower(), host,
e.get('REQUEST_METHOD', 'get').lower(), path)
for (regex, value) in regexes:
for (regex, value, custom_env) in regexes:
if regex.match(key):
e.update(custom_env)
rewritten = regex.sub(value, key)
log_rewrite('%s: [%s] [%s] -> %s' % (tag, key, value, rewritten))
return rewritten
@@ -686,7 +709,7 @@ def regex_filter_out(url, e=None):
e.get('request_method', 'get').lower(), items[0])
else:
items[0] = ':http://localhost:get %s' % items[0]
for (regex, value) in thread.routes.routes_out:
for (regex, value, tmp) in thread.routes.routes_out:
if regex.match(items[0]):
rewritten = '?'.join([regex.sub(value, items[0])] + items[1:])
log_rewrite('routes_out: [%s] -> %s' % (url, rewritten))
@@ -695,11 +718,14 @@ def regex_filter_out(url, e=None):
return url
def filter_url(url, method='get', remote='0.0.0.0', out=False, app=False, lang=None,
domain=(None,None), env=False, scheme=None, host=None, port=None):
"doctest/unittest interface to regex_filter_in() and regex_filter_out()"
regex_url = re.compile(r'^(?P<scheme>http|https|HTTP|HTTPS)\://(?P<host>[^/]*)(?P<uri>.*)')
match = regex_url.match(url)
def filter_url(url, method='get', remote='0.0.0.0',
out=False, app=False, lang=None,
domain=(None,None), env=False, scheme=None,
host=None, port=None):
"""
doctest/unittest interface to regex_filter_in() and regex_filter_out()
"""
match = regex_full_url.match(url)
urlscheme = match.group('scheme').lower()
urlhost = match.group('host').lower()
uri = match.group('uri')
@@ -994,6 +1020,7 @@ class MapUrlIn(object):
static_file = pjoin(self.request.env.applications_parent,
'applications', self.application,
'static', file)
self.extension = None
log_rewrite("route: static=%s" % static_file)
return static_file
@@ -1053,7 +1080,7 @@ class MapUrlIn(object):
if self.map_hyphen:
uri = uri.replace('_', '-')
app = app.replace('_', '-')
if self.extension != 'html':
if self.extension and self.extension != 'html':
uri += '.' + self.extension
if self.language:
uri = '/%s%s' % (self.language, uri)
@@ -1271,19 +1298,14 @@ def map_url_in(request, env, app=False):
# handle mapping of lang/static to static/lang in externally-rewritten URLs
# in case we have to handle them ourselves
if map.languages and map.map_static is False and map.arg0 == 'static' and map.args(1) in map.languages:
if 'es' in map.languages:
print 'handle static/lang %s' % map.args(1)
map.map_controller()
map.map_language()
else:
if 'es' in map.languages:
print 'NO handle static/lang %s' % map.args(1)
map.map_language()
map.map_controller()
static_file = map.map_static()
if 'es' in map.languages:
print 'static_file=%s' % static_file
if static_file:
map.update_request()
return (static_file, map.env)
map.map_function()
map.validate_args()

View File

@@ -88,7 +88,7 @@ except:
from simplejson import loads, dumps
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB, IS_INT_IN_RANGE
from gluon.utils import web2py_uuid
@@ -262,7 +262,8 @@ class MetaScheduler(threading.Thread):
start = time.time()
while p.is_alive() and (time.time()-start < task.timeout):
while p.is_alive() and (
not task.timeout or time.time()-start < task.timeout):
if tout:
try:
logging.debug(' partial output saved')
@@ -453,15 +454,20 @@ class Scheduler(MetaScheduler):
Field('args','text',default='[]',requires=TYPE(list)),
Field('vars','text',default='{}',requires=TYPE(dict)),
Field('enabled','boolean',default=True),
Field('start_time','datetime',default=now),
Field('start_time','datetime',default=now, requires=IS_NOT_EMPTY()),
Field('next_run_time','datetime',default=now),
Field('stop_time','datetime'),
Field('repeats','integer',default=1,comment="0=unlimited"),
Field('retry_failed', 'integer', default=0, comment="-1=unlimited"),
Field('period','integer',default=60,comment='seconds'),
Field('timeout','integer',default=60,comment='seconds'),
Field('repeats','integer',default=1,comment="0=unlimited",
requires=IS_INT_IN_RANGE(0, None)),
Field('retry_failed', 'integer', default=0, comment="-1=unlimited",
requires=IS_INT_IN_RANGE(-1, None)),
Field('period','integer',default=60,comment='seconds',
requires=IS_INT_IN_RANGE(0, None)),
Field('timeout','integer',default=60,comment='seconds',
requires=IS_INT_IN_RANGE(0, None)),
Field('sync_output', 'integer', default=0,
comment="update output every n sec: 0=never"),
comment="update output every n sec: 0=never",
requires=IS_INT_IN_RANGE(0, None)),
Field('times_run','integer',default=0,writable=False),
Field('times_failed','integer',default=0,writable=False),
Field('last_run_time','datetime',writable=False,readable=False),
@@ -869,4 +875,3 @@ def main():
if __name__=='__main__':
main()

View File

@@ -1,8 +1,8 @@
# this file exists for backward compatibility
__all__ = ['DAL','Field','drivers']
__all__ = ['DAL','Field','DRIVERS']
from dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, drivers, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType
from dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, DRIVERS, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType

View File

@@ -24,7 +24,7 @@ from html import FORM, INPUT, LABEL, OPTION, SELECT, BUTTON
from html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE
from html import URL, truncate_string, FIELDSET
from dal import DAL, Field, Table, Row, CALLABLETYPES, smart_query, \
bar_encode, regex_table_field, Reference
bar_encode, Reference, REGEX_TABLE_DOT_FIELD
from storage import Storage
from utils import md5_hash
from validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \
@@ -702,17 +702,17 @@ def formstyle_bootstrap(form, fields):
_submit = False
if isinstance(controls, INPUT):
controls['_class'] = 'input-xlarge'
controls.add_class('input-xlarge')
if controls['_type'] == 'submit':
# flag submit button
_submit = True
controls['_class'] = 'btn btn-primary'
if isinstance(controls, SELECT):
controls['_class'] = 'input-xlarge'
controls.add_class('input-xlarge')
if isinstance(controls, TEXTAREA):
controls['_class'] = 'input-xlarge'
controls.add_class('input-xlarge')
if isinstance(label, LABEL):
label['_class'] = 'control-label'
@@ -1376,8 +1376,8 @@ class SQLFORM(FORM):
# this should never happen but seems to happen to some
del fields['delete_this_record']
for field in self.table:
if not field.name in fields and field.writable==False \
and field.update is None:
if not field.name in fields and field.writable is False \
and field.update is None and field.compute is None:
if record_id and self.record:
fields[field.name] = self.record[field.name]
elif not self.table[field.name].default is None:
@@ -1498,11 +1498,10 @@ class SQLFORM(FORM):
selectfields = []
for field in fields:
name = str(field).replace('.','-')
criterion = []
options = search_options.get(field.type,None)
if options:
label = isinstance(field.label,str) and T(field.label) or field.label
selectfields.append((str(field),label))
selectfields.append(OPTION(label, _value=str(field)))
operators = SELECT(*[T(option) for option in options])
if field.type=='boolean':
value_input = SELECT(
@@ -1510,30 +1509,39 @@ class SQLFORM(FORM):
OPTION(T("False"),_value="F"),
_id="w2p_value_"+name)
else:
value_input = INPUT(_type='text',_id="w2p_value_"+name,
value_input = INPUT(_type='text',
_id="w2p_value_"+name,
_class=field.type)
new_button = INPUT(
_type="button", _value=T('New'),_class="btn",
_onclick="w2p_build_query('new','"+str(field)+"')")
_onclick="w2p_build_query('new','%s')" % field)
and_button = INPUT(
_type="button", _value=T('And'),_class="btn",
_onclick="w2p_build_query('and','"+str(field)+"')")
_onclick="w2p_build_query('and','%s')" % field)
or_button = INPUT(
_type="button", _value=T('Or'),_class="btn",
_onclick="w2p_build_query('or','"+str(field)+"')")
_onclick="w2p_build_query('or','%s')" % field)
close_button = INPUT(
_type="button", _value=T('Close'),_class="btn",
_onclick="jQuery('#w2p_query_panel').slideUp()")
criterion.extend([operators,value_input,new_button,and_button,or_button])
criteria.append(DIV(criterion, _id='w2p_field_%s' % name,
_class='w2p_query_row hidden'))
criteria.append(DIV(
operators,value_input,new_button,
and_button,or_button,close_button,
_id='w2p_field_%s' % name,
_class='w2p_query_row hidden',
_style='display:inline'))
criteria.insert(0,SELECT(
_id="w2p_query_fields",
_onchange="jQuery('.w2p_query_row').hide();jQuery('#w2p_field_'+jQuery('#w2p_query_fields').val().replace('.','-')).show();",
*[OPTION(label, _value=fname) for fname,label in selectfields]))
_style='float:left',
*selectfields))
fadd = SCRIPT("""
jQuery('#w2p_query_panel input,#w2p_query_panel select').css(
'width','auto').css('float','left');
jQuery('#w2p_query_panel input,#w2p_query_panel select').css('width','auto');
jQuery(function(){web2py_ajax_fields('#w2p_query_panel');});
function w2p_build_query(aggregator,a){
function w2p_build_query(aggregator,a) {
var b=a.replace('.','-');
var option = jQuery('#w2p_field_'+b+' select').val();
var value = jQuery('#w2p_value_'+b).val().replace('"','\\\\"');
@@ -1541,10 +1549,11 @@ class SQLFORM(FORM):
var k=jQuery('#web2py_keywords');
var v=k.val();
if(aggregator=='new') k.val(s); else k.val((v?(v+' '+ aggregator +' '):'')+s);
jQuery('#w2p_query_panel').slideUp();
}
""")
return CAT(DIV(_id="w2p_query_panel",_class='hidden',*criteria),fadd)
return CAT(
DIV(_id="w2p_query_panel",_class='hidden',*criteria),fadd)
@staticmethod
@@ -1656,15 +1665,16 @@ class SQLFORM(FORM):
if user_signature:
if (args != request.args and user_signature and \
not URL.verify(request,user_signature=user_signature)) or \
(not session.auth.user and \
(not (session.auth and session.auth.user) and \
('edit' in request.args or \
'create' in request.args or \
'delete' in request.args)):
session.flash = T('not authorized')
redirect(referrer)
def gridbutton(buttonclass='buttonadd',buttontext='Add',
buttonurl=url(args=[]),callback=None,delete=None,trap=True):
def gridbutton(buttonclass='buttonadd', buttontext='Add',
buttonurl=url(args=[]), callback=None,
delete=None, trap=True):
if showbuttontext:
if callback:
return A(SPAN(_class=ui.get(buttonclass)),
@@ -1826,14 +1836,15 @@ class SQLFORM(FORM):
try:
dbset = dbset(SQLFORM.build_query(
fields,request.vars.get('keywords','')))
rows = dbset.select()
rows = dbset.select(cacheable=True)
except Exception, e:
response.flash = T('Internal Error')
rows = []
else:
rows = dbset.select()
rows = dbset.select(cacheable=True)
else:
rows = dbset.select(left=left,orderby=orderby,*columns)
rows = dbset.select(left=left,orderby=orderby,
cacheable=True*columns)
if export_type in exportManager:
value = exportManager[export_type]
@@ -1853,12 +1864,6 @@ class SQLFORM(FORM):
session['_web2py_grid_referrer_'+formname] = url2(vars=request.vars)
console = DIV(_class='web2py_console %(header)s %(cornertop)s' % ui)
if create:
console.append(gridbutton(
buttonclass='buttonadd',
buttontext='Add',
buttonurl=url(args=['new',tablename])))
error = None
if searchable:
sfields = reduce(lambda a,b:a+b,
@@ -1867,13 +1872,13 @@ class SQLFORM(FORM):
search_widget = search_widget[tablename]
if search_widget=='default':
search_menu = SQLFORM.search_menu(sfields)
search_widget = lambda sfield, url: FORM(
search_widget = lambda sfield, url: DIV(FORM(
INPUT(_name='keywords',_value=request.vars.keywords,
_id='web2py_keywords',_onfocus="jQuery('#w2p_query_fields').change();jQuery('#w2p_query_panel').slideDown();"),
INPUT(_type='submit',_value=T('Search'),_class="btn"),
INPUT(_type='submit',_value=T('Clear'),_class="btn",
_onclick="jQuery('#web2py_keywords').val('');"),
search_menu,_method="GET",_action=url)
_method="GET",_action=url),search_menu)
form = search_widget and search_widget(sfields,url()) or ''
console.append(form)
keywords = request.vars.get('keywords','')
@@ -1887,12 +1892,19 @@ class SQLFORM(FORM):
error = T('Invalid query')
else:
subquery = None
if create:
console.append(gridbutton(
buttonclass='buttonadd',
buttontext='Add',
buttonurl=url(args=['new',tablename])))
if subquery:
dbset = dbset(subquery)
try:
if left or groupby:
c = 'count(*)'
nrows = dbset.select(c,left=left,groupby=groupby).first()[c]
nrows = dbset.select(c,left=left,cacheable=True,
groupby=groupby).first()[c]
else:
nrows = dbset.count()
except:
@@ -1976,7 +1988,9 @@ class SQLFORM(FORM):
try:
table_fields = [f for f in fields if f._tablename in tablenames]
rows = dbset.select(left=left,orderby=orderby,groupby=groupby,limitby=limitby,*table_fields)
rows = dbset.select(left=left,orderby=orderby,
groupby=groupby,limitby=limitby,
cacheable=True,*table_fields)
except SyntaxError:
rows = None
error = T("Query Not Supported")
@@ -2186,12 +2200,14 @@ class SQLFORM(FORM):
LI(A(T(db[referee]._plural),
_class=trap_class(),
_href=url()),
SPAN(divider,_class='divider'),_class='w2p_grid_breadcrumb_elem'))
SPAN(divider,_class='divider'),
_class='w2p_grid_breadcrumb_elem'))
if kwargs.get('details',True):
breadcrumbs.append(
LI(A(name,_class=trap_class(),
_href=url(args=['view',referee,id])),
SPAN(divider,_class='divider'),_class='w2p_grid_breadcrumb_elem'))
SPAN(divider,_class='divider'),
_class='w2p_grid_breadcrumb_elem'))
nargs+=2
else:
break
@@ -2217,16 +2233,18 @@ class SQLFORM(FORM):
del kwargs[key]
check = {}
id_field_name = table._id.name
for field in table._referenced_by:
if field.readable:
check[field.tablename] = check.get(field.tablename,[])+[field.name]
for rfield in table._referenced_by:
if rfield.readable:
check[rfield.tablename] = \
check.get(rfield.tablename,[])+[rfield.name]
for tablename in sorted(check):
linked_fieldnames = check[tablename]
tb = db[tablename]
multiple_links = len(linked_fieldnames)>1
for fieldname in linked_fieldnames:
if linked_tables is None or tablename in linked_tables:
t = T(tb._plural) if not multiple_links else T(tb._plural+'('+fieldname+')')
t = T(tb._plural) if not multiple_links else \
T(tb._plural+'('+fieldname+')')
args0 = tablename+'.'+fieldname
links.append(
lambda row,t=t,nargs=nargs,args0=args0:\
@@ -2560,7 +2578,7 @@ class ExportClass(object):
for record in self.rows:
row = []
for col in self.rows.colnames:
if not regex_table_field.match(col):
if not REGEX_TABLE_DOT_FIELD.match(col):
row.append(record._extra[col])
else:
(t, f) = col.split('.')

View File

@@ -136,7 +136,7 @@ class StorageList(Storage):
like Storage but missing elements default to [] instead of None
"""
def __getitem__(self,key):
return self.__gteattr__(key)
return self.__getattr__(key)
def __getattr__(self, key):
if key in self:
return getattr(self,key)

View File

@@ -9,4 +9,5 @@ from test_storage import *
from test_template import *
from test_utils import *
from test_contribs import *
from test_markmin import *
# from test_web import *

View File

@@ -411,12 +411,14 @@ class TestMinMaxSum(unittest.TestCase):
self.assertEqual(db.t.insert(a=3), 3)
s = db.t.a.min()
self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 1)
self.assertEqual(db(db.t.id > 0).select(s).first()[s], 1)
self.assertEqual(db().select(s).first()[s], 1)
s = db.t.a.max()
self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 3)
self.assertEqual(db().select(s).first()[s], 3)
s = db.t.a.sum()
self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 6)
self.assertEqual(db().select(s).first()[s], 6)
s = db.t.a.count()
self.assertEqual(db(db.t.id > 0).select(s)[0]._extra[s], 3)
self.assertEqual(db().select(s).first()[s], 3)
db.t.drop()

View File

@@ -81,6 +81,10 @@ try:
'1 shop')
self.assertEqual(str(T('%s %%{shop[0]}', 2)),
'2 shops')
self.assertEqual(str(T('%s %%{quark[0]}', 1)),
'1 quark')
self.assertEqual(str(T('%s %%{quark[0]}', 2)),
'2 quarks')
self.assertEqual(str(T.M('**Hello World**')),
'<strong>Hello World</strong>')
T.force('it')

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit tests for running web2py
"""
import sys
import os
if os.path.isdir('gluon'):
sys.path.append(os.path.realpath('gluon'))
else:
sys.path.append(os.path.realpath('../'))
import unittest
from gluon.contrib.markmin.markmin2html import run_doctests
class TestMarkmin(unittest.TestCase):
def testMarkmin(self):
run_doctests()
if __name__ == '__main__':
unittest.main()

View File

@@ -41,6 +41,11 @@ class TestWeb(unittest.TestCase):
client.get('index')
self.assertTrue('Welcome Homer' in client.text)
client = WebClient('http://127.0.0.1:8000/admin/default/')
client.post('index',data=dict(password='hello'))
client.get('site')
client.get('design/welcome')
if __name__ == '__main__':
unittest.main()

View File

@@ -839,6 +839,7 @@ class Auth(object):
table_event = None,
table_cas = None,
showid = False,
use_username = False,
login_email_validate = True,
login_userfield = None,
logout_onlogout = None,
@@ -1250,18 +1251,21 @@ class Auth(object):
else:
login = A(T('Login'), _href=href('login'))
register = A(T('Register'), _href=href('register'))
retrieve_username = A(T('Forgot username?'), _href=href('retrieve_username'))
lost_password = A(T('Lost password?'), _href=href('request_reset_password'))
retrieve_username = A(
T('Forgot username?'), _href=href('retrieve_username'))
lost_password = A(
T('Lost password?'), _href=href('request_reset_password'))
bar = SPAN(s1, login, s3, _class='auth_navbar')
if not 'register' in self.settings.actions_disabled:
bar.insert(-1, s2)
bar.insert(-1, register)
if self.use_username and \
not 'retrieve_username' in self.settings.actions_disabled:
if self.settings.use_username and not 'retrieve_username' \
in self.settings.actions_disabled:
bar.insert(-1, s2)
bar.insert(-1, retrieve_username)
if not 'request_reset_password' in self.settings.actions_disabled:
if not 'request_reset_password' \
in self.settings.actions_disabled:
bar.insert(-1, s2)
bar.insert(-1, lost_password)
return bar
@@ -1353,7 +1357,7 @@ class Auth(object):
writable=False,readable=False,
label=T('Modified By')))
def define_tables(self, username=False, signature=None,
def define_tables(self, username=None, signature=None,
migrate=True, fake_migrate=False):
"""
to be called unless tables are defined manually
@@ -1371,7 +1375,10 @@ class Auth(object):
db = self.db
settings = self.settings
self.use_username = username
if username is None:
username = settings.use_username
else:
settings.use_username = username
if not self.signature:
self.define_signature()
if signature==True:
@@ -1612,12 +1619,12 @@ class Auth(object):
checks = []
# make a guess about who this user is
for fieldname in ['registration_id','username','email']:
if fieldname in table_user.fields() and keys.get(fieldname,None):
if fieldname in table_user.fields() and \
keys.get(fieldname,None):
checks.append(fieldname)
value = keys[fieldname]
user = user or table_user._db(
(table_user.registration_id==value)|
(table_user[fieldname]==value)).select().first()
user = table_user(**{fieldname:value})
if user: break
if not checks:
return None
if not 'registration_id' in keys:

View File

@@ -1376,6 +1376,7 @@ class IS_GENERIC_URL(Validator):
"""
def __init__(
self,
error_message='enter a valid URL',
@@ -1402,6 +1403,9 @@ class IS_GENERIC_URL(Validator):
"prepend_scheme='%s' is not in allowed_schemes=%s" \
% (self.prepend_scheme, self.allowed_schemes)
GENERIC_URL = re.compile(r"%[^0-9A-Fa-f]{2}|%[^0-9A-Fa-f][0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]|%$|%[0-9A-Fa-f]$|%[^0-9A-Fa-f]$")
GENERIC_URL_VALID = re.compile(r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$")
def __call__(self, value):
"""
:param value: a string, the URL to validate
@@ -1411,12 +1415,9 @@ class IS_GENERIC_URL(Validator):
"""
try:
# if the URL does not misuse the '%' character
if not re.compile(
r"%[^0-9A-Fa-f]{2}|%[^0-9A-Fa-f][0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]|%$|%[0-9A-Fa-f]$|%[^0-9A-Fa-f]$"
).search(value):
if not self.GENERIC_URL.search(value):
# if the URL is only composed of valid characters
if re.compile(
r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$").match(value):
if self.GENERIC_URL_VALID.match(value):
# Then split up the URL into its components and check on
# the scheme
scheme = url_split_regex.match(value).group(2)
@@ -1432,11 +1433,10 @@ class IS_GENERIC_URL(Validator):
# ports, check to see if adding a valid scheme fixes
# the problem (but only do this if it doesn't have
# one already!)
if not re.compile('://').search(value) and None\
in self.allowed_schemes:
if value.find('://')<0 and None in self.allowed_schemes:
schemeToUse = self.prepend_scheme or 'http'
prependTest = self.__call__(schemeToUse
+ '://' + value)
prependTest = self.__call__(
schemeToUse + '://' + value)
# if the prepend test succeeded
if prependTest[1] is None:
# if prepending in the output is enabled
@@ -1791,6 +1791,9 @@ class IS_HTTP_URL(Validator):
"""
GENERIC_VALID_IP = re.compile("([\w.!~*'|;:&=+$,-]+@)?\d+\.\d+\.\d+\.\d+(:\d*)*$")
GENERIC_VALID_DOMAIN = re.compile("([\w.!~*'|;:&=+$,-]+@)?(([A-Za-z0-9]+[A-Za-z0-9\-]*[A-Za-z0-9]+\.)*([A-Za-z0-9]+\.)*)*([A-Za-z]+[A-Za-z0-9\-]*[A-Za-z0-9]+)\.?(:\d*)*$")
def __init__(
self,
error_message='enter a valid URL',
@@ -1843,16 +1846,12 @@ class IS_HTTP_URL(Validator):
# if there is an authority component
if authority:
# if authority is a valid IP address
if re.compile(
"([\w.!~*'|;:&=+$,-]+@)?\d+\.\d+\.\d+\.\d+(:\d*)*$").match(authority):
if self.GENERIC_VALID_IP.match(authority):
# Then this HTTP URL is valid
return (value, None)
else:
# else if authority is a valid domain name
domainMatch = \
re.compile(
"([\w.!~*'|;:&=+$,-]+@)?(([A-Za-z0-9]+[A-Za-z0-9\-]*[A-Za-z0-9]+\.)*([A-Za-z0-9]+\.)*)*([A-Za-z]+[A-Za-z0-9\-]*[A-Za-z0-9]+)\.?(:\d*)*$"
).match(authority)
domainMatch = self.GENERIC_VALID_DOMAIN.match(authority)
if domainMatch:
# if the top-level domain really exists
if domainMatch.group(5).lower()\
@@ -1865,13 +1864,13 @@ class IS_HTTP_URL(Validator):
path = componentsMatch.group(5)
# relative case: if this is a valid path (if it starts with
# a slash)
if re.compile('/').match(path):
if path.startswith('/'):
# Then this HTTP URL is valid
return (value, None)
else:
# abbreviated case: if we haven't already, prepend a
# scheme and see if it fixes the problem
if not re.compile('://').search(value):
if value.find('://')<0:
schemeToUse = self.prepend_scheme or 'http'
prependTest = self.__call__(schemeToUse
+ '://' + value)
@@ -2521,9 +2520,11 @@ class CLEANUP(Validator):
removes special characters on validation
"""
REGEX_CLEANUP = re.compile('[^\x09\x0a\x0d\x20-\x7e]')
def __init__(self, regex='[^\x09\x0a\x0d\x20-\x7e]'):
self.regex = re.compile(regex)
def __init__(self, regex=None):
self.regex = self.REGEX_CLEANUP if regex is None \
else re.compile(regex)
def __call__(self, value):
v = self.regex.sub('',str(value).strip())
@@ -2790,11 +2791,13 @@ class IS_STRONG(object):
class IS_IN_SUBSET(IS_IN_SET):
REGEX_W = re.compile('\w+')
def __init__(self, *a, **b):
IS_IN_SET.__init__(self, *a, **b)
def __call__(self, value):
values = re.compile("\w+").findall(str(value))
values = self.REGEX_W.findall(str(value))
failures = [x for x in values if IS_IN_SET.__call__(self, x)[1]]
if failures:
return (value, translate(self.error_message))

View File

@@ -1004,9 +1004,9 @@ def start(cron=True):
print ProgramAuthor
print ProgramVersion
from dal import drivers
from dal import DRIVERS
if not options.nobanner:
print 'Database drivers available: %s' % ', '.join(drivers)
print 'Database drivers available: %s' % ', '.join(DRIVERS)
# ## if -L load options from options.config file

View File

@@ -27,12 +27,14 @@
# set by the setLevel call, the [logger_myapp] section, and the [handler_...]
# section. For example, you will not see DEBUG messages unless all three are
# set to DEBUG.
#
# Available levels: DEBUG INFO WARNING ERROR CRITICAL
[loggers]
keys=root,rocket,markdown,web2py,rewrite,cron,app,welcome
[handlers]
keys=consoleHandler,messageBoxHandler
keys=consoleHandler,messageBoxHandler,rotatingFileHandler
#keys=consoleHandler,rotatingFileHandler
#keys=osxSysLogHandler
@@ -41,50 +43,53 @@ keys=simpleFormatter
[logger_root]
level=WARNING
handlers=consoleHandler
handlers=consoleHandler,rotatingFileHandler
[logger_web2py]
level=WARNING
handlers=consoleHandler
handlers=consoleHandler,rotatingFileHandler
qualname=web2py
propagate=0
# URL rewrite logging (routes.py)
# See also the logging parameter in routes.py
#
[logger_rewrite]
level=WARNING
qualname=web2py.rewrite
handlers=consoleHandler
handlers=consoleHandler,rotatingFileHandler
propagate=0
[logger_cron]
level=WARNING
qualname=web2py.cron
handlers=consoleHandler
handlers=consoleHandler,rotatingFileHandler
propagate=0
# generic app handler
[logger_app]
level=WARNING
qualname=web2py.app
handlers=consoleHandler
handlers=consoleHandler,rotatingFileHandler
propagate=0
# welcome app handler
[logger_welcome]
level=WARNING
qualname=web2py.app.welcome
qualname=web2py.app.welcome,rotatingFileHandler
handlers=consoleHandler
propagate=0
# loggers for legacy getLogger calls: Rocket and markdown
[logger_rocket]
level=WARNING
handlers=consoleHandler,messageBoxHandler
handlers=consoleHandler,messageBoxHandler,rotatingFileHandler
qualname=Rocket
propagate=0
[logger_markdown]
level=WARNING
handlers=consoleHandler
handlers=consoleHandler,rotatingFileHandler
qualname=markdown
propagate=0
@@ -106,7 +111,7 @@ args=()
#
[handler_rotatingFileHandler]
class=handlers.RotatingFileHandler
level=INFO
level=DEBUG
formatter=simpleFormatter
args=("logs/web2py.log", "a", 1000000, 5)

View File

@@ -26,10 +26,8 @@ administrator_email = 'you@localhost'
while 1:
for file in os.listdir(path):
filename = os.path.join(path, file)
if not ALLOW_DUPLICATES:
fileobj = open(filename, 'r')
fileobj = open(file, 'r')
try:
file_data = fileobj.read()
finally:
@@ -42,10 +40,10 @@ while 1:
hashes[key] = 1
error = RestrictedError()
error.load(request, request.application, filename)
error.load(request, request.application, file)
mail.send(to=administrator_email, subject='new web2py ticket', message=error.traceback)
os.unlink(filename)
os.unlink(os.path.join(path, file))
time.sleep(SLEEP_MINUTES * 60)

View File

@@ -9,10 +9,10 @@ if '__file__' in globals():
elif hasattr(sys, 'frozen'):
path = os.path.dirname(os.path.abspath(sys.executable)) # for py2exe
else: #should never happen
path = os.getcwd()
path = os.getcwd()
os.chdir(path)
sys.path = [path]+[p for p in sys.path if not p==path]
sys.path = [path]+[p for p in sys.path if not p == path]
# import gluon.import_all ##### This should be uncommented for py2exe.py
import gluon.widget