Compare commits

...
66 Commits
Author SHA1 Message Date
mdipierro 67485d16a5 R-2.14.4 2016-04-12 16:21:05 -05:00
mdipierro eba8ad4b55 fixed CHANGELOG 2016-04-12 15:45:54 -05:00
mdipierro 4ed6fb7ed9 Merge pull request #1288 from carpaIdea/patch-1
Missing <!DOCTYPE html>
2016-04-12 15:44:19 -05:00
carpaIdea 294c67194f Missing <!DOCTYPE html>
Of course if there is not any intentional reason to turn on "quirks mode" in browsers.

https://developer.mozilla.org/en-US/docs/Web/CSS/Common_CSS_Questions#Why_doesn't_my_CSS_which_is_valid_render_correctly
https://developer.mozilla.org/it/docs/Quirks_Mode_and_Standards_Mode
2016-04-12 22:24:30 +02:00
mdipierro 70f793b422 Merge pull request #1287 from leonelcamara/test_week6
Test week6
2016-04-12 11:14:14 -05:00
Leonel Câmara 9552d9d6d0 fixed sort=True test 2016-04-12 16:28:33 +01:00
Leonel Câmara 9ead66b6db test IS_IN_DB label is a Field and self.sort = True 2016-04-12 16:00:33 +01:00
Leonel Câmara 00c65ad160 Complete coverage for Mail.Attachment 2016-04-12 15:28:48 +01:00
Leonel Câmara b5c8b3ad25 closes #1286 2016-04-12 15:10:14 +01:00
mdipierro 5ca65d55d2 Merge pull request #1284 from BuhtigithuB/more-markmin-pep8
More markmin pep8
2016-04-11 13:38:54 -05:00
mdipierro 3f200fdc22 Merge pull request #1283 from leonelcamara/grid_issues
closes #1188
2016-04-11 13:38:37 -05:00
Hardirc 409c973dc4 Enhance markmin2pdf.py PEP8 2016-04-10 09:36:06 -04:00
Hardirc 59a194842d Enhance markmin2latex.py PEP8 2016-04-10 09:34:29 -04:00
Leonel Câmara ee8b11db2c closes #1188 2016-04-10 12:08:25 +01:00
mdipierro 8ef04ac425 Merge pull request #1282 from BuhtigithuB/Feature/heading-anchor
Feature/heading anchor
2016-04-09 18:39:39 -05:00
mdipierro 83cf098c07 fixed stupid.css and impersonate 2016-04-09 10:30:31 -05:00
mdipierro 70b41fa15e Merge pull request #1280 from BuhtigithuB/pep8-gluon-html-py
Enhance gluon/html.py PEP8
2016-04-08 23:37:54 -05:00
mdipierro 56af81f247 Merge pull request #1278 from BuhtigithuB/pep8-welcome
Pep8 welcome
2016-04-08 23:37:32 -05:00
mdipierro 7fd30d8360 Merge pull request #1276 from BuhtigithuB/Improve/web2py-com-display
Improve/web2py com display
2016-04-08 23:35:43 -05:00
mdipierro e1aefa2307 Merge pull request #1275 from BuhtigithuB/Improve/gluon-tools-py
PEP8 Recaptcha/2 docstring
2016-04-08 23:35:10 -05:00
mdipierro 7a2316ec9a Merge pull request #1260 from BuhtigithuB/add-test-sqlform
SQLFORM() basic check
2016-04-08 23:34:51 -05:00
Hardirc e48e47beb2 Enhance markmin2html.py contrib PEP8 2016-04-08 22:12:43 -04:00
Hardirc 864c308246 Enhance gluon/html.py PEP8 2016-04-08 21:11:25 -04:00
Hardirc 994f3e7ae4 Enhance welcome menu.py PEP8 2016-04-08 20:17:35 -04:00
Hardirc 1d2f74440e Enhance welcome routes.example.py PEP8 2016-04-08 20:13:58 -04:00
Hardirc 704ceec16f Enhance welcome controllers/default.py PEP8 2016-04-08 20:13:30 -04:00
Hardirc 038e25c259 Enhance welcome db.py PEP8 2016-04-08 20:05:21 -04:00
Richard Vézina c3aa02b3ce Improve welcome (web2py.com) site aesthetic 2016-04-08 15:16:58 -04:00
Richard Vézina 5cc4487d8c Improve PEP8 download.html 2016-04-08 14:17:56 -04:00
Richard Vézina d50e6aab6b Improve default.py PEP8 2016-04-08 11:52:09 -04:00
Richard Vézina 1d21f45e3e PEP8 Recaptcha/2 docstring 2016-04-07 10:19:57 -04:00
mdipierro 99a323c7ad Merge pull request #1272 from BuhtigithuB/new-logout-bare
New logout_bare() for shell logout and refactor test using it
2016-04-07 09:17:19 -05:00
Hardirc e0d86462c8 New logout_bare() for shell logout and refactor test using it 2016-04-06 22:46:24 -04:00
mdipierro ff0d10ac4f Merge pull request #1271 from leonelcamara/delete_unused_reserved
delete unused reserved_sql_keywords.py because the DAL uses this one …
2016-04-06 20:58:05 -05:00
mdipierro eee7be75c8 Merge pull request #1265 from BuhtigithuB/improve-tools-tests-some-more
Improve tools tests some more
2016-04-06 20:57:30 -05:00
mdipierro 3c69716672 Merge pull request #1270 from leonelcamara/test_week5b
Test Mail.Attachment sending test_tools.py itself
2016-04-06 20:57:06 -05:00
Leonel Câmara ad4b0eee54 delete unused reserved_sql_keywords.py because the DAL uses this one pydal/contrib/reserved_sql_keywords.py 2016-04-06 23:06:57 +01:00
Richard Vézina 0128ce3a93 Make test_login_bare() works, new test_impersonate() 2016-04-06 16:58:58 -04:00
Leonel Câmara 0629df71ef Test Mail.Attachment sending test_tools.py itself 2016-04-06 20:04:21 +01:00
mdipierro 4d1a4c48e6 Merge pull request #1268 from mbelletti/fix/cas_login
Fix #1267 cas_login
2016-04-06 13:49:28 -05:00
Massimiliano Belletti 2ffdb716cd Fix #1267 cas_login 2016-04-06 17:06:23 +02:00
mdipierro 40f04de9d2 fixed scripts/setup-web2py-nginx-uwsgi-ubuntu.sh 2016-04-06 09:20:13 -05:00
mdipierro 52615fbca7 Merge pull request #1263 from niphlod/tests/scheduler
initial tests for scheduler.py, plus a minor fix for JobGraph
2016-04-05 17:00:00 -05:00
niphlod 6abb78c559 initial tests for scheduler.py 2016-04-04 22:58:42 +02:00
Hardirc db701ffea8 correct unproperly generated SQLFORM methods and basic tests 2016-04-03 16:24:13 -04:00
mdipierro 0804c28331 Merge pull request #1259 from BuhtigithuB/new-test-sqlform
let start testing sqlhtml.py
2016-04-02 15:02:02 -05:00
mdipierro 24bc51447c Merge pull request #1257 from BuhtigithuB/improve-tools-tests
Improve tools tests
2016-04-02 15:00:41 -05:00
mdipierro ccbbdc2493 Merge branch 'leonelcamara-prettier_examples' 2016-04-02 14:59:23 -05:00
mdipierro d94e8415c7 reverted stupid 2016-04-02 14:57:36 -05:00
mdipierro 0a62e86156 Merge pull request #1254 from leonelcamara/test_week4b
Seriously increase template.py coverage
2016-04-02 14:53:39 -05:00
mdipierro 5744c06f59 Merge pull request #1253 from leonelcamara/test_week4
More test coverage for validators.py
2016-04-02 14:52:42 -05:00
mdipierro 947f92774b Merge pull request #1252 from BuhtigithuB/improve-test-chache
Improve test chache
2016-04-02 14:52:26 -05:00
Hardirc 4001407add let start testing sqlhtml.py 2016-04-02 15:49:18 -04:00
mdipierro 46ffaa6aea improved search form 2016-04-02 14:48:03 -05:00
mdipierro ba2be80080 updated stupic.css and suport page 2016-04-02 14:35:28 -05:00
Richard Vézina 3a9221a2b9 Reach fully some partially hit lines 2016-03-31 17:00:50 -04:00
Richard Vézina e0eb425223 Little improvement of tools.py 2016-03-31 16:25:55 -04:00
Richard Vézina f7ad31f066 Refactor TestAuth() with setUp() and add test case 2016-03-31 16:25:02 -04:00
Leonel Câmara d0f6ef4783 made stuff prettier
added myself to the contributor list :P
2016-03-31 00:29:28 +01:00
Richard Vézina 104d616cb9 Reorder test case and make inventory of what missing 2016-03-30 16:32:37 -04:00
Richard Vézina a8703270da PEP8 enhancement 2016-03-30 16:20:02 -04:00
Leonel Câmara ad57c3c613 test block extend, super, delimiters, etc. 2016-03-30 02:33:29 +01:00
Leonel Câmara 5c292640ba Complete coverage for IS_IN_SET
Removed unreachable code in IS_IN_SET
        if failures and self.theset:
            if self.multiple and (value is None or value == '')
It's impossible to have *failures* and have value be None or '' at the same time
2016-03-28 14:47:58 +01:00
Leonel Câmara 5cbf381a2c More test coverage for validators.py
Fixed a bug in IS_EMAIL throwing exceptions when asked to validate anything other than a string which was problematic for ANY_OF
Fixed a bug in ANY_OF.formatter where it was trying to format with a validator that didn't validate
2016-03-27 14:23:24 +01:00
Hardirc 9ac1e7188f tests inventory and reordering + PEP8 enhancement 2016-03-26 13:19:29 -04:00
Hardirc 58a8ba067c cache.py PEP8 enhancement 2016-03-26 13:11:27 -04:00
35 changed files with 2178 additions and 2571 deletions
+2 -2
View File
@@ -1,5 +1,4 @@
## 2.14.1 ## 2.14.1-4
- fixed two major security issues that caused the examples app to leak information - fixed two major security issues that caused the examples app to leak information
- new Auth(…,host_names=[…]) to prevent host header injection - new Auth(…,host_names=[…]) to prevent host header injection
@@ -45,6 +44,7 @@
sessiondb = RedisSession(redis_conn=rconn, session_expiry=False) sessiondb = RedisSession(redis_conn=rconn, session_expiry=False)
session.connect(request, response, db = sessiondb) session.connect(request, response, db = sessiondb)
Many thanks to Richard and Simone for their work and dedication.
## 2.13.* ## 2.13.*
+1 -1
View File
@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked" echo "remember that pymysql was tweaked"
src: src:
### Use semantic versioning ### Use semantic versioning
echo 'Version 2.14.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION echo 'Version 2.14.4-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files ### rm -f all junk files
make clean make clean
### clean up baisc apps ### clean up baisc apps
+1 -1
View File
@@ -1 +1 @@
Version 2.14.3-stable+timestamp.2016.03.26.17.54.43 Version 2.14.4-stable+timestamp.2016.04.12.15.44.54
+8 -8
View File
@@ -10,7 +10,7 @@ session.forget()
cache_expire = not request.is_local and 300 or 0 cache_expire = not request.is_local and 300 or 0
#@cache.action(time_expire=300, cache_model=cache.ram, quick='P') # @cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def index(): def index():
return response.render() return response.render()
@@ -19,14 +19,13 @@ def index():
def what(): def what():
import urllib import urllib
try: try:
images = XML(urllib.urlopen( images = XML(urllib.urlopen('http://www.web2py.com/poweredby/default/images').read())
'http://www.web2py.com/poweredby/default/images').read())
except: except:
images = [] images = []
return response.render(images=images) return response.render(images=images)
#@cache.action(time_expire=300, cache_model=cache.ram, quick='P') # @cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def download(): def download():
return response.render() return response.render()
@@ -74,14 +73,15 @@ def license():
filename = os.path.join(request.env.gluon_parent, 'LICENSE') filename = os.path.join(request.env.gluon_parent, 'LICENSE')
return response.render(dict(license=MARKMIN(read_file(filename)))) return response.render(dict(license=MARKMIN(read_file(filename))))
def version(): def version():
if request.args(0)=='raw': if request.args(0) == 'raw':
return request.env.web2py_version return request.env.web2py_version
from gluon.fileutils import parse_version from gluon.fileutils import parse_version
(a, b, c, pre_release, build) = parse_version(request.env.web2py_version) (a, b, c, pre_release, build) = parse_version(request.env.web2py_version)
return 'Version %i.%i.%i (%.4i-%.2i-%.2i %.2i:%.2i:%.2i) %s' % ( return 'Version %i.%i.%i (%.4i-%.2i-%.2i %.2i:%.2i:%.2i) %s' % \
a,b,c,build.year,build.month,build.day, (a, b, c, build.year, build.month, build.day, build.hour, build.minute, build.second, pre_release)
build.hour,build.minute,build.second,pre_release)
@cache.action(time_expire=300, cache_model=cache.ram, quick='P') @cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def examples(): def examples():
+6 -6
View File
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
response.menu = [ response.menu = [
(T('Home'), False, URL('default', 'index')), (T('Home'), request.controller == 'default' and request.function == 'index', URL('default', 'index')),
(T('About'), False, URL('default', 'what')), (T('About'), request.controller == 'default' and request.function == 'what', URL('default', 'what')),
(T('Download'), False, URL('default', 'download')), (T('Download'), request.controller == 'default' and request.function == 'download', URL('default', 'download')),
(T('Docs & Resources'), False, URL('default', 'documentation')), (T('Docs & Resources'), request.controller == 'default' and request.function == 'documentation', URL('default', 'documentation')),
(T('Support'), False, URL('default', 'support')), (T('Support'), request.controller == 'default' and request.function == 'support', URL('default', 'support')),
(T('Contributors'), False, URL('default', 'who'))] (T('Contributors'), request.controller == 'default' and request.function == 'who', URL('default', 'who'))]
######################################################################### #########################################################################
## Changes the menu active item ## Changes the menu active item
@@ -0,0 +1,69 @@
/* Gray the black as suggested by Anthony */
h1,h2,h3,h4,h5,h6 {color: rgb(35, 35, 35); text-transform:none}
.black {
color: rgb(35, 35, 35);
background-color: rgb(35, 35, 35);
}
/* Spacing between thead and tbody */
/* Ref: http://stackoverflow.com/questions/9258754/spacing-between-thead-and-tbody */
tbody:before {
content: "-";
display: block;
line-height: 1em;
color: transparent;
}
/* Improve buttons in download page */
th, td {padding: 0}
tbody tr:hover {background-color:transparent}
tbody tr {border-bottom: none}
p {text-align: left}
p, li { line-height: 1.6em}
/* Improve CODE() display though padding has no effect as some PRE are hardcoded somewhere can't find it */
/* padding of 10px should make it... */
pre {background-color: rgb(35, 35, 35)!important; border-radius:5px; color:white; padding: 10px}
/* Improve buttons in download page */
a.btn.btn180 {padding:10px; font-size:1.2em; width:200px}
.menu .web2py-menu-active a {
color: #26a69a;
}
.spaced-vertical {
margin: 0 0.5em 0.5em 0;
}
.btn:hover,
a.noeffect img:hover {
transition: scale .5s;
transform: scale(1.05);
}
.btn,
a.noeffect img {
transition: all .2s ease-in-out;
}
/* Lower saturation of color #26a69a - 20 points lower */
/* The below change to color #26a69a should come before other color change or they override all buttons background-color */
/* The color should maybe change at stupid.css level as it herited from there also in stupid.css it would be better
to define this color at one place actually color is defined all over the place */
a {color:#47a69d}
.btn, button, [type=button], [type=submit] {background-color:#47a69d}
.progress .determinate {background-color:#47a69d}
.progress .indeterminate {background-color:#47a69d}
a:not(.btn):not(.noeffect):hover {color:#47a69d}
a:not(.btn):not(.noeffect):after {background-color:#47a69d}
.tags > span {background-color:#47a69d}
.tags.dismissible > span.off:hover {background-color:#47a69d}
.aquamarine{background-color:#47a69d}
/* Lower the saturation of 20 points */
.green {background-color: #58cc65}
.yellow {background-color: #ffe333}
.red {background-color: #cc4229}
+30 -31
View File
@@ -6,7 +6,7 @@
/*** basic styles ***/ /*** basic styles ***/
*, *:after, *:before {border:0; margin:0; padding:0; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box} *, *:after, *:before {border:0; margin:0; padding:0; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box}
html, body {max-width: 100vw !important; overflow-x: hidden !important} html, body {max-width: 100vw; overflow-x: hidden}
body {font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif} body {font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif}
p, li {margin-bottom:0.5em} p, li {margin-bottom:0.5em}
p {text-align:justify} p {text-align:justify}
@@ -23,15 +23,13 @@ h5{font-size:1.4em; margin:0.6em 0 0.25em 0}
h6{font-size:1.2em; margin:0.5em 0 0.25em 0} h6{font-size:1.2em; margin:0.5em 0 0.25em 0}
table {border-collapse:collapse} table {border-collapse:collapse}
tbody tr:hover {background-color:#fbf6d9} tbody tr:hover {background-color:#fbf6d9}
td, th {padding:5px; vertical-align:top; text-align:left; border:0}
thead tr {background-color:#f1f1f1} thead tr {background-color:#f1f1f1}
tbody tr {border-bottom:2px solid #f1f1f1} tbody tr {border-bottom:2px solid #f1f1f1}
th {font-weight:bold; padding:5px; vertical-align:bottom; text-align:left} td, th {padding: 5px; text-align: left; vertical-align:top}
thead th {vertical-align:bottom} thead th {vertical-align:bottom}
tbody th {vertical-align:top}
header, footer {with:100%} header, footer {with:100%}
@media (max-width:599px) { @media all and (max-width:599px) {
h1{font-size:2em} h1{font-size:2em}
h2{font-size:1.8em} h2{font-size:1.8em}
h3{font-size:1.6em} h3{font-size:1.6em}
@@ -41,20 +39,20 @@ header, footer {with:100%}
} }
/*** buttons ***/ /*** buttons ***/
.btn, button, [type=button], [type=submit] {padding:0.5em 1em !important; margin:0 0.5em 0.5em 0; display:inline-block; background-color:#26a69a; color:white} .btn, button, [type=button], [type=submit] {padding:0.5em 1em; margin:0 0.5em 0.5em 0; display:inline-block; background-color:#26a69a; color:white}
.btn:hover, button:hover, [type=button]:hover, [type=submit]:hover {box-shadow:0 0 10px #666; text-decoration:none; cursor:pointer} .btn:hover, button:hover, [type=button]:hover, [type=submit]:hover {box-shadow:0 0 10px #666; text-decoration:none; cursor:pointer}
.btn.small, table .btn {padding:0.25em 0.5em !important; font-size:0.8em} .btn.small, table .btn {padding:0.25em 0.5em; font-size:0.8em}
.btn.large {padding:1em 2em !important; font-size:1.2em} .btn.large {padding:1em 2em; font-size:1.2em}
.btn.oval {border-radius:50%} .btn.oval {border-radius:50%}
/*** helpers ***/ /*** helpers ***/
.rounded {-moz-border-radius:5px; border-radius:5px} .rounded {-moz-border-radius:5px; border-radius:5px}
.padded {padding:10px 20px !important} .padded {padding:10px 20px}
.center {text-align:center !important; margin-left:auto; margin-right:auto} .center {text-align:center; margin-left:auto; margin-right:auto}
.center>div {text-align:left} .center>div {text-align:left}
.right {right:0; text-align:right} .right {right:0; text-align:right}
.middle div {vertical-align:middle !important} .middle div {vertical-align:middle}
.bottom div {vertical-align:bottom !important} .bottom div {vertical-align:bottom}
.xscroll {overflow-x:scroll} .xscroll {overflow-x:scroll}
.yscroll {overflow-y:scroll} .yscroll {overflow-y:scroll}
.nowrap {white-space:nowrap; overflow-x:hidden} .nowrap {white-space:nowrap; overflow-x:hidden}
@@ -71,14 +69,15 @@ textarea {width:100%; border:1px solid #ddd; padding:4px 8px; outline:none; outl
select {-webkit-appearance:none; outline:none; padding:0.5em 1em; border-radius:0; margin:0.5px; border-bottom:1px solid #ddd; width:100%;background-color:transparent} select {-webkit-appearance:none; outline:none; padding:0.5em 1em; border-radius:0; margin:0.5px; border-bottom:1px solid #ddd; width:100%;background-color:transparent}
input, textarea, select, button {font-size:12px} input, textarea, select, button {font-size:12px}
input:not([type]):hover, input:not([type=checkbox]):not([type=radio]):not([type=button]):not([type=submit]):hover, select:hover, textarea:hover {background-color:#fbf6d9; transition:background-color 1s ease} input:not([type]):hover, input:not([type=checkbox]):not([type=radio]):not([type=button]):not([type=submit]):hover, select:hover, textarea:hover {background-color:#fbf6d9; transition:background-color 1s ease}
input:invalid, input.error {background:#cc1f00!important;color:white} input:invalid, input.error {background:#cc1f00;color:white}
/*** grid ***/ /*** grid ***/
.container {margin-right:-20px} .container {margin-right:-20px}
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters, .container>.fill{display:inline-block; padding: 0 20px 0 0; vertical-align:top} .container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters {display:inline-block; padding: 0 20px 0 0; vertical-align:top}
.container>.fill {width:100%; margin-right:-20px} .container>.fill{display: inline-block}
.container img, .container video {max-width:100%} .container img, .container video {max-width:100%}
@media (min-width:800px) {
@media all and (min-width:800px) {
.max900 {max-width:900px; margin-left:auto; margin-right:auto} .max900 {max-width:900px; margin-left:auto; margin-right:auto}
.quarter {width:25%; margin-right:-5px} .quarter {width:25%; margin-right:-5px}
.half {width:50%; margin-right:-10px} .half {width:50%; margin-right:-10px}
@@ -86,7 +85,7 @@ input:invalid, input.error {background:#cc1f00!important;color:white}
.twothirds {width:66.66%; margin-right:-13.33px} .twothirds {width:66.66%; margin-right:-13.33px}
.threequarters {width:75%; margin-right:-15px} .threequarters {width:75%; margin-right:-15px}
} }
@media (min-width:600px) and (max-width:799px) { @media all and (min-width:600px) and (max-width:799px) {
.quarter.compressible {width:25%; margin-right:-5px} .quarter.compressible {width:25%; margin-right:-5px}
.half.compressible {width:50%; margin-right:-10px} .half.compressible {width:50%; margin-right:-10px}
.threequarters.compressible {width:75%; margin-right:-15px} .threequarters.compressible {width:75%; margin-right:-15px}
@@ -95,7 +94,7 @@ input:invalid, input.error {background:#cc1f00!important;color:white}
.twothirds {width:66.66%; margin-right:-13.33px} .twothirds {width:66.66%; margin-right:-13.33px}
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right {float:left; text-align:left} label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right {float:left; text-align:left}
} }
@media (max-width:599px) { @media all and (max-width:599px) {
.quarter:not(.compressible), .half:not(.compressible), .third:not(.compressible), .twothirds:not(.compressible), .threequarters:not(.compressible) {width:100%;} .quarter:not(.compressible), .half:not(.compressible), .third:not(.compressible), .twothirds:not(.compressible), .threequarters:not(.compressible) {width:100%;}
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right, label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right,
label.third:not(.compressible).right, label.twothirds:not(.compressible).right {float:left; text-align:left} label.third:not(.compressible).right, label.twothirds:not(.compressible).right {float:left; text-align:left}
@@ -115,7 +114,7 @@ input:invalid, input.error {background:#cc1f00!important;color:white}
display:block; display:block;
width:120%; width:120%;
background-color:#acece6; background-color:#acece6;
border-radius:0 !important; border-radius:0;
background-clip:padding-box; background-clip:padding-box;
overflow:hidden; overflow:hidden;
} }
@@ -193,17 +192,17 @@ input:invalid, input.error {background:#cc1f00!important;color:white}
.menu ul ul {top:0; left:80%; z-index:1100} .menu ul ul {top:0; left:80%; z-index:1100}
.menu li:hover > ul {visibility:visible; opacity:1} .menu li:hover > ul {visibility:visible; opacity:1}
.menu>li>ul>li:first-child:before{content:''; position:absolute; width:1px; height:1px; border:10px solid transparent; left:50px; top:-20px; margin-left:-10px; border-bottom-color:white} .menu>li>ul>li:first-child:before{content:''; position:absolute; width:1px; height:1px; border:10px solid transparent; left:50px; top:-20px; margin-left:-10px; border-bottom-color:white}
.menu.dark ul {background:black; border:1px solid black} .menu.dark ul {background:#111111; border:1px solid #111111}
.menu.dark ul a {color:white} .menu.dark ul a {color:white}
.menu.dark>li>ul>li:first-child:before{border-bottom-color:black} .menu.dark>li>ul>li:first-child:before{border-bottom-color:#111111}
@media (max-width:599px) { @media all and (max-width:599px) {
header .menu li, header .menu ul {width: 100%} header .menu li, header .menu ul {width: 100%}
header .menu.right {float:left; text-align:left} header .menu.right {float:left; text-align:left}
header .menu ul ul {top:2.5em; left:-1px} header .menu ul ul {top:2.5em; left:-1px}
} }
@media (min-width:600px) { @media all and (min-width:600px) {
.ham {display:none!important} .ham {display:none!important}
.burger.accordion * {max-height:1000px; overflow:visible} .burger.accordion * {max-height:1000px; overflow:visible}
} }
@@ -268,8 +267,8 @@ a:not(.btn):not(.noeffect):after {
[data-tooltip]:before, [data-tooltip]:after {display:none; position:absolute; top:0} [data-tooltip]:before, [data-tooltip]:after {display:none; position:absolute; top:0}
[data-tooltip]:hover:after,[data-tooltip]:hover:before {display:block} [data-tooltip]:hover:after,[data-tooltip]:hover:before {display:block}
[data-tooltip]:hover:before { [data-tooltip]:hover:before {
border-bottom:.6em solid black; border-bottom:.6em solid #111111;
border-bottom:.6em solid black; border-bottom:.6em solid #111111;
border-left:7px solid transparent; border-left:7px solid transparent;
border-right:7px solid transparent; border-right:7px solid transparent;
content:""; content:"";
@@ -288,7 +287,7 @@ a:not(.btn):not(.noeffect):after {
content:attr(data-tooltip); content:attr(data-tooltip);
left:0; left:0;
top:2px; top:2px;
margin-left:-20; margin-left:-20px;
margin-top:1.5em; margin-top:1.5em;
padding:5px 15px; padding:5px 15px;
white-space:pre-wrap; white-space:pre-wrap;
@@ -339,9 +338,6 @@ a:not(.btn):not(.noeffect):after {
transform: rotateY( 180deg ); transform: rotateY( 180deg );
} }
/*** colors from http://clrs.cc/ ***/
.navy{background-color:#001f3f!important;color:white}.blue{background-color:#0074d9!important;color:white}.aqua{background-color:#7fdbff!important;color:black}.teal{background-color:#39cccc!important;color:white}.olive{background-color:#3d9970!important;color:white}.green{background-color:#2ecc40!important;color:white}.aquamarine{background-color:#26a69a!important;color:white}.lime{background-color:#01ff70!important;color:black}.yellow{background-color:#ffdc00!important;color:black}.orange{background-color:#ff851b!important;color:white}.red{background-color:#cc1f00!important;color:white}.fuchsia{background-color:#f012be!important;color:white}.pink{background-color:#ee6e73!important;color:white}.purple{background-color:#b10dc9!important;color:white}.maroon{background-color:#85144b!important;color:white}.white{background-color:#fff!important;color:black;-webkit-box-shadow:inset 0px 0px 0px 1px #ddd;-moz-box-shadow:inset 0px 0px 0px 1px #ddd;box-shadow:inset 0px 0px 0px 1px #ddd}.gray{background-color:#aaa!important;color:white}.silver{background-color:#f1f1f1!important;color:black}.black{background-color:#000!important;color:white}.glass{background:rgba(255,255,255,0.5)!important;color:black}
/**** tags ****/ /**** tags ****/
.tags > span { .tags > span {
padding: 4px 9px; padding: 4px 9px;
@@ -350,10 +346,13 @@ a:not(.btn):not(.noeffect):after {
background-color: #26a69a; background-color: #26a69a;
border-radius: 5px; border-radius: 5px;
font-size:12px; font-size:12px;
margin: 5px 5px 5px 0 !important; margin: 2px 5px 2px 0;
line-height: 32px; display: inline-block;
} }
.tags.dismissible > span:hover {opacity: 0.5} .tags.dismissible > span:hover {opacity: 0.5}
.tags.dismissible > span:not(.off):after {content:" ✕"} .tags.dismissible > span:not(.off):after {content:" ✕"}
.tags > span.off {background-color: #ccc} .tags > span.off {background-color: #ccc}
.tags.dismissible > span.off:hover {background-color:#26a69a} .tags.dismissible > span.off:hover {background-color:#26a69a}
/*** colors from http://clrs.cc/ ***/
.navy{background-color:#001f3f;color:white}.blue{background-color:#0074d9;color:white}.aqua{background-color:#7fdbff;color:#111111}.teal{background-color:#39cccc;color:white}.olive{background-color:#3d9970;color:white}.green{background-color:#2ecc40;color:white}.aquamarine{background-color:#26a69a;color:white}.lime{background-color:#01ff70;color:#111111}.yellow{background-color:#ffdc00;color:#111111}.orange{background-color:#ff851b;color:white}.red{background-color:#cc1f00;color:white}.fuchsia{background-color:#f012be;color:white}.pink{background-color:#ee6e73;color:white}.purple{background-color:#b10dc9;color:white}.maroon{background-color:#85144b;color:white}.white{background-color:#fff;color:#111111;-webkit-box-shadow:inset 0px 0px 0px 1px #ddd;-moz-box-shadow:inset 0px 0px 0px 1px #ddd;box-shadow:inset 0px 0px 0px 1px #ddd}.gray{background-color:#aaa;color:white}.silver{background-color:#f1f1f1;color:#111111}.black{background-color:#111111;color:white}.glass{background:rgba(255,255,255,0.5);color:#111111}
@@ -17,7 +17,7 @@
<tbody> <tbody>
<tr> <tr>
<td> <td>
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a> <a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a>
</td> </td>
<td> <td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a> <a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a>
@@ -28,7 +28,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a> <a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a>
</td> </td>
<td> <td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_osx.zip">For Mac</a> <a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_osx.zip">For Mac</a>
@@ -37,7 +37,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a> <a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a>
</td> </td>
<td> <td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_src.zip">Source Code</a> <a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_src.zip">Source Code</a>
@@ -48,7 +48,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a class="btn btn180 rounded red" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a> <a class="btn btn180 rounded green" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a>
</td> </td>
<td> <td>
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a> <a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a>
@@ -69,9 +69,9 @@
<h3>Instructions</h3> <h3>Instructions</h3>
<p>After download, unzip it and click on web2py.exe (windows) or web2py.app (osx). <p>After download, unzip it and click on web2py.exe (windows) or web2py.app (osx).
To run from source, type:</p> To run from source, type:</p>
{{=CODE("python2.7 web2py.py",language=None,counter='>',_class='boxCode')}} {{=CODE("python2.7 web2py.py", language=None, counter='>', _class='boxCode')}}
<p>or for more info type:</p> <p>or for more info type:</p>
{{=CODE("python2.7 web2py.py -h",language=None,counter='>',_class='boxCode')}} {{=CODE("python2.7 web2py.py -h", language=None, counter='>', _class='boxCode')}}
<h3>Caveats</h3> <h3>Caveats</h3>
@@ -84,7 +84,7 @@
<p>Applications built with web2py can be released under any license the author wishes as long they do not contain web2py code. They can link unmodified web2py libraries and they can be distributed with official web2py binaries. In particular web2py applications can be distributed in closed source. The admin interface provides a button to byte-code compile.</p> <p>Applications built with web2py can be released under any license the author wishes as long they do not contain web2py code. They can link unmodified web2py libraries and they can be distributed with official web2py binaries. In particular web2py applications can be distributed in closed source. The admin interface provides a button to byte-code compile.</p>
<p>It is fine to distribute web2py (source or compiled) with your applications as long as you make it clear in the license where your application ends and web2py starts.</p> <p>It is fine to distribute web2py (source or compiled) with your applications as long as you make it clear in the license where your application ends and web2py starts.</p>
<p>web2py is copyrighted by Massimo Di Pierro. The web2py trademark is owned by Massimo Di Pierro.</p> <p>web2py is copyrighted by Massimo Di Pierro. The web2py trademark is owned by Massimo Di Pierro.</p>
<a class="btn btn-small" href="{{=URL('license')}}">read more</a> <a class="btn btn-small rounded" href="{{=URL('license')}}">read more</a>
<h3>Artwork</h3> <h3>Artwork</h3>
<center> <center>
@@ -3,22 +3,22 @@
<div class="container"> <div class="container">
<div class="twothirds"> <div class="twothirds">
<div class="padded"> <div class="padded">
<h3>web2py<sup>TM</sup> Web Framework</h3> <h3><img src="{{=URL('static/images', 'web2py_logo.png')}}"> Web Framework</h3>
<p>Free open source full-stack framework for rapid development of fast, scalable, <a href="http://www.web2py.com/book/default/chapter/01#Security" target="_blank">secure</a> and portable database-driven web-based applications. Written and programmable in <a href="http://www.python.org" target="_blank">Python</a>.</p> <p>Free open source full-stack framework for rapid development of fast, scalable, <a href="http://www.web2py.com/book/default/chapter/01#Security" target="_blank">secure</a> and portable database-driven web-based applications. Written and programmable in <a href="http://www.python.org" target="_blank">Python</a>.</p>
<table width="100%"> <table width="100%">
<tr> <tr>
<td> <td>
<a href="http://web2py.com/book"> <a class="noeffect" href="http://web2py.com/book">
<img src="{{=URL('static','images/book-5th.png')}}" /> <img src="{{=URL('static','images/book-5th.png')}}" />
</a> </a>
</td> </td>
<td> <td>
<a href="https://vimeo.com/album/3016728"> <a class="noeffect" href="https://vimeo.com/album/3016728">
<img src="{{=URL('static','images/videos.png')}}" /> <img src="{{=URL('static','images/videos.png')}}" />
</a> </a>
</td> </td>
<td> <td>
<a href="http://link.packtpub.com/SUlnrN"> <a class="noeffect" href="http://link.packtpub.com/SUlnrN">
<img src="{{=URL('static','images/book-recipes.png')}}" /> <img src="{{=URL('static','images/book-recipes.png')}}" />
</a> </a>
</td> </td>
@@ -29,8 +29,8 @@
</div> </div>
<div class="third"> <div class="third">
<div class="padded center"> <div class="padded center">
<a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23"> <a class="noeffect" href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23">
<img src="{{=URL('static','images/infoworld2012.jpeg')}}"> <img class="spaced-vertical" src="{{=URL('static','images/infoworld2012.jpeg')}}">
</a> </a>
<a class="btn rounded red fill" href="{{=URL('download')}}"> <a class="btn rounded red fill" href="{{=URL('download')}}">
Download Now Download Now
@@ -32,7 +32,6 @@
<li><a target="_blank" href="http://www.albendas.com">Albendas</a> (Spain)</li> <li><a target="_blank" href="http://www.albendas.com">Albendas</a> (Spain)</li>
<li><a target="_blank" href="http://www.appliedobjects.com">Applied Objects</a> (New Zealand)</li> <li><a target="_blank" href="http://www.appliedobjects.com">Applied Objects</a> (New Zealand)</li>
<li><a target="_blank" href="http://www.sistemasagiles.com.ar/">Sistemas Ágiles</a> ("Agile Systems") (Argentina)</li> <li><a target="_blank" href="http://www.sistemasagiles.com.ar/">Sistemas Ágiles</a> ("Agile Systems") (Argentina)</li>
<li><a target="_blank" href="http://www.definescope.com/en/services/consulting/">DefineScope</a> (Portugal)</li>
<li><a target="_blank" href="http://www.tasko.it/">Tasko</a> (Italy)</li> <li><a target="_blank" href="http://www.tasko.it/">Tasko</a> (Italy)</li>
<li><a target="_blank" href="http://www.geekondemand.it/"> GeekOnDemand</a> (Italy)</li> <li><a target="_blank" href="http://www.geekondemand.it/"> GeekOnDemand</a> (Italy)</li>
<li><a target="_blank" href="http://stifix.com"> Stifix</a> (Indonesia)</li> <li><a target="_blank" href="http://stifix.com"> Stifix</a> (Indonesia)</li>
@@ -82,6 +82,7 @@
</li><li>Keith Yang (openid) </li><li>Keith Yang (openid)
</li><li><a href="http://dev.s-cubism.com/web2py_plugins">Kenji Hosoda</a> (plugins) </li><li><a href="http://dev.s-cubism.com/web2py_plugins">Kenji Hosoda</a> (plugins)
</li><li>Kyle Smith (javascript) </li><li>Kyle Smith (javascript)
</li><li><a href="https://github.com/leonelcamara">Leonel Câmara</a>
</li><li><a href="http://blog.donews.com/limodou/">Limodou</a> (winservice) </li><li><a href="http://blog.donews.com/limodou/">Limodou</a> (winservice)
</li><li><a href="https://github.com/lucasdavila">Lucas D'Ávila</a> </li><li><a href="https://github.com/lucasdavila">Lucas D'Ávila</a>
</li><li>Marc Abramowitz (tests and travis continuous integration) </li><li>Marc Abramowitz (tests and travis continuous integration)
+6 -11
View File
@@ -1,22 +1,17 @@
<!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/calendar.css')}}" rel="stylesheet" type="text/css"/> <link href="{{=URL('static','css/calendar.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/web2py.css')}}" rel="stylesheet" type="text/css"/> <link href="{{=URL('static','css/web2py.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/examples.css')}}" rel="stylesheet" type="text/css"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<style> <link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
th, td {color: black!important} <link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
tbody tr:hover {background-color:transparent}
tbody tr {border-bottom: none}
p {text-align: left}
p, li { line-height: 1.6em}
pre {background-color: black!important;border-radius:5px; color:white; padding:10px}
a.btn.btn180 {padding:20px; font-size:1.2em; width:200px!important}
</style>
{{ {{
left_sidebar_enabled = globals().get('left_sidebar_enabled', False) left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
right_sidebar_enabled = globals().get('right_sidebar_enabled', False) right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
@@ -29,7 +24,7 @@
<header class="black padded"> <header class="black padded">
<div class="container middle max900"> <div class="container middle max900">
<div class="fill middle"> <div class="fill middle">
<label class="ham padded fa fa-bars" for="menu"></label> <label class="ham" for="menu"><i class="fa fa-bars padded"></i></label>
<div class="burger accordion"> <div class="burger accordion">
<input type="checkbox" id="menu"/> <input type="checkbox" id="menu"/>
{{=MENU(response.menu,_class='menu')}} {{=MENU(response.menu,_class='menu')}}
+7 -6
View File
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations # this file is released under public domain and you can use without limitations
######################################################################### # -------------------------------------------------------------------------
## This is a sample controller # This is a sample controller
## - index is the default action of any application # - index is the default action of any application
## - user is required for authentication and authorization # - user is required for authentication and authorization
## - download is for downloading files uploaded in the db (does streaming) # - download is for downloading files uploaded in the db (does streaming)
######################################################################### # -------------------------------------------------------------------------
def index(): def index():
""" """
+86 -54
View File
@@ -1,60 +1,84 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
######################################################################### # -------------------------------------------------------------------------
## This scaffolding model makes your app work on Google App Engine too # This scaffolding model makes your app work on Google App Engine too
## File is released under public domain and you can use without limitations # File is released under public domain and you can use without limitations
######################################################################### # -------------------------------------------------------------------------
if request.global_settings.web2py_version < "2.14.1": if request.global_settings.web2py_version < "2.14.1":
raise HTTP(500, "Requires web2py 2.13.3 or newer") raise HTTP(500, "Requires web2py 2.13.3 or newer")
## if SSL/HTTPS is properly configured and you want all HTTP requests to # -------------------------------------------------------------------------
## be redirected to HTTPS, uncomment the line below: # if SSL/HTTPS is properly configured and you want all HTTP requests to
# be redirected to HTTPS, uncomment the line below:
# -------------------------------------------------------------------------
# request.requires_https() # request.requires_https()
## app configuration made easy. Look inside private/appconfig.ini # -------------------------------------------------------------------------
# app configuration made easy. Look inside private/appconfig.ini
# -------------------------------------------------------------------------
from gluon.contrib.appconfig import AppConfig from gluon.contrib.appconfig import AppConfig
## once in production, remove reload=True to gain full speed
# -------------------------------------------------------------------------
# once in production, remove reload=True to gain full speed
# -------------------------------------------------------------------------
myconf = AppConfig(reload=True) myconf = AppConfig(reload=True)
if not request.env.web2py_runtime_gae: if not request.env.web2py_runtime_gae:
## if NOT running on Google App Engine use SQLite or other DB # ---------------------------------------------------------------------
db = DAL(myconf.get('db.uri'), # if NOT running on Google App Engine use SQLite or other DB
pool_size = myconf.get('db.pool_size'), # ---------------------------------------------------------------------
migrate_enabled = myconf.get('db.migrate'), db = DAL(myconf.get('db.uri'),
check_reserved = ['all']) pool_size=myconf.get('db.pool_size'),
migrate_enabled=myconf.get('db.migrate'),
check_reserved=['all'])
else: else:
## connect to Google BigTable (optional 'google:datastore://namespace') # ---------------------------------------------------------------------
# connect to Google BigTable (optional 'google:datastore://namespace')
# ---------------------------------------------------------------------
db = DAL('google:datastore+ndb') db = DAL('google:datastore+ndb')
## store sessions and tickets there # ---------------------------------------------------------------------
# store sessions and tickets there
# ---------------------------------------------------------------------
session.connect(request, response, db=db) session.connect(request, response, db=db)
## or store session in Memcache, Redis, etc. # ---------------------------------------------------------------------
## from gluon.contrib.memdb import MEMDB # or store session in Memcache, Redis, etc.
## from google.appengine.api.memcache import Client # from gluon.contrib.memdb import MEMDB
## session.connect(request, response, db = MEMDB(Client())) # from google.appengine.api.memcache import Client
# session.connect(request, response, db = MEMDB(Client()))
# ---------------------------------------------------------------------
## by default give a view/generic.extension to all actions from localhost # -------------------------------------------------------------------------
## none otherwise. a pattern can be 'controller/function.extension' # by default give a view/generic.extension to all actions from localhost
# none otherwise. a pattern can be 'controller/function.extension'
# -------------------------------------------------------------------------
response.generic_patterns = ['*'] if request.is_local else [] response.generic_patterns = ['*'] if request.is_local else []
## choose a style for forms # -------------------------------------------------------------------------
# choose a style for forms
# -------------------------------------------------------------------------
response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
response.form_label_separator = myconf.get('forms.separator') or '' response.form_label_separator = myconf.get('forms.separator') or ''
# -------------------------------------------------------------------------
## (optional) optimize handling of static files # (optional) optimize handling of static files
# -------------------------------------------------------------------------
# response.optimize_css = 'concat,minify,inline' # response.optimize_css = 'concat,minify,inline'
# response.optimize_js = 'concat,minify,inline' # response.optimize_js = 'concat,minify,inline'
## (optional) static assets folder versioning
# -------------------------------------------------------------------------
# (optional) static assets folder versioning
# -------------------------------------------------------------------------
# response.static_version = '0.0.0' # response.static_version = '0.0.0'
#########################################################################
## Here is sample code if you need for # -------------------------------------------------------------------------
## - email capabilities # Here is sample code if you need for
## - authentication (registration, login, logout, ... ) # - email capabilities
## - authorization (role based authorization) # - authentication (registration, login, logout, ... )
## - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss) # - authorization (role based authorization)
## - old style crud actions # - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss)
## (more options discussed in gluon/tools.py) # - old style crud actions
######################################################################### # (more options discussed in gluon/tools.py)
# -------------------------------------------------------------------------
from gluon.tools import Auth, Service, PluginManager from gluon.tools import Auth, Service, PluginManager
@@ -63,10 +87,14 @@ auth = Auth(db, host_names=myconf.get('host.names'))
service = Service() service = Service()
plugins = PluginManager() plugins = PluginManager()
## create all tables needed by auth if not custom tables # -------------------------------------------------------------------------
# create all tables needed by auth if not custom tables
# -------------------------------------------------------------------------
auth.define_tables(username=False, signature=False) auth.define_tables(username=False, signature=False)
## configure email # -------------------------------------------------------------------------
# configure email
# -------------------------------------------------------------------------
mail = auth.settings.mailer mail = auth.settings.mailer
mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server') mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server')
mail.settings.sender = myconf.get('smtp.sender') mail.settings.sender = myconf.get('smtp.sender')
@@ -74,27 +102,31 @@ mail.settings.login = myconf.get('smtp.login')
mail.settings.tls = myconf.get('smtp.tls') or False mail.settings.tls = myconf.get('smtp.tls') or False
mail.settings.ssl = myconf.get('smtp.ssl') or False mail.settings.ssl = myconf.get('smtp.ssl') or False
## configure auth policy # -------------------------------------------------------------------------
# configure auth policy
# -------------------------------------------------------------------------
auth.settings.registration_requires_verification = False auth.settings.registration_requires_verification = False
auth.settings.registration_requires_approval = False auth.settings.registration_requires_approval = False
auth.settings.reset_password_requires_verification = True auth.settings.reset_password_requires_verification = True
######################################################################### # -------------------------------------------------------------------------
## Define your tables below (or better in another model file) for example # Define your tables below (or better in another model file) for example
## #
## >>> db.define_table('mytable',Field('myfield','string')) # >>> db.define_table('mytable', Field('myfield', 'string'))
## #
## Fields can be 'string','text','password','integer','double','boolean' # Fields can be 'string','text','password','integer','double','boolean'
## 'date','time','datetime','blob','upload', 'reference TABLENAME' # 'date','time','datetime','blob','upload', 'reference TABLENAME'
## There is an implicit 'id integer autoincrement' field # There is an implicit 'id integer autoincrement' field
## Consult manual for more options, validators, etc. # Consult manual for more options, validators, etc.
## #
## More API examples for controllers: # More API examples for controllers:
## #
## >>> db.mytable.insert(myfield='value') # >>> db.mytable.insert(myfield='value')
## >>> rows=db(db.mytable.myfield=='value').select(db.mytable.ALL) # >>> rows = db(db.mytable.myfield == 'value').select(db.mytable.ALL)
## >>> for row in rows: print row.id, row.myfield # >>> for row in rows: print row.id, row.myfield
######################################################################### # -------------------------------------------------------------------------
## after defining tables, uncomment below to enable auditing # -------------------------------------------------------------------------
# after defining tables, uncomment below to enable auditing
# -------------------------------------------------------------------------
# auth.enable_record_versioning(db) # auth.enable_record_versioning(db)
+121 -108
View File
@@ -1,28 +1,32 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations # this file is released under public domain and you can use without limitations
######################################################################### # ----------------------------------------------------------------------------------------------------------------------
## Customize your APP title, subtitle and menus here # Customize your APP title, subtitle and menus here
######################################################################### # ----------------------------------------------------------------------------------------------------------------------
response.logo = A(B('web',SPAN(2),'py'),XML('&trade;&nbsp;'), response.logo = A(B('web', SPAN(2), 'py'), XML('&trade;&nbsp;'),
_class="navbar-brand",_href="http://www.web2py.com/", _class="navbar-brand", _href="http://www.web2py.com/",
_id="web2py-logo") _id="web2py-logo")
response.title = request.application.replace('_',' ').title() response.title = request.application.replace('_', ' ').title()
response.subtitle = '' response.subtitle = ''
## read more at http://dev.w3.org/html5/markup/meta.name.html # ----------------------------------------------------------------------------------------------------------------------
# read more at http://dev.w3.org/html5/markup/meta.name.html
# ----------------------------------------------------------------------------------------------------------------------
response.meta.author = myconf.get('app.author') response.meta.author = myconf.get('app.author')
response.meta.description = myconf.get('app.description') response.meta.description = myconf.get('app.description')
response.meta.keywords = myconf.get('app.keywords') response.meta.keywords = myconf.get('app.keywords')
response.meta.generator = myconf.get('app.generator') response.meta.generator = myconf.get('app.generator')
## your http://google.com/analytics id # ----------------------------------------------------------------------------------------------------------------------
# your http://google.com/analytics id
# ----------------------------------------------------------------------------------------------------------------------
response.google_analytics_id = None response.google_analytics_id = None
######################################################################### # ----------------------------------------------------------------------------------------------------------------------
## this is the main application menu add/remove items as required # this is the main application menu add/remove items as required
######################################################################### # ----------------------------------------------------------------------------------------------------------------------
response.menu = [ response.menu = [
(T('Home'), False, URL('default', 'index'), []) (T('Home'), False, URL('default', 'index'), [])
@@ -30,109 +34,118 @@ response.menu = [
DEVELOPMENT_MENU = True DEVELOPMENT_MENU = True
#########################################################################
## provide shortcuts for development. remove in production # ----------------------------------------------------------------------------------------------------------------------
######################################################################### # provide shortcuts for development. remove in production
# ----------------------------------------------------------------------------------------------------------------------
def _(): def _():
# ------------------------------------------------------------------------------------------------------------------
# shortcuts # shortcuts
# ------------------------------------------------------------------------------------------------------------------
app = request.application app = request.application
ctr = request.controller ctr = request.controller
# ------------------------------------------------------------------------------------------------------------------
# useful links to internal and external resources # useful links to internal and external resources
# ------------------------------------------------------------------------------------------------------------------
response.menu += [ response.menu += [
(T('My Sites'), False, URL('admin', 'default', 'site')), (T('My Sites'), False, URL('admin', 'default', 'site')),
(T('This App'), False, '#', [ (T('This App'), False, '#', [
(T('Design'), False, URL('admin', 'default', 'design/%s' % app)), (T('Design'), False, URL('admin', 'default', 'design/%s' % app)),
LI(_class="divider"), LI(_class="divider"),
(T('Controller'), False, (T('Controller'), False,
URL( URL(
'admin', 'default', 'edit/%s/controllers/%s.py' % (app, ctr))), 'admin', 'default', 'edit/%s/controllers/%s.py' % (app, ctr))),
(T('View'), False, (T('View'), False,
URL( URL(
'admin', 'default', 'edit/%s/views/%s' % (app, response.view))), 'admin', 'default', 'edit/%s/views/%s' % (app, response.view))),
(T('DB Model'), False, (T('DB Model'), False,
URL( URL(
'admin', 'default', 'edit/%s/models/db.py' % app)), 'admin', 'default', 'edit/%s/models/db.py' % app)),
(T('Menu Model'), False, (T('Menu Model'), False,
URL( URL(
'admin', 'default', 'edit/%s/models/menu.py' % app)), 'admin', 'default', 'edit/%s/models/menu.py' % app)),
(T('Config.ini'), False, (T('Config.ini'), False,
URL( URL(
'admin', 'default', 'edit/%s/private/appconfig.ini' % app)), 'admin', 'default', 'edit/%s/private/appconfig.ini' % app)),
(T('Layout'), False, (T('Layout'), False,
URL( URL(
'admin', 'default', 'edit/%s/views/layout.html' % app)), 'admin', 'default', 'edit/%s/views/layout.html' % app)),
(T('Stylesheet'), False, (T('Stylesheet'), False,
URL( URL(
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % app)), 'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % app)),
(T('Database'), False, URL(app, 'appadmin', 'index')), (T('Database'), False, URL(app, 'appadmin', 'index')),
(T('Errors'), False, URL( (T('Errors'), False, URL(
'admin', 'default', 'errors/' + app)), 'admin', 'default', 'errors/' + app)),
(T('About'), False, URL( (T('About'), False, URL(
'admin', 'default', 'about/' + app)), 'admin', 'default', 'about/' + app)),
]), ]),
('web2py.com', False, '#', [ ('web2py.com', False, '#', [
(T('Download'), False, (T('Download'), False,
'http://www.web2py.com/examples/default/download'), 'http://www.web2py.com/examples/default/download'),
(T('Support'), False, (T('Support'), False,
'http://www.web2py.com/examples/default/support'), 'http://www.web2py.com/examples/default/support'),
(T('Demo'), False, 'http://web2py.com/demo_admin'), (T('Demo'), False, 'http://web2py.com/demo_admin'),
(T('Quick Examples'), False, (T('Quick Examples'), False,
'http://web2py.com/examples/default/examples'), 'http://web2py.com/examples/default/examples'),
(T('FAQ'), False, 'http://web2py.com/AlterEgo'), (T('FAQ'), False, 'http://web2py.com/AlterEgo'),
(T('Videos'), False, (T('Videos'), False,
'http://www.web2py.com/examples/default/videos/'), 'http://www.web2py.com/examples/default/videos/'),
(T('Free Applications'), (T('Free Applications'),
False, 'http://web2py.com/appliances'), False, 'http://web2py.com/appliances'),
(T('Plugins'), False, 'http://web2py.com/plugins'), (T('Plugins'), False, 'http://web2py.com/plugins'),
(T('Recipes'), False, 'http://web2pyslices.com/'), (T('Recipes'), False, 'http://web2pyslices.com/'),
]), ]),
(T('Documentation'), False, '#', [ (T('Documentation'), False, '#', [
(T('Online book'), False, 'http://www.web2py.com/book'), (T('Online book'), False, 'http://www.web2py.com/book'),
LI(_class="divider"), LI(_class="divider"),
(T('Preface'), False, (T('Preface'), False,
'http://www.web2py.com/book/default/chapter/00'), 'http://www.web2py.com/book/default/chapter/00'),
(T('Introduction'), False, (T('Introduction'), False,
'http://www.web2py.com/book/default/chapter/01'), 'http://www.web2py.com/book/default/chapter/01'),
(T('Python'), False, (T('Python'), False,
'http://www.web2py.com/book/default/chapter/02'), 'http://www.web2py.com/book/default/chapter/02'),
(T('Overview'), False, (T('Overview'), False,
'http://www.web2py.com/book/default/chapter/03'), 'http://www.web2py.com/book/default/chapter/03'),
(T('The Core'), False, (T('The Core'), False,
'http://www.web2py.com/book/default/chapter/04'), 'http://www.web2py.com/book/default/chapter/04'),
(T('The Views'), False, (T('The Views'), False,
'http://www.web2py.com/book/default/chapter/05'), 'http://www.web2py.com/book/default/chapter/05'),
(T('Database'), False, (T('Database'), False,
'http://www.web2py.com/book/default/chapter/06'), 'http://www.web2py.com/book/default/chapter/06'),
(T('Forms and Validators'), False, (T('Forms and Validators'), False,
'http://www.web2py.com/book/default/chapter/07'), 'http://www.web2py.com/book/default/chapter/07'),
(T('Email and SMS'), False, (T('Email and SMS'), False,
'http://www.web2py.com/book/default/chapter/08'), 'http://www.web2py.com/book/default/chapter/08'),
(T('Access Control'), False, (T('Access Control'), False,
'http://www.web2py.com/book/default/chapter/09'), 'http://www.web2py.com/book/default/chapter/09'),
(T('Services'), False, (T('Services'), False,
'http://www.web2py.com/book/default/chapter/10'), 'http://www.web2py.com/book/default/chapter/10'),
(T('Ajax Recipes'), False, (T('Ajax Recipes'), False,
'http://www.web2py.com/book/default/chapter/11'), 'http://www.web2py.com/book/default/chapter/11'),
(T('Components and Plugins'), False, (T('Components and Plugins'), False,
'http://www.web2py.com/book/default/chapter/12'), 'http://www.web2py.com/book/default/chapter/12'),
(T('Deployment Recipes'), False, (T('Deployment Recipes'), False,
'http://www.web2py.com/book/default/chapter/13'), 'http://www.web2py.com/book/default/chapter/13'),
(T('Other Recipes'), False, (T('Other Recipes'), False,
'http://www.web2py.com/book/default/chapter/14'), 'http://www.web2py.com/book/default/chapter/14'),
(T('Helping web2py'), False, (T('Helping web2py'), False,
'http://www.web2py.com/book/default/chapter/15'), 'http://www.web2py.com/book/default/chapter/15'),
(T("Buy web2py's book"), False, (T("Buy web2py's book"), False,
'http://stores.lulu.com/web2py'), 'http://stores.lulu.com/web2py'),
]), ]),
(T('Community'), False, None, [ (T('Community'), False, None, [
(T('Groups'), False, (T('Groups'), False,
'http://www.web2py.com/examples/default/usergroups'), 'http://www.web2py.com/examples/default/usergroups'),
(T('Twitter'), False, 'http://twitter.com/web2py'), (T('Twitter'), False, 'http://twitter.com/web2py'),
(T('Live Chat'), False, (T('Live Chat'), False,
'http://webchat.freenode.net/?channels=web2py'), 'http://webchat.freenode.net/?channels=web2py'),
]), ]),
] ]
if DEVELOPMENT_MENU: _()
if "auth" in locals(): auth.wikimenu()
if DEVELOPMENT_MENU:
_()
if "auth" in locals():
auth.wikimenu()
+15 -12
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------------------------------------------------
# This is an app-specific example router # This is an app-specific example router
# #
# This simple router is used for setting languages from app/languages directory # This simple router is used for setting languages from app/languages directory
@@ -8,31 +9,33 @@
# a default_language # a default_language
# #
# See <web2py-root-dir>/examples/routes.parametric.example.py for parameter's detail # See <web2py-root-dir>/examples/routes.parametric.example.py for parameter's detail
#------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------------------
# To enable this route file you must do the steps: # To enable this route file you must do the steps:
#
# 1. rename <web2py-root-dir>/examples/routes.parametric.example.py to routes.py # 1. rename <web2py-root-dir>/examples/routes.parametric.example.py to routes.py
# 2. rename this APP/routes.example.py to APP/routes.py # 2. rename this APP/routes.example.py to APP/routes.py (where APP - is your application directory)
# (where APP - is your application directory) # 3. restart web2py (or reload routes in web2py admin interface)
# 3. restart web2py (or reload routes in web2py admin interfase)
# #
# YOU CAN COPY THIS FILE TO ANY APPLICATION'S ROOT DIRECTORY WITHOUT CHANGES! # YOU CAN COPY THIS FILE TO ANY APPLICATION'S ROOT DIRECTORY WITHOUT CHANGES!
# ----------------------------------------------------------------------------------------------------------------------
from fileutils import abspath from fileutils import abspath
from languages import read_possible_languages from languages import read_possible_languages
possible_languages = read_possible_languages(abspath('applications', app)) possible_languages = read_possible_languages(abspath('applications', app))
#NOTE! app - is an application based router's parameter with name of an # ----------------------------------------------------------------------------------------------------------------------
# application. E.g.'welcome' # NOTE! app - is an application based router's parameter with name of an application. E.g.'welcome'
# ----------------------------------------------------------------------------------------------------------------------
routers = { routers = {
app: dict( app: dict(
default_language = possible_languages['default'][0], default_language=possible_languages['default'][0],
languages = [lang for lang in possible_languages languages=[lang for lang in possible_languages if lang != 'default']
if lang != 'default']
) )
} }
#NOTE! To change language in your application using these rules add this line # ----------------------------------------------------------------------------------------------------------------------
#in one of your models files: # NOTE! To change language in your application using these rules add this line in one of your models files:
# ----------------------------------------------------------------------------------------------------------------------
# if request.uri_language: T.force(request.uri_language) # if request.uri_language: T.force(request.uri_language)
+37 -50
View File
@@ -44,9 +44,9 @@ except ImportError:
have_settings = False have_settings = False
try: try:
import cPickle as pickle import cPickle as pickle
except: except:
import pickle import pickle
try: try:
import psutil import psutil
@@ -54,6 +54,7 @@ try:
except ImportError: except ImportError:
HAVE_PSUTIL = False HAVE_PSUTIL = False
def remove_oldest_entries(storage, percentage=90): def remove_oldest_entries(storage, percentage=90):
# compute current memory usage (%) # compute current memory usage (%)
old_mem = psutil.virtual_memory().percent old_mem = psutil.virtual_memory().percent
@@ -66,7 +67,8 @@ def remove_oldest_entries(storage, percentage=90):
# comute used memory again # comute used memory again
new_mem = psutil.virtual_memory().percent new_mem = psutil.virtual_memory().percent
# if the used memory did not decrease stop # if the used memory did not decrease stop
if new_mem >= old_mem: break if new_mem >= old_mem:
break
# net new measurement for memory usage and loop # net new measurement for memory usage and loop
old_mem = new_mem old_mem = new_mem
@@ -78,6 +80,7 @@ __all__ = ['Cache', 'lazy_cache']
DEFAULT_TIME_EXPIRE = 300 DEFAULT_TIME_EXPIRE = 300
class CacheAbstract(object): class CacheAbstract(object):
""" """
Abstract class for cache implementations. Abstract class for cache implementations.
@@ -99,7 +102,7 @@ class CacheAbstract(object):
""" """
cache_stats_name = 'web2py_cache_statistics' cache_stats_name = 'web2py_cache_statistics'
max_ram_utilization = None # percent max_ram_utilization = None # percent
def __init__(self, request=None): def __init__(self, request=None):
"""Initializes the object """Initializes the object
@@ -182,13 +185,14 @@ class CacheInRam(CacheAbstract):
self.request = request self.request = request
self.storage = OrderedDict() if HAVE_PSUTIL else {} self.storage = OrderedDict() if HAVE_PSUTIL else {}
self.app = request.application if request else '' self.app = request.application if request else ''
def initialize(self): def initialize(self):
if self.initialized: if self.initialized:
return return
else: else:
self.initialized = True self.initialized = True
self.locker.acquire() self.locker.acquire()
if not self.app in self.meta_storage: if self.app not in self.meta_storage:
self.storage = self.meta_storage[self.app] = \ self.storage = self.meta_storage[self.app] = \
OrderedDict() if HAVE_PSUTIL else {} OrderedDict() if HAVE_PSUTIL else {}
self.stats[self.app] = {'hit_total': 0, 'misses': 0} self.stats[self.app] = {'hit_total': 0, 'misses': 0}
@@ -205,7 +209,7 @@ class CacheInRam(CacheAbstract):
else: else:
self._clear(storage, regex) self._clear(storage, regex)
if not self.app in self.stats: if self.app not in self.stats:
self.stats[self.app] = {'hit_total': 0, 'misses': 0} self.stats[self.app] = {'hit_total': 0, 'misses': 0}
self.locker.release() self.locker.release()
@@ -251,8 +255,8 @@ class CacheInRam(CacheAbstract):
self.locker.acquire() self.locker.acquire()
self.storage[key] = (now, value) self.storage[key] = (now, value)
self.stats[self.app]['misses'] += 1 self.stats[self.app]['misses'] += 1
if HAVE_PSUTIL and self.max_ram_utilization!=None and random.random()<0.10: if HAVE_PSUTIL and self.max_ram_utilization is not None and random.random() < 0.10:
remove_oldest_entries(self.storage, percentage = self.max_ram_utilization) remove_oldest_entries(self.storage, percentage=self.max_ram_utilization)
self.locker.release() self.locker.release()
return value return value
@@ -292,14 +296,15 @@ class CacheOnDisk(CacheAbstract):
self.folder = folder self.folder = folder
self.key_filter_in = lambda key: key self.key_filter_in = lambda key: key
self.key_filter_out = lambda key: key self.key_filter_out = lambda key: key
self.file_lock_time_wait = file_lock_time_wait # How long we should wait before retrying to lock a file held by another process self.file_lock_time_wait = file_lock_time_wait
# How long we should wait before retrying to lock a file held by another process
# We still need a mutex for each file as portalocker only blocks other processes # We still need a mutex for each file as portalocker only blocks other processes
self.file_locks = defaultdict(thread.allocate_lock) self.file_locks = defaultdict(thread.allocate_lock)
# Make sure we use valid filenames. # Make sure we use valid filenames.
if sys.platform == "win32": if sys.platform == "win32":
import base64 import base64
def key_filter_in_windows(key): def key_filter_in_windows(key):
""" """
Windows doesn't allow \ / : * ? "< > | in filenames. Windows doesn't allow \ / : * ? "< > | in filenames.
@@ -316,7 +321,6 @@ class CacheOnDisk(CacheAbstract):
self.key_filter_in = key_filter_in_windows self.key_filter_in = key_filter_in_windows
self.key_filter_out = key_filter_out_windows self.key_filter_out = key_filter_out_windows
def wait_portalock(self, val_file): def wait_portalock(self, val_file):
""" """
Wait for the process file lock. Wait for the process file lock.
@@ -328,15 +332,12 @@ class CacheOnDisk(CacheAbstract):
except: except:
time.sleep(self.file_lock_time_wait) time.sleep(self.file_lock_time_wait)
def acquire(self, key): def acquire(self, key):
self.file_locks[key].acquire() self.file_locks[key].acquire()
def release(self, key): def release(self, key):
self.file_locks[key].release() self.file_locks[key].release()
def __setitem__(self, key, value): def __setitem__(self, key, value):
key = self.key_filter_in(key) key = self.key_filter_in(key)
val_file = recfile.open(key, mode='wb', path=self.folder) val_file = recfile.open(key, mode='wb', path=self.folder)
@@ -344,7 +345,6 @@ class CacheOnDisk(CacheAbstract):
pickle.dump(value, val_file, pickle.HIGHEST_PROTOCOL) pickle.dump(value, val_file, pickle.HIGHEST_PROTOCOL)
val_file.close() val_file.close()
def __getitem__(self, key): def __getitem__(self, key):
key = self.key_filter_in(key) key = self.key_filter_in(key)
try: try:
@@ -357,12 +357,10 @@ class CacheOnDisk(CacheAbstract):
val_file.close() val_file.close()
return value return value
def __contains__(self, key): def __contains__(self, key):
key = self.key_filter_in(key) key = self.key_filter_in(key)
return (key in self.file_locks) or recfile.exists(key, path=self.folder) return (key in self.file_locks) or recfile.exists(key, path=self.folder)
def __delitem__(self, key): def __delitem__(self, key):
key = self.key_filter_in(key) key = self.key_filter_in(key)
try: try:
@@ -370,13 +368,11 @@ class CacheOnDisk(CacheAbstract):
except IOError: except IOError:
raise KeyError raise KeyError
def __iter__(self): def __iter__(self):
for dirpath, dirnames, filenames in os.walk(self.folder): for dirpath, dirnames, filenames in os.walk(self.folder):
for filename in filenames: for filename in filenames:
yield self.key_filter_out(filename) yield self.key_filter_out(filename)
def safe_apply(self, key, function, default_value=None): def safe_apply(self, key, function, default_value=None):
""" """
Safely apply a function to the value of a key in storage and set Safely apply a function to the value of a key in storage and set
@@ -403,25 +399,21 @@ class CacheOnDisk(CacheAbstract):
val_file.close() val_file.close()
return new_value return new_value
def keys(self): def keys(self):
return list(self.__iter__()) return list(self.__iter__())
def get(self, key, default=None): def get(self, key, default=None):
try: try:
return self[key] return self[key]
except KeyError: except KeyError:
return default return default
def __init__(self, request=None, folder=None): def __init__(self, request=None, folder=None):
self.initialized = False self.initialized = False
self.request = request self.request = request
self.folder = folder self.folder = folder
self.storage = None self.storage = None
def initialize(self): def initialize(self):
if self.initialized: if self.initialized:
return return
@@ -440,7 +432,6 @@ class CacheOnDisk(CacheAbstract):
self.storage = CacheOnDisk.PersistentStorage(folder) self.storage = CacheOnDisk.PersistentStorage(folder)
def __call__(self, key, f, def __call__(self, key, f,
time_expire=DEFAULT_TIME_EXPIRE): time_expire=DEFAULT_TIME_EXPIRE):
self.initialize() self.initialize()
@@ -487,7 +478,6 @@ class CacheOnDisk(CacheAbstract):
self.storage.release(key) self.storage.release(key)
return value return value
def clear(self, regex=None): def clear(self, regex=None):
self.initialize() self.initialize()
storage = self.storage storage = self.storage
@@ -504,7 +494,6 @@ class CacheOnDisk(CacheAbstract):
pass pass
storage.release(key) storage.release(key)
def increment(self, key, value=1): def increment(self, key, value=1):
self.initialize() self.initialize()
self.storage.acquire(key) self.storage.acquire(key)
@@ -513,7 +502,6 @@ class CacheOnDisk(CacheAbstract):
return value return value
class CacheAction(object): class CacheAction(object):
def __init__(self, func, key, time_expire, cache, cache_model): def __init__(self, func, key, time_expire, cache, cache_model):
self.__name__ = func.__name__ self.__name__ = func.__name__
@@ -572,9 +560,9 @@ class Cache(object):
logger.warning('no cache.disk (AttributeError)') logger.warning('no cache.disk (AttributeError)')
def action(self, time_expire=DEFAULT_TIME_EXPIRE, cache_model=None, def action(self, time_expire=DEFAULT_TIME_EXPIRE, cache_model=None,
prefix=None, session=False, vars=True, lang=True, prefix=None, session=False, vars=True, lang=True,
user_agent=False, public=True, valid_statuses=None, user_agent=False, public=True, valid_statuses=None,
quick=None): quick=None):
"""Better fit for caching an action """Better fit for caching an action
Warning: Warning:
@@ -602,6 +590,7 @@ class Cache(object):
""" """
from gluon import current from gluon import current
from gluon.http import HTTP from gluon.http import HTTP
def wrap(func): def wrap(func):
def wrapped_f(): def wrapped_f():
if current.request.env.request_method != 'GET': if current.request.env.request_method != 'GET':
@@ -621,13 +610,14 @@ class Cache(object):
cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire) cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire)
if not session_ and public_: if not session_ and public_:
cache_control += ', public' cache_control += ', public'
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT') expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)
).strftime('%a, %d %b %Y %H:%M:%S GMT')
else: else:
cache_control += ', private' cache_control += ', private'
expires = 'Fri, 01 Jan 1990 00:00:00 GMT' expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
if cache_model: if cache_model:
#figure out the correct cache key # figure out the correct cache key
cache_key = [current.request.env.path_info, current.response.view] cache_key = [current.request.env.path_info, current.response.view]
if session_: if session_:
cache_key.append(current.response.session_id) cache_key.append(current.response.session_id)
@@ -644,28 +634,28 @@ class Cache(object):
if prefix: if prefix:
cache_key = prefix + cache_key cache_key = prefix + cache_key
try: try:
#action returns something # action returns something
rtn = cache_model(cache_key, lambda : func(), time_expire=time_expire) rtn = cache_model(cache_key, lambda: func(), time_expire=time_expire)
http, status = None, current.response.status http, status = None, current.response.status
except HTTP, e: except HTTP, e:
#action raises HTTP (can still be valid) # action raises HTTP (can still be valid)
rtn = cache_model(cache_key, lambda : e.body, time_expire=time_expire) rtn = cache_model(cache_key, lambda: e.body, time_expire=time_expire)
http, status = HTTP(e.status, rtn, **e.headers), e.status http, status = HTTP(e.status, rtn, **e.headers), e.status
else: else:
#action raised a generic exception # action raised a generic exception
http = None http = None
else: else:
#no server-cache side involved # no server-cache side involved
try: try:
#action returns something # action returns something
rtn = func() rtn = func()
http, status = None, current.response.status http, status = None, current.response.status
except HTTP, e: except HTTP, e:
#action raises HTTP (can still be valid) # action raises HTTP (can still be valid)
status = e.status status = e.status
http = HTTP(e.status, e.body, **e.headers) http = HTTP(e.status, e.body, **e.headers)
else: else:
#action raised a generic exception # action raised a generic exception
http = None http = None
send_headers = False send_headers = False
if http and isinstance(valid_statuses, list): if http and isinstance(valid_statuses, list):
@@ -675,15 +665,13 @@ class Cache(object):
if str(status)[0] in '123': if str(status)[0] in '123':
send_headers = True send_headers = True
if send_headers: if send_headers:
headers = { headers = {'Pragma': None,
'Pragma' : None, 'Expires': expires,
'Expires' : expires, 'Cache-Control': cache_control}
'Cache-Control' : cache_control
}
current.response.headers.update(headers) current.response.headers.update(headers)
if cache_model and not send_headers: if cache_model and not send_headers:
#we cached already the value, but the status is not valid # we cached already the value, but the status is not valid
#so we need to delete the cached value # so we need to delete the cached value
cache_model(cache_key, None) cache_model(cache_key, None)
if http: if http:
if send_headers: if send_headers:
@@ -740,8 +728,7 @@ class Cache(object):
allow replacing cache.ram with cache.with_prefix(cache.ram,'prefix') allow replacing cache.ram with cache.with_prefix(cache.ram,'prefix')
it will add prefix to all the cache keys used. it will add prefix to all the cache keys used.
""" """
return lambda key, f, time_expire=DEFAULT_TIME_EXPIRE, prefix=prefix:\ return lambda key, f, time_expire=DEFAULT_TIME_EXPIRE, prefix=prefix: cache_model(prefix + key, f, time_expire)
cache_model(prefix + key, f, time_expire)
def lazy_cache(key=None, time_expire=None, cache_model='ram'): def lazy_cache(key=None, time_expire=None, cache_model='ram'):
+288 -256
View File
@@ -7,10 +7,11 @@ import re
import urllib import urllib
from cgi import escape from cgi import escape
from string import maketrans from string import maketrans
try: try:
from ast import parse as ast_parse from ast import parse as ast_parse
import ast import ast
except ImportError: # python 2.5 except ImportError: # python 2.5
from compiler import parse from compiler import parse
import compiler.ast as ast import compiler.ast as ast
@@ -530,41 +531,47 @@ As shown in Ref.!`!`mdipierro`!`!:cite
``<ul/>``, ``<ol/>``, ``<code/>``, ``<table/>``, ``<blockquote/>``, ``<h1/>``, ..., ``<h6/>`` do not have ``<p>...</p>`` around them. ``<ul/>``, ``<ol/>``, ``<code/>``, ``<table/>``, ``<blockquote/>``, ``<h1/>``, ..., ``<h6/>`` do not have ``<p>...</p>`` around them.
""" """
html_colors=['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', html_colors = ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green',
'lime', 'maroon', 'navy', 'olive', 'purple', 'red', 'lime', 'maroon', 'navy', 'olive', 'purple', 'red',
'silver', 'teal', 'white', 'yellow'] 'silver', 'teal', 'white', 'yellow']
META = '\x06' META = '\x06'
LINK = '\x07' LINK = '\x07'
DISABLED_META = '\x08' DISABLED_META = '\x08'
LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" />' 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_URL = re.compile(r'@/(?P<a>\w*)/(?P<c>\w*)/(?P<f>\w*(\.\w+)?)(/(?P<args>[\w\.\-/]+))?')
regex_env2=re.compile(r'@\{(?P<a>[\w\-\.]+?)(\:(?P<b>.*?))?\}') regex_env2 = 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_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(
regex_strong=re.compile(r'\*\*(?P<t>[^\s*]+( +[^\s*]+)*)\*\*') '(' + META + '|' + DISABLED_META + r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',
regex_del=re.compile(r'~~(?P<t>[^\s*]+( +[^\s*]+)*)~~') re.S)
regex_em=re.compile(r"''(?P<t>([^\s']| |'(?!'))+)''") regex_strong = re.compile(r'\*\*(?P<t>[^\s*]+( +[^\s*]+)*)\*\*')
regex_num=re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$") regex_del = re.compile(r'~~(?P<t>[^\s*]+( +[^\s*]+)*)~~')
regex_list=re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$') regex_em = re.compile(r"''(?P<t>([^\s']| |'(?!'))+)''")
regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$') regex_num = re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$")
regex_tq=re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$') 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_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+)', re.M)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w|[\w\-.]+@[\w\-.]+)',re.M) regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w|[\w\-.]+@[\w\-.]+)', re.M)
regex_link=re.compile(r'('+LINK+r')|\[\[(?P<s>.+?)\]\]',re.S) regex_link = re.compile(r'(' + LINK + r')|\[\[(?P<s>.+?)\]\]', re.S)
regex_link_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$',re.S) regex_link_level2 = re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$', re.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|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',re.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|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',
re.S)
regex_markmin_escape = re.compile(r"(\\*)(['`:*~\\[\]{}@\$+\-.#\n])") regex_markmin_escape = re.compile(r"(\\*)(['`:*~\\[\]{}@\$+\-.#\n])")
regex_backslash = re.compile(r"\\(['`:*~\\[\]{}@\$+\-.#\n])") regex_backslash = re.compile(r"\\(['`:*~\\[\]{}@\$+\-.#\n])")
ttab_in = maketrans("'`:*~\\[]{}@$+-.#\n", '\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05') ttab_in = maketrans("'`:*~\\[]{}@$+-.#\n", '\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05')
ttab_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05',"'`:*~\\[]{}@$+-.#\n") ttab_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05', "'`:*~\\[]{}@$+-.#\n")
regex_quote = re.compile('(?P<name>\w+?)\s*\=\s*') regex_quote = re.compile('(?P<name>\w+?)\s*\=\s*')
def make_dict(b): def make_dict(b):
return '{%s}' % regex_quote.sub("'\g<name>':",b) return '{%s}' % regex_quote.sub("'\g<name>':", b)
def safe_eval(node_or_string, env): def safe_eval(node_or_string, env):
""" """
Safely evaluate an expression node or a string containing a Python Safely evaluate an expression node or a string containing a Python
@@ -578,6 +585,7 @@ def safe_eval(node_or_string, env):
node_or_string = ast_parse(node_or_string, mode='eval') node_or_string = ast_parse(node_or_string, mode='eval')
if isinstance(node_or_string, ast.Expression): if isinstance(node_or_string, ast.Expression):
node_or_string = node_or_string.body node_or_string = node_or_string.body
def _convert(node): def _convert(node):
if isinstance(node, ast.Str): if isinstance(node, ast.Str):
return node.s return node.s
@@ -594,11 +602,11 @@ def safe_eval(node_or_string, env):
if node.id in _safe_names: if node.id in _safe_names:
return _safe_names[node.id] return _safe_names[node.id]
elif isinstance(node, ast.BinOp) and \ elif isinstance(node, ast.BinOp) and \
isinstance(node.op, (Add, Sub)) and \ isinstance(node.op, (Add, Sub)) and \
isinstance(node.right, Num) and \ isinstance(node.right, Num) and \
isinstance(node.right.n, complex) and \ isinstance(node.right.n, complex) and \
isinstance(node.left, Num) and \ isinstance(node.left, Num) and \
isinstance(node.left.n, (int, long, float)): isinstance(node.left.n, (int, long, float)):
left = node.left.n left = node.left.n
right = node.right.n right = node.right.n
if isinstance(node.op, Add): if isinstance(node.op, Add):
@@ -606,57 +614,66 @@ def safe_eval(node_or_string, env):
else: else:
return left - right return left - right
raise ValueError('malformed string') raise ValueError('malformed string')
return _convert(node_or_string) return _convert(node_or_string)
def markmin_escape(text): def markmin_escape(text):
""" insert \\ before markmin control characters: '`:*~[]{}@$ """ """ insert \\ before markmin control characters: '`:*~[]{}@$ """
return regex_markmin_escape.sub( return regex_markmin_escape.sub(
lambda m: '\\'+m.group(0).replace('\\','\\\\'), text) lambda m: '\\' + m.group(0).replace('\\', '\\\\'), text)
def replace_autolinks(text,autolinks):
def replace_autolinks(text, autolinks):
return regex_auto.sub(lambda m: autolinks(m.group('k')), text) return regex_auto.sub(lambda m: autolinks(m.group('k')), text)
def replace_at_urls(text,url):
# this is experimental @{function/args}
def u1(match,url=url):
a,c,f,args = match.group('a','c','f','args')
return url(a=a or None,c=c or None,f = f or None,
args=(args or '').split('/'), scheme=True, host=True)
return regex_URL.sub(u1,text)
def replace_components(text,env): def replace_at_urls(text, url):
# this is experimental @{function/args}
def u1(match, url=url):
a, c, f, args = match.group('a', 'c', 'f', 'args')
return url(a=a or None, c=c or None, f=f or None,
args=(args or '').split('/'), scheme=True, host=True)
return regex_URL.sub(u1, text)
def replace_components(text, env):
# not perfect but acceptable # not perfect but acceptable
def u2(match, env=env): def u2(match, env=env):
f = env.get(match.group('a'), match.group(0)) f = env.get(match.group('a'), match.group(0))
if callable(f): if callable(f):
b = match.group('b') b = match.group('b')
try: try:
b = safe_eval(make_dict(b),env) b = safe_eval(make_dict(b), env)
except: except:
pass pass
try: try:
f = f(**b) if isinstance(b,dict) else f(b) f = f(**b) if isinstance(b, dict) else f(b)
except Exception, e: except Exception, e:
f = 'ERROR: %s' % e f = 'ERROR: %s' % e
return str(f) return str(f)
text = regex_env2.sub(u2, text) text = regex_env2.sub(u2, text)
return text return text
def autolinks_simple(url): def autolinks_simple(url):
""" """
it automatically converts the url to link, it automatically converts the url to link,
image, video or audio tag image, video or audio tag
""" """
u_url=url.lower() u_url = url.lower()
if '@' in url and not '://' in url: if '@' in url and '://' not in url:
return '<a href="mailto:%s">%s</a>' % (url, url) return '<a href="mailto:%s">%s</a>' % (url, url)
elif u_url.endswith(('.jpg','.jpeg','.gif','.png')): elif u_url.endswith(('.jpg', '.jpeg', '.gif', '.png')):
return '<img src="%s" controls />' % url return '<img src="%s" controls />' % url
elif u_url.endswith(('.mp4','.mpeg','.mov','.ogv')): elif u_url.endswith(('.mp4', '.mpeg', '.mov', '.ogv')):
return '<video src="%s" controls></video>' % url return '<video src="%s" controls></video>' % url
elif u_url.endswith(('.mp3','.wav','.ogg')): elif u_url.endswith(('.mp3', '.wav', '.ogg')):
return '<audio src="%s" controls></audio>' % url return '<audio src="%s" controls></audio>' % url
return '<a href="%s">%s</a>' % (url,url) return '<a href="%s">%s</a>' % (url, url)
def protolinks_simple(proto, url): def protolinks_simple(proto, url):
""" """
@@ -667,16 +684,18 @@ def protolinks_simple(proto, url):
proto="iframe" proto="iframe"
url="http://www.example.com/path" url="http://www.example.com/path"
""" """
if proto in ('iframe','embed'): #== 'iframe': if proto in ('iframe', 'embed'): # == 'iframe':
return '<iframe src="%s" frameborder="0" allowfullscreen></iframe>'%url return '<iframe src="%s" frameborder="0" allowfullscreen></iframe>' % url
#elif proto == 'embed': # NOTE: embed is a synonym to iframe now # elif proto == 'embed': # NOTE: embed is a synonym to iframe now
# return '<a href="%s" class="%sembed">%s></a>'%(url,class_prefix,url) # return '<a href="%s" class="%sembed">%s></a>'%(url,class_prefix,url)
elif proto == 'qr': elif proto == 'qr':
return '<img style="width:100px" src="http://chart.apis.google.com/chart?cht=qr&chs=100x100&chl=%s&choe=UTF-8&chld=H" alt="QR Code" title="QR Code" />'%url return '<img style="width:100px" src="http://chart.apis.google.com/chart?cht=qr&chs=100x100&chl=%s&choe=UTF-8&chld=H" alt="QR Code" title="QR Code" />' % url
return proto+':'+url return proto + ':' + url
def email_simple(email): def email_simple(email):
return '<a href="mailto:%s">%s</a>' % (email, email) return '<a href="mailto:%s">%s</a>' % (email, email)
def render(text, def render(text,
extra={}, extra={},
@@ -925,17 +944,19 @@ def render(text,
>>> render("anchor with name 'NEWLINE': [[NEWLINE [newline] ]]") >>> render("anchor with name 'NEWLINE': [[NEWLINE [newline] ]]")
'<p>anchor with name \\'NEWLINE\\': <span class="anchor" id="markmin_NEWLINE">newline</span></p>' '<p>anchor with name \\'NEWLINE\\': <span class="anchor" id="markmin_NEWLINE">newline</span></p>'
""" """
if autolinks=="default": autolinks = autolinks_simple if autolinks == "default":
if protolinks=="default": protolinks = protolinks_simple autolinks = autolinks_simple
pp='\n' if pretty_print else '' if protolinks == "default":
if isinstance(text,unicode): protolinks = protolinks_simple
pp = '\n' if pretty_print else ''
if isinstance(text, unicode):
text = text.encode('utf8') text = text.encode('utf8')
text = str(text or '') text = str(text or '')
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text) text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
text = text.replace('\x05','').replace('\r\n', '\n') # concatenate strings separeted by \\n text = text.replace('\x05', '').replace('\r\n', '\n') # concatenate strings separeted by \\n
if URL is not None: if URL is not None:
text = replace_at_urls(text,URL) text = replace_at_urls(text, URL)
if latex == 'google': if latex == 'google':
text = regex_dd.sub('``\g<latex>``:latex ', text) text = regex_dd.sub('``\g<latex>``:latex ', text)
@@ -945,9 +966,10 @@ def render(text,
# store them into segments they will be treated as code # store them into segments they will be treated as code
############################################################# #############################################################
segments = [] segments = []
def mark_code(m): def mark_code(m):
g = m.group(0) g = m.group(0)
if g in (META, DISABLED_META ): if g in (META, DISABLED_META):
segments.append((None, None, None, g)) segments.append((None, None, None, g))
return m.group() return m.group()
elif g == '````': elif g == '````':
@@ -956,10 +978,12 @@ def render(text,
else: else:
c = m.group('c') or '' c = m.group('c') or ''
p = m.group('p') or '' p = m.group('p') or ''
if 'code' in allowed and not c in allowed['code']: c = '' if 'code' in allowed and c not in allowed['code']:
code = m.group('t').replace('!`!','`') c = ''
code = m.group('t').replace('!`!', '`')
segments.append((code, c, p, m.group(0))) segments.append((code, c, p, m.group(0)))
return META return META
text = regex_code.sub(mark_code, text) text = regex_code.sub(mark_code, text)
############################################################# #############################################################
@@ -967,56 +991,58 @@ def render(text,
# store them into links they will be treated as link # store them into links they will be treated as link
############################################################# #############################################################
links = [] links = []
def mark_link(m): def mark_link(m):
links.append( None if m.group() == LINK links.append(None if m.group() == LINK
else m.group('s') ) else m.group('s'))
return LINK return LINK
text = regex_link.sub(mark_link, text) text = regex_link.sub(mark_link, text)
text = escape(text) text = escape(text)
if protolinks: if protolinks:
text = regex_proto.sub(lambda m: protolinks(*m.group('p','k')), text) text = regex_proto.sub(lambda m: protolinks(*m.group('p', 'k')), text)
if autolinks: if autolinks:
text = replace_autolinks(text,autolinks) text = replace_autolinks(text, autolinks)
############################################################# #############################################################
# normalize spaces # normalize spaces
############################################################# #############################################################
strings=text.split('\n') strings = text.split('\n')
def parse_title(t, s): #out, lev, etags, tag, s): def parse_title(t, s): # out, lev, etags, tag, s):
hlevel=str(len(t)) hlevel = str(len(t))
out.extend(etags[::-1]) out.extend(etags[::-1])
out.append("<h%s>%s"%(hlevel,s)) out.append("<h%s>%s" % (hlevel, s))
etags[:]=["</h%s>%s"%(hlevel,pp)] etags[:] = ["</h%s>%s" % (hlevel, pp)]
lev=0 lev = 0
ltags[:]=[] ltags[:] = []
tlev[:]=[] tlev[:] = []
return (lev, 'h') return (lev, 'h')
def parse_list(t, p, s, tag, lev, mtag, lineno): def parse_list(t, p, s, tag, lev, mtag, lineno):
lent=len(t) lent = len(t)
if lent<lev: # current item level < previous item level if lent < lev: # current item level < previous item level
while ltags[-1]>lent: while ltags[-1] > lent:
ltags.pop() ltags.pop()
out.append(etags.pop()) out.append(etags.pop())
lev=lent lev = lent
tlev[lev:]=[] tlev[lev:] = []
if lent>lev: # current item level > previous item level if lent > lev: # current item level > previous item level
if lev==0: # previous line is not a list (paragraph or title) if lev == 0: # previous line is not a list (paragraph or title)
out.extend(etags[::-1]) out.extend(etags[::-1])
ltags[:]=[] ltags[:] = []
tlev[:]=[] tlev[:] = []
etags[:]=[] etags[:] = []
if pend and mtag == '.': # paragraph in a list: if pend and mtag == '.': # paragraph in a list:
out.append(etags.pop()) out.append(etags.pop())
ltags.pop() ltags.pop()
for i in xrange(lent-lev): for i in xrange(lent - lev):
out.append('<'+tag+'>'+pp) out.append('<' + tag + '>' + pp)
etags.append('</'+tag+'>'+pp) etags.append('</' + tag + '>' + pp)
lev+=1 lev += 1
ltags.append(lev) ltags.append(lev)
tlev.append(tag) tlev.append(tag)
elif lent == lev: elif lent == lev:
@@ -1025,22 +1051,22 @@ def render(text,
for i in xrange(ltags.count(lent)): for i in xrange(ltags.count(lent)):
ltags.pop() ltags.pop()
out.append(etags.pop()) out.append(etags.pop())
tlev[-1]=tag tlev[-1] = tag
out.append('<'+tag+'>'+pp) out.append('<' + tag + '>' + pp)
etags.append('</'+tag+'>'+pp) etags.append('</' + tag + '>' + pp)
ltags.append(lev) ltags.append(lev)
else: else:
if ltags.count(lev)>1: if ltags.count(lev) > 1:
out.append(etags.pop()) out.append(etags.pop())
ltags.pop() ltags.pop()
mtag='l' mtag = 'l'
out.append('<li>') out.append('<li>')
etags.append('</li>'+pp) etags.append('</li>' + pp)
ltags.append(lev) ltags.append(lev)
if s[:1] == '-': if s[:1] == '-':
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno) (s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
if p and mtag=='l': if p and mtag == 'l':
(lev,mtag,lineno)=parse_point(t, s, lev, '', lineno) (lev, mtag, lineno) = parse_point(t, s, lev, '', lineno)
else: else:
out.append(s) out.append(s)
@@ -1048,28 +1074,28 @@ def render(text,
def parse_point(t, s, lev, mtag, lineno): def parse_point(t, s, lev, mtag, lineno):
""" paragraphs in lists """ """ paragraphs in lists """
lent=len(t) lent = len(t)
if lent>lev: if lent > lev:
return parse_list(t, '.', s, 'ul', lev, mtag, lineno) return parse_list(t, '.', s, 'ul', lev, mtag, lineno)
elif lent<lev: elif lent < lev:
while ltags[-1]>lent: while ltags[-1] > lent:
ltags.pop() ltags.pop()
out.append(etags.pop()) out.append(etags.pop())
lev=lent lev = lent
tlev[lev:]=[] tlev[lev:] = []
mtag='' mtag = ''
elif lent==lev: elif lent == lev:
if pend and mtag == '.': if pend and mtag == '.':
out.append(etags.pop()) out.append(etags.pop())
ltags.pop() ltags.pop()
if br and mtag in ('l','.'): if br and mtag in ('l', '.'):
out.append(br) out.append(br)
if s == META: if s == META:
mtag = '' mtag = ''
else: else:
mtag = '.' mtag = '.'
if s[:1] == '-': if s[:1] == '-':
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno) (s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
if mtag == '.': if mtag == '.':
out.append(pbeg) out.append(pbeg)
if pend: if pend:
@@ -1083,19 +1109,19 @@ def render(text,
# - is empty -> this is an <hr /> tag # - is empty -> this is an <hr /> tag
# - consists '|' -> table # - consists '|' -> table
# - consists other characters -> blockquote # - consists other characters -> blockquote
if (lineno+1 >= strings_len or if (lineno + 1 >= strings_len or
not(s.count('-') == len(s) and len(s)>3)): not (s.count('-') == len(s) and len(s) > 3)):
return (s, mtag, lineno) return (s, mtag, lineno)
lineno+=1 lineno += 1
s = strings[lineno].strip() s = strings[lineno].strip()
if s: if s:
if '|' in s: if '|' in s:
# table # table
tout=[] tout = []
thead=[] thead = []
tbody=[] tbody = []
rownum=0 rownum = 0
t_id = '' t_id = ''
t_cls = '' t_cls = ''
@@ -1104,14 +1130,14 @@ def render(text,
s = strings[lineno].strip() s = strings[lineno].strip()
if s[:1] == '=': if s[:1] == '=':
# header or footer # header or footer
if s.count('=')==len(s) and len(s)>3: if s.count('=') == len(s) and len(s) > 3:
if not thead: # if thead list is empty: if not thead: # if thead list is empty:
thead = tout thead = tout
else: else:
tbody.extend(tout) tbody.extend(tout)
tout = [] tout = []
rownum=0 rownum = 0
lineno+=1 lineno += 1
continue continue
m = regex_tq.match(s) m = regex_tq.match(s)
@@ -1121,36 +1147,36 @@ def render(text,
break break
if rownum % 2: if rownum % 2:
tr = '<tr class="even">' tr = '<tr class="even">'
else: else:
tr = '<tr class="first">' if rownum == 0 else '<tr>' tr = '<tr class="first">' if rownum == 0 else '<tr>'
tout.append(tr + ''.join(['<td%s>%s</td>' % ( tout.append(tr + ''.join(['<td%s>%s</td>' % (
' class="num"' ' class="num"'
if regex_num.match(f) else '', if regex_num.match(f) else '',
f.strip() f.strip()
) for f in s.split('|')])+'</tr>'+pp) ) for f in s.split('|')]) + '</tr>' + pp)
rownum+=1 rownum += 1
lineno+=1 lineno += 1
t_cls = ' class="%s%s"'%(class_prefix, t_cls) \ t_cls = ' class="%s%s"' % (class_prefix, t_cls) \
if t_cls and t_cls != 'id' else '' if t_cls and t_cls != 'id' else ''
t_id = ' id="%s%s"'%(id_prefix, t_id) if t_id else '' t_id = ' id="%s%s"' % (id_prefix, t_id) if t_id else ''
s = '' s = ''
if thead: if thead:
s += '<thead>'+pp+''.join([l for l in thead])+'</thead>'+pp s += '<thead>' + pp + ''.join([l for l in thead]) + '</thead>' + pp
if not tbody: # tbody strings are in tout list if not tbody: # tbody strings are in tout list
tbody = tout tbody = tout
tout = [] tout = []
if tbody: # if tbody list is not empty: if tbody: # if tbody list is not empty:
s += '<tbody>'+pp+''.join([l for l in tbody])+'</tbody>'+pp s += '<tbody>' + pp + ''.join([l for l in tbody]) + '</tbody>' + pp
if tout: # tfoot is not empty: if tout: # tfoot is not empty:
s += '<tfoot>'+pp+''.join([l for l in tout])+'</tfoot>'+pp 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) s = '<table%s%s>%s%s</table>%s' % (t_cls, t_id, pp, s, pp)
mtag='t' mtag = 't'
else: else:
# parse blockquote: # parse blockquote:
bq_begin=lineno bq_begin = lineno
t_mode = False # embedded table t_mode = False # embedded table
t_cls = '' t_cls = ''
t_id = '' t_id = ''
@@ -1160,57 +1186,57 @@ def render(text,
if not t_mode: if not t_mode:
m = regex_tq.match(s) m = regex_tq.match(s)
if m: if m:
if (lineno+1 == strings_len or if (lineno + 1 == strings_len or
'|' not in strings[lineno+1]): '|' not in strings[lineno + 1]):
t_cls = m.group('c') or '' t_cls = m.group('c') or ''
t_id = m.group('p') or '' t_id = m.group('p') or ''
break break
if regex_bq_headline.match(s): if regex_bq_headline.match(s):
if (lineno+1 < strings_len and if (lineno + 1 < strings_len and
strings[lineno+1].strip()): strings[lineno + 1].strip()):
t_mode = True t_mode = True
lineno+=1 lineno += 1
continue continue
elif regex_tq.match(s): elif regex_tq.match(s):
t_mode=False t_mode = False
lineno+=1 lineno += 1
continue continue
lineno+=1 lineno += 1
t_cls = ' class="%s%s"'%(class_prefix,t_cls) \ t_cls = ' class="%s%s"' % (class_prefix, t_cls) \
if t_cls and t_cls != 'id' else '' if t_cls and t_cls != 'id' else ''
t_id = ' id="%s%s"'%(id_prefix,t_id) \ t_id = ' id="%s%s"' % (id_prefix, t_id) \
if t_id else '' if t_id else ''
s = '<blockquote%s%s>%s</blockquote>%s' \ s = '<blockquote%s%s>%s</blockquote>%s' \
% (t_cls, % (t_cls,
t_id, t_id,
'\n'.join(strings[bq_begin:lineno]),pp) '\n'.join(strings[bq_begin:lineno]), pp)
mtag='q' mtag = 'q'
else: else:
s = '<hr />' s = '<hr />'
lineno-=1 lineno -= 1
mtag='q' mtag = 'q'
return (s, 'q', lineno) return (s, 'q', lineno)
if sep == 'p': if sep == 'p':
pbeg = "<p>" pbeg = "<p>"
pend = "</p>"+pp pend = "</p>" + pp
br = '' br = ''
else: else:
pbeg = pend = '' pbeg = pend = ''
br = "<br />"+pp if sep=='br' else '' br = "<br />" + pp if sep == 'br' else ''
lev = 0 # nesting level of lists lev = 0 # nesting level of lists
c0 = '' # first character of current line c0 = '' # first character of current line
out = [] # list of processed lines out = [] # list of processed lines
etags = [] # trailing tags etags = [] # trailing tags
ltags = [] # level# correspondent to trailing tag ltags = [] # level# correspondent to trailing tag
tlev = [] # list of tags for each level ('ul' or 'ol') tlev = [] # list of tags for each level ('ul' or 'ol')
mtag = '' # marked tag (~last tag) ('l','.','h','p','t'). Used to set <br/> mtag = '' # marked tag (~last tag) ('l','.','h','p','t'). Used to set <br/>
# and to avoid <p></p> around tables and blockquotes # and to avoid <p></p> around tables and blockquotes
lineno = 0 lineno = 0
strings_len = len(strings) strings_len = len(strings)
while lineno < strings_len: while lineno < strings_len:
@@ -1222,65 +1248,67 @@ def render(text,
#### ++++ ---- .... ------- field | field | field <-body #### ++++ ---- .... ------- field | field | field <-body
##### +++++ ----- ..... ---------------------:class[id] ##### +++++ ----- ..... ---------------------:class[id]
""" """
pc0=c0 # first character of previous line pc0 = c0 # first character of previous line
c0=s[:1] c0 = s[:1]
if c0: # for non empty strings if c0: # for non empty strings
if c0 in "#+-.": # first character is one of: # + - . if c0 in "#+-.": # first character is one of: # + - .
(t1,t2,p,ss) = regex_list.findall(s)[0] (t1, t2, p, ss) = regex_list.findall(s)[0]
# t1 - tag ("###") # t1 - tag ("###")
# t2 - tag ("+++", "---", "...") # t2 - tag ("+++", "---", "...")
# p - paragraph point ('.')->for "++." or "--." # p - paragraph point ('.')->for "++." or "--."
# ss - other part of string # ss - other part of string
if t1 or t2: if t1 or t2:
# headers and lists: # headers and lists:
if c0 == '#': # headers if c0 == '#': # headers
(lev, mtag) = parse_title(t1, ss) (lev, mtag) = parse_title(t1, ss)
lineno+=1 lineno += 1
continue continue
elif c0 == '+': # ordered list elif c0 == '+': # ordered list
(lev, mtag, lineno)= parse_list(t2, p, ss, 'ol', lev, mtag, lineno) (lev, mtag, lineno) = parse_list(t2, p, ss, 'ol', lev, mtag, lineno)
lineno+=1 lineno += 1
continue continue
elif c0 == '-': # unordered list, table or blockquote elif c0 == '-': # unordered list, table or blockquote
if p or ss: if p or ss:
(lev, mtag, lineno) = parse_list(t2, p, ss, 'ul', lev, mtag, lineno) (lev, mtag, lineno) = parse_list(t2, p, ss, 'ul', lev, mtag, lineno)
lineno+=1 lineno += 1
continue continue
else: else:
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno) (s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
elif lev>0: # and c0 == '.' # paragraph in lists elif lev > 0: # and c0 == '.' # paragraph in lists
(lev, mtag, lineno) = parse_point(t2, ss, lev, mtag, lineno) (lev, mtag, lineno) = parse_point(t2, ss, lev, mtag, lineno)
lineno+=1 lineno += 1
continue continue
if lev == 0 and (mtag == 'q' or s == META): if lev == 0 and (mtag == 'q' or s == META):
# new paragraph # new paragraph
pc0='' pc0 = ''
if pc0 == '' or (mtag != 'p' and s0 not in (' ','\t')): if pc0 == '' or (mtag != 'p' and s0 not in (' ', '\t')):
# paragraph # paragraph
out.extend(etags[::-1]) out.extend(etags[::-1])
etags=[] etags = []
ltags=[] ltags = []
tlev=[] tlev = []
lev=0 lev = 0
if br and mtag == 'p': out.append(br) if br and mtag == 'p':
out.append(br)
if mtag != 'q' and s != META: if mtag != 'q' and s != META:
if pend: etags=[pend] if pend:
out.append(pbeg) etags = [pend]
mtag = 'p' out.append(pbeg)
mtag = 'p'
else: else:
mtag = '' mtag = ''
out.append(s) out.append(s)
else: else:
if lev>0 and mtag=='.' and s == META: if lev > 0 and mtag == '.' and s == META:
out.append(etags.pop()) out.append(etags.pop())
ltags.pop() ltags.pop()
out.append(s) out.append(s)
mtag = '' mtag = ''
else: else:
out.append(' '+s) out.append(' ' + s)
lineno+=1 lineno += 1
out.extend(etags[::-1]) out.extend(etags[::-1])
text = ''.join(out) text = ''.join(out)
@@ -1295,7 +1323,7 @@ def render(text,
# deal with images, videos, audios and links # deal with images, videos, audios and links
############################################################# #############################################################
def sub_media(m): def sub_media(m):
t,a,k,p,w = m.group('t','a','k','p','w') t, a, k, p, w = m.group('t', 'a', 'k', 'p', 'w')
if not k: if not k:
return m.group(0) return m.group(0)
k = escape(k) k = escape(k)
@@ -1305,40 +1333,40 @@ def render(text,
p_begin = p_end = '' p_begin = p_end = ''
if p == 'center': if p == 'center':
p_begin = '<p style="text-align:center">' p_begin = '<p style="text-align:center">'
p_end = '</p>'+pp p_end = '</p>' + pp
elif p == 'blockleft': elif p == 'blockleft':
p_begin = '<p style="text-align:left">' p_begin = '<p style="text-align:left">'
p_end = '</p>'+pp p_end = '</p>' + pp
elif p == 'blockright': elif p == 'blockright':
p_begin = '<p style="text-align:right">' p_begin = '<p style="text-align:right">'
p_end = '</p>'+pp p_end = '</p>' + pp
elif p in ('left','right'): elif p in ('left', 'right'):
style = ('float:%s' % p)+(';%s' % style if style else '') style = ('float:%s' % p) + (';%s' % style if style else '')
if t and regex_auto.match(t): if t and regex_auto.match(t):
p_begin = p_begin + '<a href="%s">' % t p_begin = p_begin + '<a href="%s">' % t
p_end = '</a>' + p_end p_end = '</a>' + p_end
t = '' t = ''
if style: if style:
style = ' style="%s"' % style style = ' style="%s"' % style
if p in ('video','audio'): if p in ('video', 'audio'):
t = render(t, {}, {}, 'br', URL, environment, latex, t = render(t, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print) autolinks, protolinks, class_prefix, id_prefix, pretty_print)
return '<%(p)s controls="controls"%(title)s%(style)s><source src="%(k)s" />%(t)s</%(p)s>' \ return '<%(p)s controls="controls"%(title)s%(style)s><source src="%(k)s" />%(t)s</%(p)s>' \
% dict(p=p, title=title, style=style, k=k, t=t) % dict(p=p, title=title, style=style, k=k, t=t)
alt = ' alt="%s"'%escape(t).replace(META, DISABLED_META) if t else '' alt = ' alt="%s"' % escape(t).replace(META, DISABLED_META) if t else ''
return '%(begin)s<img src="%(k)s"%(alt)s%(title)s%(style)s />%(end)s' \ return '%(begin)s<img src="%(k)s"%(alt)s%(title)s%(style)s />%(end)s' \
% dict(begin=p_begin, k=k, alt=alt, title=title, style=style, end=p_end) % dict(begin=p_begin, k=k, alt=alt, title=title, style=style, end=p_end)
def sub_link(m): def sub_link(m):
t,a,k,p = m.group('t','a','k','p') t, a, k, p = m.group('t', 'a', 'k', 'p')
if not k and not t: if not k and not t:
return m.group(0) return m.group(0)
t = t or '' t = t or ''
a = escape(a) if a else '' a = escape(a) if a else ''
if k: if k:
if '#' in k and not ':' in k.split('#')[0]: if '#' in k and ':' not in k.split('#')[0]:
# wikipage, not external url # wikipage, not external url
k=k.replace('#','#'+id_prefix) k = k.replace('#', '#' + id_prefix)
k = escape(k) k = escape(k)
title = ' title="%s"' % a.replace(META, DISABLED_META) if a else '' title = ' title="%s"' % a.replace(META, DISABLED_META) if a else ''
target = ' target="_blank"' if p == 'popup' else '' target = ' target="_blank"' if p == 'popup' else ''
@@ -1347,18 +1375,18 @@ def render(text,
return '<a href="%(k)s"%(title)s%(target)s>%(t)s</a>' \ return '<a href="%(k)s"%(title)s%(target)s>%(t)s</a>' \
% dict(k=k, title=title, target=target, t=t) % dict(k=k, title=title, target=target, t=t)
if t == 'NEWLINE' and not a: if t == 'NEWLINE' and not a:
return '<br />'+pp return '<br />' + pp
return '<span class="anchor" id="%s">%s</span>' % ( return '<span class="anchor" id="%s">%s</span>' % (
escape(id_prefix+t), escape(id_prefix + t),
render(a, {},{},'br', URL, render(a, {}, {}, 'br', URL,
environment, latex, autolinks, environment, latex, autolinks,
protolinks, class_prefix, protolinks, class_prefix,
id_prefix, pretty_print)) id_prefix, pretty_print))
parts = text.split(LINK) parts = text.split(LINK)
text = parts[0] text = parts[0]
for i,s in enumerate(links): for i, s in enumerate(links):
if s == None: if s is None:
html = LINK html = LINK
else: else:
html = regex_media_level2.sub(sub_media, s) html = regex_media_level2.sub(sub_media, s)
@@ -1366,51 +1394,53 @@ def render(text,
html = regex_link_level2.sub(sub_link, html) html = regex_link_level2.sub(sub_link, html)
if html == s: if html == s:
# return unprocessed string as a signal of an error # return unprocessed string as a signal of an error
html = '[[%s]]'%s html = '[[%s]]' % s
text += html + parts[i+1] text += html + parts[i + 1]
############################################################# #############################################################
# process all code text # process all code text
############################################################# #############################################################
def expand_meta(m): def expand_meta(m):
code,b,p,s = segments.pop(0) code, b, p, s = segments.pop(0)
if code==None or m.group() == DISABLED_META: if code is None or m.group() == DISABLED_META:
return escape(s) return escape(s)
if b in extra: if b in extra:
if code[:1]=='\n': code=code[1:] if code[:1] == '\n':
if code[-1:]=='\n': code=code[:-1] code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
if p: if p:
return str(extra[b](code,p)) return str(extra[b](code, p))
else: else:
return str(extra[b](code)) return str(extra[b](code))
elif b=='cite': elif b == 'cite':
return '['+','.join('<a href="#%s" class="%s">%s</a>' \ return '[' + ','.join('<a href="#%s" class="%s">%s</a>' %
% (id_prefix+d,b,d) \ (id_prefix + d, b, d) for d in escape(code).split(',')) + ']'
for d in escape(code).split(','))+']' elif b == 'latex':
elif b=='latex':
return LATEX % urllib.quote(code) return LATEX % urllib.quote(code)
elif b in html_colors: elif b in html_colors:
return '<span style="color: %s">%s</span>' \ return '<span style="color: %s">%s</span>' \
% (b, render(code, {}, {}, 'br', URL, environment, latex, % (b, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print)) autolinks, protolinks, class_prefix, id_prefix, pretty_print))
elif b in ('c', 'color') and p: elif b in ('c', 'color') and p:
c=p.split(':') c = p.split(':')
fg='color: %s;' % c[0] if c[0] else '' fg = 'color: %s;' % c[0] if c[0] else ''
bg='background-color: %s;' % c[1] if len(c)>1 and c[1] else '' bg = 'background-color: %s;' % c[1] if len(c) > 1 and c[1] else ''
return '<span style="%s%s">%s</span>' \ return '<span style="%s%s">%s</span>' \
% (fg, bg, render(code, {}, {}, 'br', URL, environment, latex, % (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print)) autolinks, protolinks, class_prefix, id_prefix, pretty_print))
cls = ' class="%s%s"'%(class_prefix,b) if b and b != 'id' else '' cls = ' class="%s%s"' % (class_prefix, b) if b and b != 'id' else ''
id = ' id="%s%s"'%(id_prefix,escape(p)) if p else '' id = ' id="%s%s"' % (id_prefix, escape(p)) if p else ''
beg=(code[:1]=='\n') beg = (code[:1] == '\n')
end=[None,-1][code[-1:]=='\n'] end = [None, -1][code[-1:] == '\n']
if beg and end: if beg and end:
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, escape(code[1:-1]), pp) 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])) return '<code%s%s>%s</code>' % (cls, id, escape(code[beg:end]))
text = regex_expand_meta.sub(expand_meta, text) text = regex_expand_meta.sub(expand_meta, text)
if environment: if environment:
text = replace_components(text,environment) text = replace_components(text, environment)
return text.translate(ttab_out) return text.translate(ttab_out)
@@ -1423,16 +1453,18 @@ def markmin2html(text, extra={}, allowed={}, sep='p',
class_prefix=class_prefix, id_prefix=id_prefix, class_prefix=class_prefix, id_prefix=id_prefix,
pretty_print=pretty_print) pretty_print=pretty_print)
def run_doctests(): def run_doctests():
import doctest import doctest
doctest.testmod() doctest.testmod()
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
import doctest import doctest
from textwrap import dedent from textwrap import dedent
html=dedent(""" html = dedent("""
<!doctype html> <!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <head>
@@ -1446,7 +1478,7 @@ if __name__ == '__main__':
</html>""")[1:] </html>""")[1:]
if sys.argv[1:2] == ['-h']: if sys.argv[1:2] == ['-h']:
style=dedent(""" style = dedent("""
<style> <style>
blockquote { background-color: #FFFAAE; padding: 7px; } blockquote { background-color: #FFFAAE; padding: 7px; }
table { border-collapse: collapse; } table { border-collapse: collapse; }
@@ -1467,22 +1499,23 @@ if __name__ == '__main__':
body=markmin2html(__doc__, pretty_print=True)) body=markmin2html(__doc__, pretty_print=True))
elif sys.argv[1:2] == ['-t']: elif sys.argv[1:2] == ['-t']:
from timeit import Timer from timeit import Timer
loops=1000
ts = Timer("markmin2html(__doc__)","from markmin2html import markmin2html") loops = 1000
ts = Timer("markmin2html(__doc__)", "from markmin2html import markmin2html")
print 'timeit "markmin2html(__doc__)":' print 'timeit "markmin2html(__doc__)":'
t = min([ts.timeit(loops) for i in range(3)]) t = min([ts.timeit(loops) for i in range(3)])
print "%s loops, best of 3: %.3f ms per loop" % (loops, t/1000*loops) print "%s loops, best of 3: %.3f ms per loop" % (loops, t / 1000 * loops)
elif len(sys.argv) > 1: elif len(sys.argv) > 1:
fargv = open(sys.argv[1],'r') fargv = open(sys.argv[1], 'r')
try: try:
markmin_text=fargv.read() markmin_text = fargv.read()
# embed css file from second parameter into html file # embed css file from second parameter into html file
if len(sys.argv) > 2: if len(sys.argv) > 2:
if sys.argv[2].startswith('@'): if sys.argv[2].startswith('@'):
markmin_style = '<link rel="stylesheet" href="'+sys.argv[2][1:]+'"/>' markmin_style = '<link rel="stylesheet" href="' + sys.argv[2][1:] + '"/>'
else: else:
fargv2 = open(sys.argv[2],'r') fargv2 = open(sys.argv[2], 'r')
try: try:
markmin_style = "<style>\n" + fargv2.read() + "</style>" markmin_style = "<style>\n" + fargv2.read() + "</style>"
finally: finally:
@@ -1496,10 +1529,9 @@ if __name__ == '__main__':
fargv.close() fargv.close()
else: else:
print "Usage: "+sys.argv[0]+" -h | -t | file.markmin [file.css|@path_to/css]" print "Usage: " + sys.argv[0] + " -h | -t | file.markmin [file.css|@path_to/css]"
print "where: -h - print __doc__" print "where: -h - print __doc__"
print " -t - timeit __doc__ (for testing purpuse only)" print " -t - timeit __doc__ (for testing purpuse only)"
print " file.markmin [file.css] - process file.markmin + built in file.css (optional)" 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)" print " file.markmin [@path_to/css] - process file.markmin + link path_to/css (optional)"
run_doctests() run_doctests()
+138 -117
View File
@@ -7,53 +7,57 @@ import sys
import doctest import doctest
from optparse import OptionParser from optparse import OptionParser
__all__ = ['render','markmin2latex'] __all__ = ['render', 'markmin2latex']
META = 'META' META = 'META'
regex_newlines = re.compile('(\n\r)|(\r\n)') regex_newlines = re.compile('(\n\r)|(\r\n)')
regex_dd=re.compile('\$\$(?P<latex>.*?)\$\$') regex_dd = re.compile('\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('('+META+')|(``(?P<t>.*?)``(:(?P<c>\w+))?)',re.S) regex_code = re.compile('(' + META + ')|(``(?P<t>.*?)``(:(?P<c>\w+))?)', re.S)
regex_title = re.compile('^#{1} (?P<t>[^\n]+)',re.M) regex_title = re.compile('^#{1} (?P<t>[^\n]+)', re.M)
regex_maps = [ regex_maps = [
(re.compile('[ \t\r]+\n'),'\n'), (re.compile('[ \t\r]+\n'), '\n'),
(re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'),'{\\\\bf \g<t>}'), (re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'), '{\\\\bf \g<t>}'),
(re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"),'{\\it \g<t>}'), (re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"), '{\\it \g<t>}'),
(re.compile('^#{5,6}\s*(?P<t>[^\n]+)',re.M),'\n\n{\\\\bf \g<t>}\n'), (re.compile('^#{5,6}\s*(?P<t>[^\n]+)', re.M), '\n\n{\\\\bf \g<t>}\n'),
(re.compile('^#{4}\s*(?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsubsection{\g<t>}\n'), (re.compile('^#{4}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\subsubsection{\g<t>}\n'),
(re.compile('^#{3}\s*(?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsection{\g<t>}\n'), (re.compile('^#{3}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\subsection{\g<t>}\n'),
(re.compile('^#{2}\s*(?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\section{\g<t>}\n'), (re.compile('^#{2}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\section{\g<t>}\n'),
(re.compile('^#{1}\s*(?P<t>[^\n]+)',re.M),''), (re.compile('^#{1}\s*(?P<t>[^\n]+)', re.M), ''),
(re.compile('^\- +(?P<t>.*)',re.M),'\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'), (re.compile('^\- +(?P<t>.*)', re.M), '\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'),
(re.compile('^\+ +(?P<t>.*)',re.M),'\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'), (re.compile('^\+ +(?P<t>.*)', re.M), '\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'),
(re.compile('\\\\end\{itemize\}\s+\\\\begin\{itemize\}'),'\n'), (re.compile('\\\\end\{itemize\}\s+\\\\begin\{itemize\}'), '\n'),
(re.compile('\n\s+\n'),'\n\n')] (re.compile('\n\s+\n'), '\n\n')]
regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n',re.M|re.S) regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n', re.M | re.S)
regex_anchor = re.compile('\[\[(?P<t>\S+)\]\]') regex_anchor = re.compile('\[\[(?P<t>\S+)\]\]')
regex_bibitem = re.compile('\-\s*\[\[(?P<t>\S+)\]\]') regex_bibitem = re.compile('\-\s*\[\[(?P<t>\S+)\]\]')
regex_image_width = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center) +(?P<w>\d+px)\]\]') regex_image_width = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center) +(?P<w>\d+px)\]\]')
regex_image = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center)\]\]') regex_image = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center)\]\]')
#regex_video = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +video\]\]') # regex_video = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +video\]\]')
#regex_audio = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +audio\]\]') # regex_audio = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +audio\]\]')
regex_link = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+)\]\]') regex_link = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+)\]\]')
regex_auto = re.compile('(?<!["\w])(?P<k>\w+://[\w\.\-\?&%\:]+)',re.M) regex_auto = re.compile('(?<!["\w])(?P<k>\w+://[\w\.\-\?&%\:]+)', re.M)
regex_commas = re.compile('[ ]+(?P<t>[,;\.])') regex_commas = re.compile('[ ]+(?P<t>[,;\.])')
regex_noindent = re.compile('\n\n(?P<t>[a-z])') regex_noindent = re.compile('\n\n(?P<t>[a-z])')
#regex_quote_left = re.compile('"(?=\w)')
#regex_quote_right = re.compile('(?=\w\.)"')
def latex_escape(text,pound=True):
text=text.replace('\\','{\\textbackslash}') # regex_quote_left = re.compile('"(?=\w)')
for c in '^_&$%{}': text=text.replace(c,'\\'+c) # regex_quote_right = re.compile('(?=\w\.)"')
text=text.replace('\\{\\textbackslash\\}','{\\textbackslash}')
if pound: text=text.replace('#','\\#') def latex_escape(text, pound=True):
text = text.replace('\\', '{\\textbackslash}')
for c in '^_&$%{}':
text = text.replace(c, '\\' + c)
text = text.replace('\\{\\textbackslash\\}', '{\\textbackslash}')
if pound: text = text.replace('#', '\\#')
return text return text
def render(text, def render(text,
extra={}, extra={},
allowed={}, allowed={},
sep='p', sep='p',
image_mapper=lambda x:x, image_mapper=lambda x: x,
chapters=False): chapters=False):
############################################################# #############################################################
# replace all blocks marked with ``...``:class with META # replace all blocks marked with ``...``:class with META
@@ -61,62 +65,68 @@ def render(text,
############################################################# #############################################################
text = str(text or '') text = str(text or '')
segments, i = [], 0 segments, i = [], 0
text = regex_dd.sub('``\g<latex>``:latex ',text) text = regex_dd.sub('``\g<latex>``:latex ', text)
text = regex_newlines.sub('\n',text) text = regex_newlines.sub('\n', text)
while True: while True:
item = regex_code.search(text,i) item = regex_code.search(text, i)
if not item: break if not item:
if item.group()==META: break
segments.append((None,None)) if item.group() == META:
text = text[:item.start()]+META+text[item.end():] segments.append((None, None))
text = text[:item.start()] + META + text[item.end():]
else: else:
c = item.group('c') or '' c = item.group('c') or ''
if 'code' in allowed and not c in allowed['code']: c = '' if 'code' in allowed and c not in allowed['code']:
code = item.group('t').replace('!`!','`') c = ''
segments.append((code,c)) code = item.group('t').replace('!`!', '`')
text = text[:item.start()]+META+text[item.end():] segments.append((code, c))
i=item.start()+3 text = text[:item.start()] + META + text[item.end():]
i = item.start() + 3
############################################################# #############################################################
# do h1,h2,h3,h4,h5,h6,b,i,ol,ul and normalize spaces # do h1,h2,h3,h4,h5,h6,b,i,ol,ul and normalize spaces
############################################################# #############################################################
title = regex_title.search(text) title = regex_title.search(text)
if not title: title='Title' if not title:
else: title=title.group('t') title = 'Title'
else:
title = title.group('t')
text = latex_escape(text,pound=False) text = latex_escape(text, pound=False)
texts = text.split('## References',1) texts = text.split('## References', 1)
text = regex_anchor.sub('\\label{\g<t>}', texts[0]) text = regex_anchor.sub('\\label{\g<t>}', texts[0])
if len(texts)==2: if len(texts) == 2:
text += '\n\\begin{thebibliography}{999}\n' text += '\n\\begin{thebibliography}{999}\n'
text += regex_bibitem.sub('\n\\\\bibitem{\g<t>}', texts[1]) text += regex_bibitem.sub('\n\\\\bibitem{\g<t>}', texts[1])
text += '\n\\end{thebibliography}\n' text += '\n\\end{thebibliography}\n'
text = '\n'.join(t.strip() for t in text.split('\n')) text = '\n'.join(t.strip() for t in text.split('\n'))
for regex, sub in regex_maps: for regex, sub in regex_maps:
text = regex.sub(sub,text) text = regex.sub(sub, text)
text=text.replace('#','\\#') text = text.replace('#', '\\#')
text=text.replace('`',"'") text = text.replace('`', "'")
############################################################# #############################################################
# process tables and blockquotes # process tables and blockquotes
############################################################# #############################################################
while True: while True:
item = regex_table.search(text) item = regex_table.search(text)
if not item: break if not item:
break
c = item.group('c') or '' c = item.group('c') or ''
if 'table' in allowed and not c in allowed['table']: c = '' if 'table' in allowed and c not in allowed['table']:
c = ''
content = item.group('t') content = item.group('t')
if ' | ' in content: if ' | ' in content:
rows = content.replace('\n','\\\\\n').replace(' | ',' & ') rows = content.replace('\n', '\\\\\n').replace(' | ', ' & ')
row0,row2 = rows.split('\\\\\n',1) row0, row2 = rows.split('\\\\\n', 1)
cols=row0.count(' & ')+1 cols = row0.count(' & ') + 1
cal='{'+''.join('l' for j in range(cols))+'}' cal = '{' + ''.join('l' for j in range(cols)) + '}'
tabular = '\\begin{center}\n{\\begin{tabular}'+cal+'\\hline\n' + row0+'\\\\ \\hline\n'+row2 + ' \\\\ \\hline\n\\end{tabular}}\n\\end{center}' tabular = '\\begin{center}\n{\\begin{tabular}' + cal + '\\hline\n' + row0 + '\\\\ \\hline\n' + row2 + ' \\\\ \\hline\n\\end{tabular}}\n\\end{center}'
if row2.count('\n')>20: tabular='\\newpage\n'+tabular if row2.count('\n') > 20:
tabular = '\\newpage\n' + tabular
text = text[:item.start()] + tabular + text[item.end():] text = text[:item.start()] + tabular + text[item.end():]
else: else:
text = text[:item.start()] + '\\begin{quote}' + content + '\\end{quote}' + text[item.end():] text = text[:item.start()] + '\\begin{quote}' + content + '\\end{quote}' + text[item.end():]
@@ -126,29 +136,32 @@ def render(text,
############################################################# #############################################################
def sub(x): def sub(x):
f=image_mapper(x.group('k')) f = image_mapper(x.group('k'))
if not f: return None if not f:
return '\n\\begin{center}\\includegraphics[width=8cm]{%s}\\end{center}\n' % (f) return None
text = regex_image_width.sub(sub,text) return '\n\\begin{center}\\includegraphics[width=8cm]{%s}\\end{center}\n' % f
text = regex_image.sub(sub,text)
text = regex_image_width.sub(sub, text)
text = regex_image.sub(sub, text)
text = regex_link.sub('{\\\\footnotesize\\href{\g<k>}{\g<t>}}', text) text = regex_link.sub('{\\\\footnotesize\\href{\g<k>}{\g<t>}}', text)
text = regex_commas.sub('\g<t>',text) text = regex_commas.sub('\g<t>', text)
text = regex_noindent.sub('\n\\\\noindent \g<t>',text) text = regex_noindent.sub('\n\\\\noindent \g<t>', text)
### fix paths in images # ## fix paths in images
regex=re.compile('\\\\_\w*\.(eps|png|jpg|gif)') regex = re.compile('\\\\_\w*\.(eps|png|jpg|gif)')
while True: while True:
match=regex.search(text) match = regex.search(text)
if not match: break if not match:
text=text[:match.start()]+text[match.start()+1:] break
#text = regex_quote_left.sub('``',text) text = text[:match.start()] + text[match.start() + 1:]
#text = regex_quote_right.sub("''",text) # text = regex_quote_left.sub('``',text)
# text = regex_quote_right.sub("''",text)
if chapters: if chapters:
text=text.replace(r'\section*{',r'\chapter*{') text = text.replace(r'\section*{', r'\chapter*{')
text=text.replace(r'\section{',r'\chapter{') text = text.replace(r'\section{', r'\chapter{')
text=text.replace(r'subsection{',r'section{') text = text.replace(r'subsection{', r'section{')
############################################################# #############################################################
# process all code text # process all code text
@@ -156,57 +169,64 @@ def render(text,
parts = text.split(META) parts = text.split(META)
text = parts[0] text = parts[0]
authors = [] authors = []
for i,(code,b) in enumerate(segments): for i, (code, b) in enumerate(segments):
if code==None: if code is None:
html = META html = META
else: else:
if b=='hidden': if b == 'hidden':
html='' html = ''
elif b=='author': elif b == 'author':
author = latex_escape(code.strip()) author = latex_escape(code.strip())
authors.append(author) authors.append(author)
html='' html = ''
elif b=='inxx': elif b == 'inxx':
html='\inxx{%s}' % latex_escape(code) html = '\inxx{%s}' % latex_escape(code)
elif b=='cite': elif b == 'cite':
html='~\cite{%s}' % latex_escape(code.strip()) html = '~\cite{%s}' % latex_escape(code.strip())
elif b=='ref': elif b == 'ref':
html='~\ref{%s}' % latex_escape(code.strip()) html = '~\ref{%s}' % latex_escape(code.strip())
elif b=='latex': elif b == 'latex':
if '\n' in code: if '\n' in code:
html='\n\\begin{equation}\n%s\n\\end{equation}\n' % code.strip() html = '\n\\begin{equation}\n%s\n\\end{equation}\n' % code.strip()
else: else:
html='$%s$' % code.strip() html = '$%s$' % code.strip()
elif b=='latex_eqnarray': elif b == 'latex_eqnarray':
code=code.strip() code = code.strip()
code='\\\\'.join(x.replace('=','&=&',1) for x in code.split('\\\\')) code = '\\\\'.join(x.replace('=', '&=&', 1) for x in code.split('\\\\'))
html='\n\\begin{eqnarray}\n%s\n\\end{eqnarray}\n' % code html = '\n\\begin{eqnarray}\n%s\n\\end{eqnarray}\n' % code
elif b.startswith('latex_'): elif b.startswith('latex_'):
key=b[6:] key = b[6:]
html='\\begin{%s}%s\\end{%s}' % (key,code,key) html = '\\begin{%s}%s\\end{%s}' % (key, code, key)
elif b in extra: elif b in extra:
if code[:1]=='\n': code=code[1:] if code[:1] == '\n':
if code[-1:]=='\n': code=code[:-1] code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
html = extra[b](code) html = extra[b](code)
elif code[:1]=='\n' or code[:-1]=='\n': elif code[:1] == '\n' or code[:-1] == '\n':
if code[:1]=='\n': code=code[1:] if code[:1] == '\n':
if code[-1:]=='\n': code=code[:-1] code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
if code.startswith('<') or code.startswith('{{') or code.startswith('http'): if code.startswith('<') or code.startswith('{{') or code.startswith('http'):
html = '\\begin{lstlisting}[keywords={}]\n%s\n\\end{lstlisting}' % code html = '\\begin{lstlisting}[keywords={}]\n%s\n\\end{lstlisting}' % code
else: else:
html = '\\begin{lstlisting}\n%s\n\\end{lstlisting}' % code html = '\\begin{lstlisting}\n%s\n\\end{lstlisting}' % code
else: else:
if code[:1]=='\n': code=code[1:] if code[:1] == '\n':
if code[-1:]=='\n': code=code[:-1] code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
html = '{\\ft %s}' % latex_escape(code) html = '{\\ft %s}' % latex_escape(code)
try: try:
text = text+html+parts[i+1] text = text + html + parts[i + 1]
except: except:
text = text + '... WIKI PROCESSING ERROR ...' text = text + '... WIKI PROCESSING ERROR ...'
break break
text = text.replace(' ~\\cite','~\\cite') text = text.replace(' ~\\cite', '~\\cite')
return text, title, authors return text, title, authors
WRAPPER = """ WRAPPER = """
\\documentclass[12pt]{article} \\documentclass[12pt]{article}
\\usepackage{hyperref} \\usepackage{hyperref}
@@ -239,12 +259,14 @@ WRAPPER = """
\\end{document} \\end{document}
""" """
def markmin2latex(data, image_mapper=lambda x:x, extra={},
def markmin2latex(data, image_mapper=lambda x: x, extra={},
wrapper=WRAPPER): wrapper=WRAPPER):
body, title, authors = render(data, extra=extra, image_mapper=image_mapper) body, title, authors = render(data, extra=extra, image_mapper=image_mapper)
author = '\n\\and\n'.join(a.replace('\n','\\\\\n\\footnotesize ') for a in authors) author = '\n\\and\n'.join(a.replace('\n', '\\\\\n\\footnotesize ') for a in authors)
return wrapper % dict(title=title, author=author, body=body) return wrapper % dict(title=title, author=author, body=body)
if __name__ == '__main__': if __name__ == '__main__':
parser = OptionParser() parser = OptionParser()
parser.add_option("-i", "--info", dest="info", parser.add_option("-i", "--info", dest="info",
@@ -252,40 +274,39 @@ if __name__ == '__main__':
parser.add_option("-t", "--test", dest="test", action="store_true", parser.add_option("-t", "--test", dest="test", action="store_true",
default=False) default=False)
parser.add_option("-n", "--no_wrapper", dest="no_wrapper", parser.add_option("-n", "--no_wrapper", dest="no_wrapper",
action="store_true",default=False) action="store_true", default=False)
parser.add_option("-c", "--chapters", dest="chapters",action="store_true", parser.add_option("-c", "--chapters", dest="chapters", action="store_true",
default=False,help="switch section for chapter") default=False, help="switch section for chapter")
parser.add_option("-w", "--wrapper", dest="wrapper", default=False, parser.add_option("-w", "--wrapper", dest="wrapper", default=False,
help="latex file containing header and footer") help="latex file containing header and footer")
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
if options.info: if options.info:
import markmin2html import markmin2html
markmin2latex(markmin2html.__doc__) markmin2latex(markmin2html.__doc__)
elif options.test: elif options.test:
doctest.testmod() doctest.testmod()
else: else:
if options.wrapper: if options.wrapper:
fwrapper = open(options.wrapper,'rb') fwrapper = open(options.wrapper, 'rb')
try: try:
wrapper = fwrapper.read() wrapper = fwrapper.read()
finally: finally:
fwrapper.close() fwrapper.close()
elif options.no_wrapper: elif options.no_wrapper:
wrapper = '%(body)s' wrapper = '%(body)s'
else: else:
wrapper = WRAPPER wrapper = WRAPPER
for f in args: for f in args:
fargs = open(f,'r') fargs = open(f, 'r')
content_data = [] content_data = []
try: try:
content_data.append(fargs.read()) content_data.append(fargs.read())
finally: finally:
fargs.close() fargs.close()
content = '\n'.join(content_data) content = '\n'.join(content_data)
output= markmin2latex(content, output = markmin2latex(content,
wrapper=wrapper, wrapper=wrapper,
chapters=options.chapters) chapters=options.chapters)
print output print output
+28 -27
View File
@@ -13,21 +13,22 @@ from markmin2latex import markmin2latex
__all__ = ['markmin2pdf'] __all__ = ['markmin2pdf']
def removeall(path):
ERROR_STR= """Error removing %(path)s, %(error)s """ def removeall(path):
ERROR_STR = """Error removing %(path)s, %(error)s """
def rmgeneric(path, __func__): def rmgeneric(path, __func__):
try: try:
__func__(path) __func__(path)
except OSError, (errno, strerror): except OSError, (errno, strerror):
print ERROR_STR % {'path' : path, 'error': strerror } print ERROR_STR % {'path': path, 'error': strerror}
files=[path] files = [path]
while files: while files:
file=files[0] file = files[0]
if os.path.isfile(file): if os.path.isfile(file):
f=os.remove f = os.remove
rmgeneric(file, os.remove) rmgeneric(file, os.remove)
del files[0] del files[0]
elif os.path.isdir(file): elif os.path.isdir(file):
@@ -36,7 +37,7 @@ def removeall(path):
rmgeneric(file, os.rmdir) rmgeneric(file, os.rmdir)
del files[0] del files[0]
else: else:
files = [os.path.join(file,x) for x in nested] + files files = [os.path.join(file, x) for x in nested] + files
def latex2pdf(latex, pdflatex='pdflatex', passes=3): def latex2pdf(latex, pdflatex='pdflatex', passes=3):
@@ -49,13 +50,13 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
- passes: defines how often pdflates should be run in the texfile. - passes: defines how often pdflates should be run in the texfile.
""" """
pdflatex=pdflatex pdflatex = pdflatex
passes=passes passes = passes
warnings=[] warnings = []
# setup the envoriment # setup the envoriment
tmpdir = mkdtemp() tmpdir = mkdtemp()
texfile = open(tmpdir+'/test.tex','wb') texfile = open(tmpdir + '/test.tex', 'wb')
texfile.write(latex) texfile.write(latex)
texfile.seek(0) texfile.seek(0)
texfile.close() texfile.close()
@@ -63,8 +64,8 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
# start doing some work # start doing some work
for i in range(0, passes): for i in range(0, passes):
logfd,logname = mkstemp() logfd, logname = mkstemp()
outfile=os.fdopen(logfd) outfile = os.fdopen(logfd)
try: try:
ret = subprocess.call([pdflatex, ret = subprocess.call([pdflatex,
'-interaction=nonstopmode', '-interaction=nonstopmode',
@@ -75,18 +76,18 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
finally: finally:
outfile.close() outfile.close()
re_errors=re.compile('^\!(.*)$',re.M) re_errors = re.compile('^\!(.*)$', re.M)
re_warnings=re.compile('^LaTeX Warning\:(.*)$',re.M) re_warnings = re.compile('^LaTeX Warning\:(.*)$', re.M)
flog = open(logname) flog = open(logname)
try: try:
loglines = flog.read() loglines = flog.read()
finally: finally:
flog.close() flog.close()
errors=re_errors.findall(loglines) errors = re_errors.findall(loglines)
warnings=re_warnings.findall(loglines) warnings = re_warnings.findall(loglines)
os.unlink(logname) os.unlink(logname)
pdffile=texfile.rsplit('.',1)[0]+'.pdf' pdffile = texfile.rsplit('.', 1)[0] + '.pdf'
if os.path.isfile(pdffile): if os.path.isfile(pdffile):
fpdf = open(pdffile, 'rb') fpdf = open(pdffile, 'rb')
try: try:
@@ -100,31 +101,31 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
def markmin2pdf(text, image_mapper=lambda x: None, extra={}): def markmin2pdf(text, image_mapper=lambda x: None, extra={}):
return latex2pdf(markmin2latex(text,image_mapper=image_mapper, extra=extra)) return latex2pdf(markmin2latex(text, image_mapper=image_mapper, extra=extra))
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
import doctest import doctest
import markmin2html import markmin2html
if sys.argv[1:2]==['-h']:
if sys.argv[1:2] == ['-h']:
data, warnings, errors = markmin2pdf(markmin2html.__doc__) data, warnings, errors = markmin2pdf(markmin2html.__doc__)
if errors: if errors:
print 'ERRORS:'+'\n'.join(errors) print 'ERRORS:' + '\n'.join(errors)
print 'WARNGINS:'+'\n'.join(warnings) print 'WARNGINS:' + '\n'.join(warnings)
else: else:
print data print data
elif len(sys.argv)>1: elif len(sys.argv) > 1:
fargv = open(sys.argv[1],'rb') fargv = open(sys.argv[1], 'rb')
try: try:
data, warnings, errors = markmin2pdf(fargv.read()) data, warnings, errors = markmin2pdf(fargv.read())
finally: finally:
fargv.close() fargv.close()
if errors: if errors:
print 'ERRORS:'+'\n'.join(errors) print 'ERRORS:' + '\n'.join(errors)
print 'WARNGINS:'+'\n'.join(warnings) print 'WARNGINS:' + '\n'.join(warnings)
else: else:
print data print data
else: else:
doctest.testmod() doctest.testmod()
+6 -7
View File
@@ -308,7 +308,7 @@ def URL(a=None,
else: else:
function = f function = f
# if the url gets a static resource, don't force extention # if the url gets a static resource, don't force extension
if controller == 'static': if controller == 'static':
extension = None extension = None
# add static version to url # add static version to url
@@ -955,7 +955,6 @@ class DIV(XmlComponent):
# get the xml for the inner components # get the xml for the inner components
co = join([xmlescape(component) for component in co = join([xmlescape(component) for component in
self.components]) self.components])
return (fa, co) return (fa, co)
def xml(self): def xml(self):
@@ -990,7 +989,7 @@ class DIV(XmlComponent):
Examples: Examples:
>>> markdown = lambda text,tag=None,attributes={}: \ >>> markdown = lambda text, tag=None, attributes={}: \
{None: re.sub('\s+',' ',text), \ {None: re.sub('\s+',' ',text), \
'h1':'#'+text+'\\n\\n', \ 'h1':'#'+text+'\\n\\n', \
'p':text+'\\n'}.get(tag,text) 'p':text+'\\n'}.get(tag,text)
@@ -1171,13 +1170,13 @@ class DIV(XmlComponent):
return i return i
else: else:
self[i] = replace(self[i]) if callable(replace) else replace self[i] = replace(self[i]) if callable(replace) else replace
return i+1 return i + 1
# loop the components # loop the components
if find_text or find_components: if find_text or find_components:
i = 0 i = 0
while i < len(self.components): while i < len(self.components):
c = self[i] c = self[i]
j = i+1 j = i + 1
if check and find_text and isinstance(c, str) and \ if check and find_text and isinstance(c, str) and \
((is_regex and find_text.search(c)) or (str(find_text) in c)): ((is_regex and find_text.search(c)) or (str(find_text) in c)):
j = replace_component(i) j = replace_component(i)
@@ -2573,7 +2572,7 @@ class MENU(DIV):
item[3], select, prefix=CAT(prefix, item[0], '/')) item[3], select, prefix=CAT(prefix, item[0], '/'))
select['_onchange'] = 'window.location=this.value' select['_onchange'] = 'window.location=this.value'
# avoid to wrap the select if no custom items are present # avoid to wrap the select if no custom items are present
html = DIV(select, self.serialize(custom_items)) if len(custom_items) else select html = DIV(select, self.serialize(custom_items)) if len(custom_items) else select
return html return html
def xml(self): def xml(self):
@@ -2805,7 +2804,7 @@ class MARKMIN(XmlComponent):
self.extra = extra or {} self.extra = extra or {}
self.allowed = allowed or {} self.allowed = allowed or {}
self.sep = sep self.sep = sep
self.url = URL if url == True else url self.url = URL if url is True else url
self.environment = environment self.environment = environment
self.latex = latex self.latex = latex
self.autolinks = autolinks self.autolinks = autolinks
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -181,7 +181,7 @@ class JobGraph(object):
task_child=task_child, task_child=task_child,
job_name=self.job_name) job_name=self.job_name)
def validate(self, job_name): def validate(self, job_name=None):
"""Validates if all tasks job_name can be completed, i.e. there """Validates if all tasks job_name can be completed, i.e. there
are no mutual dependencies among tasks. are no mutual dependencies among tasks.
Commits at the end if successfull, or it rollbacks the entire Commits at the end if successfull, or it rollbacks the entire
+28 -13
View File
@@ -1897,33 +1897,39 @@ class SQLFORM(FORM):
else: else:
field_type = field.type field_type = field.type
operators = SELECT(*[OPTION(T(option), _value=option) for option in options], _class='form-control') operators = SELECT(*[OPTION(T(option), _value=option) for option in options],
_class='form-control')
_id = "%s_%s" % (value_id, name) _id = "%s_%s" % (value_id, name)
if field_type in ['boolean', 'double', 'time', 'integer']: if field_type in ['boolean', 'double', 'time', 'integer']:
widget_ = SQLFORM.widgets[field_type] widget_ = SQLFORM.widgets[field_type]
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control') value_input = widget_.widget(field, field.default, _id=_id,
_class=widget_._class + ' form-control')
elif field_type == 'date': elif field_type == 'date':
iso_format = {'_data-w2p_date_format': '%Y-%m-%d'} iso_format = {'_data-w2p_date_format': '%Y-%m-%d'}
widget_ = SQLFORM.widgets.date widget_ = SQLFORM.widgets.date
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format) value_input = widget_.widget(field, field.default, _id=_id,
_class=widget_._class + ' form-control',
**iso_format)
elif field_type == 'datetime': elif field_type == 'datetime':
iso_format = {'_data-w2p_datetime_format': '%Y-%m-%d %H:%M:%S'} iso_format = {'_data-w2p_datetime_format': '%Y-%m-%d %H:%M:%S'}
widget_ = SQLFORM.widgets.datetime widget_ = SQLFORM.widgets.datetime
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format) value_input = widget_.widget(field, field.default, _id=_id,
elif (field_type.startswith('reference ') or _class=widget_._class + ' form-control',
field_type.startswith('list:reference ')) and \ **iso_format)
hasattr(field.requires, 'options') or \ elif hasattr(field.requires, 'options'):
hasattr(field.requires, 'options'):
value_input = SELECT( value_input = SELECT(
*[OPTION(v, _value=k) *[OPTION(v, _value=k)
for k, v in field.requires.options()], for k, v in field.requires.options()],
_class='form-control', _class='form-control',
**dict(_id=_id)) **dict(_id=_id))
elif field_type.startswith('reference ') or \ elif (field_type.startswith('integer') or
field_type.startswith('list:integer') or \ field_type.startswith('reference ') or
field_type.startswith('list:reference '): field_type.startswith('list:integer') or
field_type.startswith('list:reference ')):
widget_ = SQLFORM.widgets.integer widget_ = SQLFORM.widgets.integer
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control') value_input = widget_.widget(
field, field.default, _id=_id,
_class=widget_._class + ' form-control')
else: else:
value_input = INPUT( value_input = INPUT(
_type='text', _id=_id, _type='text', _id=_id,
@@ -3037,7 +3043,16 @@ class SQLFORM(FORM):
query = query & constraints[table._tablename] query = query & constraints[table._tablename]
if isinstance(links, dict): if isinstance(links, dict):
links = links.get(table._tablename, []) links = links.get(table._tablename, [])
for key in 'columns,orderby,searchable,sortable,paginate,deletable,editable,details,selectable,create,fields'.split(','): for key in ('fields', 'field_id', 'left', 'headers', 'orderby', 'groupby', 'searchable',
'sortable', 'paginate', 'deletable', 'editable', 'details', 'selectable',
'create', 'csv', 'links', 'links_in_grid', 'upload', 'maxtextlengths',
'maxtextlength', 'onvalidation', 'onfailure', 'oncreate', 'onupdate',
'ondelete', 'sorter_icons', 'ui', 'showbuttontext', '_class', 'formname',
'search_widget', 'advanced_search', 'ignore_rw', 'formstyle', 'exportclasses',
'formargs', 'createargs', 'editargs', 'viewargs', 'selectable_submit_button',
'buttons_placement', 'links_placement', 'noconfirm', 'cache_count', 'client_side_delete',
'ignore_common_filters', 'auto_pagination', 'use_cursor'
):
if isinstance(kwargs.get(key, None), dict): if isinstance(kwargs.get(key, None), dict):
if table._tablename in kwargs[key]: if table._tablename in kwargs[key]:
kwargs[key] = kwargs[key][table._tablename] kwargs[key] = kwargs[key][table._tablename]
+1
View File
@@ -21,6 +21,7 @@ from test_contribs import *
from test_web import * from test_web import *
from test_dal import * from test_dal import *
from test_tools import * from test_tools import *
from test_scheduler import *
if sys.version[:3] == '2.7': if sys.version[:3] == '2.7':
from test_old_doctests import * from test_old_doctests import *
+20 -14
View File
@@ -37,10 +37,11 @@ def tearDownModule():
pass pass
class TestCache(unittest.TestCase): class TestCache(unittest.TestCase):
def testCacheInRam(self): # TODO: test_CacheAbstract(self):
def test_CacheInRam(self):
# defaults to mode='http' # defaults to mode='http'
cache = CacheInRam() cache = CacheInRam()
@@ -53,22 +54,21 @@ class TestCache(unittest.TestCase):
cache.clear() cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3) self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4) self.assertEqual(cache('a', lambda: 4, 0), 4)
#test singleton behaviour # test singleton behaviour
cache = CacheInRam() cache = CacheInRam()
cache.clear() cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3) self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4) self.assertEqual(cache('a', lambda: 4, 0), 4)
#test key deletion # test key deletion
cache('a', None) cache('a', None)
self.assertEqual(cache('a', lambda: 5, 100), 5) self.assertEqual(cache('a', lambda: 5, 100), 5)
#test increment # test increment
self.assertEqual(cache.increment('a'), 6) self.assertEqual(cache.increment('a'), 6)
self.assertEqual(cache('a', lambda: 1, 100), 6) self.assertEqual(cache('a', lambda: 1, 100), 6)
cache.increment('b') cache.increment('b')
self.assertEqual(cache('b', lambda: 'x', 100), 1) self.assertEqual(cache('b', lambda: 'x', 100), 1)
def test_CacheOnDisk(self):
def testCacheOnDisk(self):
# defaults to mode='http' # defaults to mode='http'
s = Storage({'application': 'admin', s = Storage({'application': 'admin',
@@ -83,30 +83,36 @@ class TestCache(unittest.TestCase):
cache.clear() cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3) self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4) self.assertEqual(cache('a', lambda: 4, 0), 4)
#test singleton behaviour # test singleton behaviour
cache = CacheOnDisk(s) cache = CacheOnDisk(s)
cache.clear() cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3) self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4) self.assertEqual(cache('a', lambda: 4, 0), 4)
#test key deletion # test key deletion
cache('a', None) cache('a', None)
self.assertEqual(cache('a', lambda: 5, 100), 5) self.assertEqual(cache('a', lambda: 5, 100), 5)
#test increment # test increment
self.assertEqual(cache.increment('a'), 6) self.assertEqual(cache.increment('a'), 6)
self.assertEqual(cache('a', lambda: 1, 100), 6) self.assertEqual(cache('a', lambda: 1, 100), 6)
cache.increment('b') cache.increment('b')
self.assertEqual(cache('b', lambda: 'x', 100), 1) self.assertEqual(cache('b', lambda: 'x', 100), 1)
def testCacheWithPrefix(self): # TODO: def test_CacheAction(self):
# TODO: def test_Cache(self):
# TODO: def test_lazy_cache(self):
def test_CacheWithPrefix(self):
s = Storage({'application': 'admin', s = Storage({'application': 'admin',
'folder': 'applications/admin'}) 'folder': 'applications/admin'})
cache = Cache(s) cache = Cache(s)
prefix = cache.with_prefix(cache.ram,'prefix') prefix = cache.with_prefix(cache.ram, 'prefix')
self.assertEqual(prefix('a', lambda: 1, 0), 1) self.assertEqual(prefix('a', lambda: 1, 0), 1)
self.assertEqual(prefix('a', lambda: 2, 100), 1) self.assertEqual(prefix('a', lambda: 2, 100), 1)
self.assertEqual(cache.ram('prefixa', lambda: 2, 100), 1) self.assertEqual(cache.ram('prefixa', lambda: 2, 100), 1)
def testRegex(self): def test_Regex(self):
cache = CacheInRam() cache = CacheInRam()
self.assertEqual(cache('a1', lambda: 1, 0), 1) self.assertEqual(cache('a1', lambda: 1, 0), 1)
self.assertEqual(cache('a2', lambda: 2, 100), 2) self.assertEqual(cache('a2', lambda: 2, 100), 2)
@@ -114,7 +120,7 @@ class TestCache(unittest.TestCase):
self.assertEqual(cache('a1', lambda: 2, 0), 2) self.assertEqual(cache('a1', lambda: 2, 0), 2)
self.assertEqual(cache('a2', lambda: 3, 100), 3) self.assertEqual(cache('a2', lambda: 3, 100), 3)
def testDALcache(self): def test_DALcache(self):
s = Storage({'application': 'admin', s = Storage({'application': 'admin',
'folder': 'applications/admin'}) 'folder': 'applications/admin'})
cache = Cache(s) cache = Cache(s)
+8 -6
View File
@@ -106,16 +106,18 @@ class TestDALAdapters(unittest.TestCase):
def test_mysql(self): def test_mysql(self):
if os.environ.get('APPVEYOR'): if os.environ.get('APPVEYOR'):
return return
os.environ["DB"] = "mysql://root:@localhost/pydal" if os.environ.get('TRAVIS'):
result = self._run_tests() os.environ["DB"] = "mysql://root:@localhost/pydal"
self.assertTrue(result) result = self._run_tests()
self.assertTrue(result)
def test_pg8000(self): def test_pg8000(self):
if os.environ.get('APPVEYOR'): if os.environ.get('APPVEYOR'):
return return
os.environ["DB"] = "postgres:pg8000://postgres:@localhost/pydal" if os.environ.get('TRAVIS'):
result = self._run_tests() os.environ["DB"] = "postgres:pg8000://postgres:@localhost/pydal"
self.assertTrue(result) result = self._run_tests()
self.assertTrue(result)
if __name__ == '__main__': if __name__ == '__main__':
+354
View File
@@ -0,0 +1,354 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit tests for gluon.scheduler
"""
import os
import unittest
import glob
import datetime
import sys
from fix_path import fix_sys_path
fix_sys_path(__file__)
from gluon.storage import Storage
from gluon.languages import translator
from gluon.scheduler import JobGraph, Scheduler
from gluon.dal import DAL
class BaseTestScheduler(unittest.TestCase):
def setUp(self):
self.db = None
self.cleanfolder()
from gluon import current
s = Storage({'application': 'welcome',
'folder': 'applications/welcome',
'controller': 'default'})
current.request = s
T = translator('', 'en')
current.T = T
self.db = DAL('sqlite://dummy2.db', check_reserved=['all'])
def cleanfolder(self):
if self.db:
self.db.close()
try:
os.unlink('dummy2.db')
except:
pass
tfiles = glob.glob('*_scheduler*.table')
for a in tfiles:
os.unlink(a)
def tearDown(self):
self.cleanfolder()
try:
self.inner_teardown()
except:
pass
class TestsForJobGraph(BaseTestScheduler):
def testJobGraph(self):
s = Scheduler(self.db)
myjob = JobGraph(self.db, 'job_1')
fname = 'foo'
# We have a few items to wear, and there's an "order" to respect...
# Items are: watch, jacket, shirt, tie, pants, undershorts, belt, shoes, socks
# Now, we can't put on the tie without wearing the shirt first, etc...
watch = s.queue_task(fname, task_name='watch')
jacket = s.queue_task(fname, task_name='jacket')
shirt = s.queue_task(fname, task_name='shirt')
tie = s.queue_task(fname, task_name='tie')
pants = s.queue_task(fname, task_name='pants')
undershorts = s.queue_task(fname, task_name='undershorts')
belt = s.queue_task(fname, task_name='belt')
shoes = s.queue_task(fname, task_name='shoes')
socks = s.queue_task(fname, task_name='socks')
# before the tie, comes the shirt
myjob.add_deps(tie.id, shirt.id)
# before the belt too comes the shirt
myjob.add_deps(belt.id, shirt.id)
# before the jacket, comes the tie
myjob.add_deps(jacket.id, tie.id)
# before the belt, come the pants
myjob.add_deps(belt.id, pants.id)
# before the shoes, comes the pants
myjob.add_deps(shoes.id, pants.id)
# before the pants, comes the undershorts
myjob.add_deps(pants.id, undershorts.id)
# before the shoes, comes the undershorts
myjob.add_deps(shoes.id, undershorts.id)
# before the jacket, comes the belt
myjob.add_deps(jacket.id, belt.id)
# before the shoes, comes the socks
myjob.add_deps(shoes.id, socks.id)
## results in the following topological sort
# 9,3,6 --> 4,5 --> 8,7 --> 2
# socks, shirt, undershorts
# tie, pants
# shoes, belt
# jacket
known_toposort = [
set([socks.id, shirt.id, undershorts.id]),
set([tie.id, pants.id]),
set([shoes.id, belt.id]),
set([jacket.id])
]
toposort = myjob.validate('job_1')
self.assertEqual(toposort, known_toposort)
# add a cyclic dependency, jacket to undershorts
myjob.add_deps(undershorts.id, jacket.id)
# no exceptions raised, but result None
self.assertEqual(myjob.validate('job_1'), None)
def testJobGraphFailing(self):
s = Scheduler(self.db)
myjob = JobGraph(self.db, 'job_1')
fname = 'foo'
# We have a few items to wear, and there's an "order" to respect...
# Items are: watch, jacket, shirt, tie, pants, undershorts, belt, shoes, socks
# Now, we can't put on the tie without wearing the shirt first, etc...
watch = s.queue_task(fname, task_name='watch')
jacket = s.queue_task(fname, task_name='jacket')
shirt = s.queue_task(fname, task_name='shirt')
tie = s.queue_task(fname, task_name='tie')
pants = s.queue_task(fname, task_name='pants')
undershorts = s.queue_task(fname, task_name='undershorts')
belt = s.queue_task(fname, task_name='belt')
shoes = s.queue_task(fname, task_name='shoes')
socks = s.queue_task(fname, task_name='socks')
# before the tie, comes the shirt
myjob.add_deps(tie.id, shirt.id)
# before the belt too comes the shirt
myjob.add_deps(belt.id, shirt.id)
# before the jacket, comes the tie
myjob.add_deps(jacket.id, tie.id)
# before the belt, come the pants
myjob.add_deps(belt.id, pants.id)
# before the shoes, comes the pants
myjob.add_deps(shoes.id, pants.id)
# before the pants, comes the undershorts
myjob.add_deps(pants.id, undershorts.id)
# before the shoes, comes the undershorts
myjob.add_deps(shoes.id, undershorts.id)
# before the jacket, comes the belt
myjob.add_deps(jacket.id, belt.id)
# before the shoes, comes the socks
myjob.add_deps(shoes.id, socks.id)
# add a cyclic dependency, jacket to undershorts
myjob.add_deps(undershorts.id, jacket.id)
# no exceptions raised, but result None
self.assertEqual(myjob.validate('job_1'), None)
# and no deps added
deps_inserted = self.db(self.db.scheduler_task_deps.id>0).count()
self.assertEqual(deps_inserted, 0)
def testJobGraphDifferentJobs(self):
s = Scheduler(self.db)
myjob1 = JobGraph(self.db, 'job_1')
myjob2 = JobGraph(self.db, 'job_2')
fname = 'foo'
# We have a few items to wear, and there's an "order" to respect...
# Items are: watch, jacket, shirt, tie, pants, undershorts, belt, shoes, socks
# Now, we can't put on the tie without wearing the shirt first, etc...
watch = s.queue_task(fname, task_name='watch')
jacket = s.queue_task(fname, task_name='jacket')
shirt = s.queue_task(fname, task_name='shirt')
tie = s.queue_task(fname, task_name='tie')
pants = s.queue_task(fname, task_name='pants')
undershorts = s.queue_task(fname, task_name='undershorts')
belt = s.queue_task(fname, task_name='belt')
shoes = s.queue_task(fname, task_name='shoes')
socks = s.queue_task(fname, task_name='socks')
# before the tie, comes the shirt
myjob1.add_deps(tie.id, shirt.id)
# before the belt too comes the shirt
myjob1.add_deps(belt.id, shirt.id)
# before the jacket, comes the tie
myjob1.add_deps(jacket.id, tie.id)
# before the belt, come the pants
myjob1.add_deps(belt.id, pants.id)
# before the shoes, comes the pants
myjob2.add_deps(shoes.id, pants.id)
# before the pants, comes the undershorts
myjob2.add_deps(pants.id, undershorts.id)
# before the shoes, comes the undershorts
myjob2.add_deps(shoes.id, undershorts.id)
# before the jacket, comes the belt
myjob2.add_deps(jacket.id, belt.id)
# before the shoes, comes the socks
myjob2.add_deps(shoes.id, socks.id)
# every job by itself can be completed
self.assertNotEqual(myjob1.validate('job_1'), None)
self.assertNotEqual(myjob1.validate('job_2'), None)
# and, implicitly, every queued task can be too
self.assertNotEqual(myjob1.validate(), None)
# add a cyclic dependency, jacket to undershorts
myjob2.add_deps(undershorts.id, jacket.id)
# every job can still be completed by itself
self.assertNotEqual(myjob1.validate('job_1'), None)
self.assertNotEqual(myjob1.validate('job_2'), None)
# but trying to see if every task will ever be completed fails
self.assertEqual(myjob2.validate(), None)
class TestsForSchedulerAPIs(BaseTestScheduler):
def testQueue_Task(self):
def isnotqueued(result):
self.assertEqual(result.id, None)
self.assertEqual(result.uuid, None)
self.assertEqual(len(result.errors.keys()) > 0, True)
def isqueued(result):
self.assertNotEqual(result.id, None)
self.assertNotEqual(result.uuid, None)
self.assertEqual(len(result.errors.keys()), 0)
s = Scheduler(self.db)
fname = 'foo'
watch = s.queue_task(fname, task_name='watch')
# queuing a task returns id, errors, uuid
self.assertEqual(set(watch.keys()), set(['id', 'uuid', 'errors']))
# queueing nothing isn't allowed
self.assertRaises(TypeError, s.queue_task, *[])
# passing pargs and pvars wrongly
# # pargs as dict
isnotqueued(s.queue_task(fname, dict(a=1), dict(b=1)))
# # pvars as list
isnotqueued(s.queue_task(fname, ['foo', 'bar'], ['foo', 'bar']))
# two tasks with the same uuid won't be there
isqueued(s.queue_task(fname, uuid='a'))
isnotqueued(s.queue_task(fname, uuid='a'))
# # #FIXME add here every parameter
def testTask_Status(self):
s = Scheduler(self.db)
fname = 'foo'
watch = s.queue_task(fname, task_name='watch')
# fetch status by id
by_id = s.task_status(watch.id)
# fetch status by uuid
by_uuid = s.task_status(watch.uuid)
# fetch status by query
by_query = s.task_status(self.db.scheduler_task.function_name == 'foo')
self.assertEqual(by_id, by_uuid)
self.assertEqual(by_id, by_query)
# fetch status by anything else throws
self.assertRaises(SyntaxError, s.task_status, *[[1, 2]])
# adding output returns the joined set, plus "result"
rtn = s.task_status(watch.id, output=True)
self.assertEqual(set(rtn.keys()), set(['scheduler_run', 'scheduler_task', 'result']))
class testForSchedulerRunnerBase(BaseTestScheduler):
def inner_teardown(self):
from gluon import current
fdest = os.path.join(current.request.folder, 'models', 'scheduler.py')
os.unlink(fdest)
def writefunction(self, content, initlines=None):
from gluon import current
fdest = os.path.join(current.request.folder, 'models', 'scheduler.py')
if initlines is None:
initlines = """
import os
import time
from gluon.scheduler import Scheduler
db_dal = os.path.abspath(os.path.join(request.folder, '..', '..', 'dummy2.db'))
sched_dal = DAL('sqlite://%s' % db_dal, folder=os.path.dirname(db_dal))
sched = Scheduler(sched_dal, max_empty_runs=20, migrate=False, heartbeat=1)
"""
with open(fdest, 'w') as q:
q.write(initlines)
q.write(content)
def exec_sched(self):
import subprocess
call_args = [sys.executable, 'web2py.py', '-K', 'welcome']
ret = subprocess.call(call_args, env=dict(os.environ))
return ret
class TestsForSchedulerRunner(testForSchedulerRunnerBase):
def testBasic(self):
s = Scheduler(self.db)
foo = s.queue_task('foo')
self.db.commit()
self.writefunction(r"""
def foo():
return 'a'
""")
ret = self.exec_sched()
# process finished just fine
self.assertEqual(ret, 0)
info = s.task_status(foo.id, output=True)
self.assertEqual(info.result, 'a')
def testRetryFailed(self):
s = Scheduler(self.db)
failed = s.queue_task('demo2', retry_failed=1, period=5)
failed_consecutive = s.queue_task('demo8', retry_failed=2, repeats=2, period=5)
self.db.commit()
self.writefunction(r"""
def demo2():
1/0
def demo8():
placeholder = os.path.join(request.folder, 'private', 'demo8.pholder')
with open(placeholder, 'a') as g:
g.write('\nplaceholder for demo8 created')
num_of_lines = 0
with open(placeholder) as f:
num_of_lines = len([a for a in f.read().split('\n') if a])
print 'number of lines', num_of_lines
if num_of_lines <= 2:
1/0
else:
os.unlink(placeholder)
return 1
""")
ret = self.exec_sched()
# process finished just fine
self.assertEqual(ret, 0)
# failed - checks
info = s.task_status(failed.id)
task_runs = self.db(self.db.scheduler_run.task_id == info.id).select()
res = [
("task status failed", info.status == 'FAILED'),
("task times_run is 0", info.times_run == 0),
("task times_failed is 2", info.times_failed == 2),
("task ran 2 times only", len(task_runs) == 2),
("scheduler_run records are FAILED", (task_runs[0].status == task_runs[1].status == 'FAILED')),
("period is respected", (task_runs[1].start_time > task_runs[0].start_time + datetime.timedelta(seconds=info.period)))
]
for a in res:
self.assertEqual(a[1], True, msg=a[0])
# failed consecutive - checks
info = s.task_status(failed_consecutive.id)
task_runs = self.db(self.db.scheduler_run.task_id == info.id).select()
res = [
("task status completed", info.status == 'COMPLETED'),
("task times_run is 2", info.times_run == 2),
("task times_failed is 0", info.times_failed == 0),
("task ran 6 times", len(task_runs) == 6),
("scheduler_run records for COMPLETED is 2", len([run.status for run in task_runs if run.status == 'COMPLETED']) == 2),
("scheduler_run records for FAILED is 4", len([run.status for run in task_runs if run.status == 'FAILED']) == 4),
]
for a in res:
self.assertEqual(a[1], True, msg=a[0])
if __name__ == '__main__':
unittest.main()
File diff suppressed because one or more lines are too long
+76 -1
View File
@@ -9,10 +9,11 @@ from fix_path import fix_sys_path
fix_sys_path(__file__) fix_sys_path(__file__)
import template
from template import render from template import render
class TestVirtualFields(unittest.TestCase): class TestTemplate(unittest.TestCase):
def testRun(self): def testRun(self):
self.assertEqual(render(content='{{for i in range(n):}}{{=i}}{{pass}}', self.assertEqual(render(content='{{for i in range(n):}}{{=i}}{{pass}}',
@@ -61,6 +62,80 @@ class TestVirtualFields(unittest.TestCase):
self.assertRaises( self.assertRaises(
SyntaxError, render, content='{{pass\n=list((1,2,\n3))}}') SyntaxError, render, content='{{pass\n=list((1,2,\n3))}}')
def testWithDummyFileSystem(self):
from os.path import join as pjoin
import contextlib
from StringIO import StringIO
from gluon.restricted import RestrictedError
@contextlib.contextmanager
def monkey_patch(module, fn_name, patch):
try:
unpatch = getattr(module, fn_name)
except AttributeError:
unpatch = None
setattr(module, fn_name, patch)
try:
yield
finally:
if unpatch is None:
delattr(module, fn_name)
else:
setattr(module, fn_name, unpatch)
def dummy_open(path, mode):
if path == pjoin('views', 'layout.html'):
return StringIO("{{block left_sidebar}}left{{end}}"
"{{include}}"
"{{block right_sidebar}}right{{end}}")
elif path == pjoin('views', 'layoutbrackets.html'):
return StringIO("[[block left_sidebar]]left[[end]]"
"[[include]]"
"[[block right_sidebar]]right[[end]]")
elif path == pjoin('views', 'default', 'index.html'):
return StringIO("{{extend 'layout.html'}}"
"{{block left_sidebar}}{{super}} {{end}}"
"to"
"{{block right_sidebar}} {{super}}{{end}}")
elif path == pjoin('views', 'default', 'indexbrackets.html'):
return StringIO("[[extend 'layoutbrackets.html']]"
"[[block left_sidebar]][[super]] [[end]]"
"to"
"[[block right_sidebar]] [[super]][[end]]")
elif path == pjoin('views', 'default', 'missing.html'):
return StringIO("{{extend 'wut'}}"
"{{block left_sidebar}}{{super}} {{end}}"
"to"
"{{block right_sidebar}} {{super}}{{end}}")
elif path == pjoin('views', 'default', 'noescape.html'):
return StringIO("""{{=NOESCAPE('<script></script>')}}""")
raise IOError
with monkey_patch(template, 'open', dummy_open):
self.assertEqual(
render(filename=pjoin('views', 'default', 'index.html'),
path='views'),
'left to right')
self.assertEqual(
render(filename=pjoin('views', 'default', 'indexbrackets.html'),
path='views', delimiters=('[[', ']]')),
'left to right')
self.assertRaises(
RestrictedError,
render,
filename=pjoin('views', 'default', 'missing.html'),
path='views')
response = template.DummyResponse()
response.delimiters = ('[[', ']]')
self.assertEqual(
render(filename=pjoin('views', 'default', 'indexbrackets.html'),
path='views', context={'response': response}),
'left to right')
self.assertEqual(
render(filename=pjoin('views', 'default', 'noescape.html'),
context={'NOESCAPE': template.NOESCAPE}),
'<script></script>')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
+315 -81
View File
@@ -20,7 +20,7 @@ DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
from gluon.dal import DAL, Field from gluon.dal import DAL, Field
from pydal.objects import Table from pydal.objects import Table
from tools import Auth, Mail from tools import Auth, Mail, Recaptcha, Recaptcha2
from gluon.globals import Request, Response, Session from gluon.globals import Request, Response, Session
from storage import Storage from storage import Storage
from languages import translator from languages import translator
@@ -29,57 +29,6 @@ from gluon.http import HTTP
python_version = sys.version[:3] python_version = sys.version[:3]
IS_IMAP = "imap" in DEFAULT_URI IS_IMAP = "imap" in DEFAULT_URI
@unittest.skipIf(IS_IMAP, "TODO: Imap raises 'Connection refused'")
class TestAuth(unittest.TestCase):
def testRun(self):
# setup
request = Request(env={})
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.folder = 'applications/admin'
response = Response()
session = Session()
T = translator('', 'en')
session.connect(request, response)
from gluon.globals import current
current.request = request
current.response = response
current.session = session
current.T = T
db = DAL(DEFAULT_URI, check_reserved=['all'])
auth = Auth(db)
auth.define_tables(username=True, signature=False)
self.assertTrue('auth_user' in db)
self.assertTrue('auth_group' in db)
self.assertTrue('auth_membership' in db)
self.assertTrue('auth_permission' in db)
self.assertTrue('auth_event' in db)
db.define_table('t0', Field('tt'), auth.signature)
auth.enable_record_versioning(db)
self.assertTrue('t0_archive' in db)
for f in ['login', 'register', 'retrieve_password',
'retrieve_username']:
html_form = getattr(auth, f)().xml()
self.assertTrue('name="_formkey"' in html_form)
for f in ['logout', 'verify_email', 'reset_password',
'change_password', 'profile', 'groups']:
self.assertRaisesRegexp(HTTP, "303*", getattr(auth, f))
self.assertRaisesRegexp(HTTP, "401*", auth.impersonate)
try:
for t in ['t0_archive', 't0', 'auth_cas', 'auth_event',
'auth_membership', 'auth_permission', 'auth_group',
'auth_user']:
db[t].drop()
except SyntaxError as e:
# GAE doesn't support drop
pass
return
class TestMail(unittest.TestCase): class TestMail(unittest.TestCase):
""" """
@@ -102,7 +51,7 @@ class TestMail(unittest.TestCase):
users = {} users = {}
def __init__(self, address, port, **kwargs): def __init__(self, address, port, **kwargs):
self.address=address self.address = address
self.port = port self.port = port
self.has_quit = False self.has_quit = False
self.tls = False self.tls = False
@@ -110,14 +59,14 @@ class TestMail(unittest.TestCase):
def login(self, username, password): def login(self, username, password):
if username not in self.users or self.users[username] != password: if username not in self.users or self.users[username] != password:
raise smtplib.SMTPAuthenticationError raise smtplib.SMTPAuthenticationError
self.username=username self.username = username
self.password=password self.password = password
def sendmail(self, sender, to, payload): def sendmail(self, sender, to, payload):
self.inbox.append(TestMail.Message(sender, to, payload)) self.inbox.append(TestMail.Message(sender, to, payload))
def quit(self): def quit(self):
self.has_quit=True self.has_quit = True
def ehlo(self, hostname=None): def ehlo(self, hostname=None):
pass pass
@@ -125,7 +74,6 @@ class TestMail(unittest.TestCase):
def starttls(self): def starttls(self):
self.tls = True self.tls = True
def setUp(self): def setUp(self):
self.original_SMTP = smtplib.SMTP self.original_SMTP = smtplib.SMTP
self.original_SMTP_SSL = smtplib.SMTP_SSL self.original_SMTP_SSL = smtplib.SMTP_SSL
@@ -141,10 +89,10 @@ class TestMail(unittest.TestCase):
mail.settings.server = 'smtp.example.com:25' mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com' mail.settings.sender = 'you@example.com'
self.assertTrue(mail.send(to=['somebody@example.com'], self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello', subject='hello',
# If reply_to is omitted, then mail.settings.sender is used # If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com', reply_to='us@example.com',
message='world')) message='world'))
message = TestMail.DummySMTP.inbox.pop() message = TestMail.DummySMTP.inbox.pop()
self.assertEqual(message.sender, mail.settings.sender) self.assertEqual(message.sender, mail.settings.sender)
self.assertEqual(message.to, ['somebody@example.com']) self.assertEqual(message.to, ['somebody@example.com'])
@@ -158,10 +106,10 @@ class TestMail(unittest.TestCase):
mail.settings.sender = 'you@example.com' mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password' mail.settings.login = 'username:password'
self.assertFalse(mail.send(to=['somebody@example.com'], self.assertFalse(mail.send(to=['somebody@example.com'],
subject='hello', subject='hello',
# If reply_to is omitted, then mail.settings.sender is used # If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com', reply_to='us@example.com',
message='world')) message='world'))
def test_login(self): def test_login(self):
TestMail.DummySMTP.users['username'] = 'password' TestMail.DummySMTP.users['username'] = 'password'
@@ -170,10 +118,10 @@ class TestMail(unittest.TestCase):
mail.settings.sender = 'you@example.com' mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password' mail.settings.login = 'username:password'
self.assertTrue(mail.send(to=['somebody@example.com'], self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello', subject='hello',
# If reply_to is omitted, then mail.settings.sender is used # If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com', reply_to='us@example.com',
message='world')) message='world'))
del TestMail.DummySMTP.users['username'] del TestMail.DummySMTP.users['username']
TestMail.DummySMTP.inbox.pop() TestMail.DummySMTP.inbox.pop()
@@ -182,10 +130,10 @@ class TestMail(unittest.TestCase):
mail.settings.server = 'smtp.example.com:25' mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com' mail.settings.sender = 'you@example.com'
self.assertTrue(mail.send(to=['somebody@example.com'], self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello', subject='hello',
# If reply_to is omitted, then mail.settings.sender is used # If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com', reply_to='us@example.com',
message='<html><head></head><body></body></html>')) message='<html><head></head><body></body></html>'))
message = TestMail.DummySMTP.inbox.pop() message = TestMail.DummySMTP.inbox.pop()
self.assertTrue('Content-Type: text/html' in message.payload) self.assertTrue('Content-Type: text/html' in message.payload)
@@ -195,10 +143,10 @@ class TestMail(unittest.TestCase):
mail.settings.sender = 'you@example.com' mail.settings.sender = 'you@example.com'
mail.settings.ssl = True mail.settings.ssl = True
self.assertTrue(mail.send(to=['somebody@example.com'], self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello', subject='hello',
# If reply_to is omitted, then mail.settings.sender is used # If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com', reply_to='us@example.com',
message='world')) message='world'))
TestMail.DummySMTP.inbox.pop() TestMail.DummySMTP.inbox.pop()
def test_tls(self): def test_tls(self):
@@ -207,16 +155,302 @@ class TestMail(unittest.TestCase):
mail.settings.sender = 'you@example.com' mail.settings.sender = 'you@example.com'
mail.settings.tls = True mail.settings.tls = True
self.assertTrue(mail.send(to=['somebody@example.com'], self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello', subject='hello',
# If reply_to is omitted, then mail.settings.sender is used # If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com', reply_to='us@example.com',
message='world')) message='world'))
TestMail.DummySMTP.inbox.pop() TestMail.DummySMTP.inbox.pop()
def test_attachment(self):
module_file = os.path.abspath(__file__)
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
message='world',
attachments=Mail.Attachment(module_file)))
message = TestMail.DummySMTP.inbox.pop()
import email
parsed_msg = email.message_from_string(message.payload)
attachment = parsed_msg.get_payload(1).get_payload(decode=True)
with open(module_file, 'rb') as mf:
self.assertEqual(attachment.decode('utf-8'), mf.read().decode('utf-8'))
# Test missing attachment name error
stream = open(module_file)
self.assertRaises(Exception, lambda *args, **kwargs: Mail.Attachment(*args, **kwargs), stream)
stream.close()
# Test you can define content-id and content type
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
message='world',
attachments=Mail.Attachment(module_file, content_id='trololo', content_type='tra/lala')))
message = TestMail.DummySMTP.inbox.pop()
self.assertTrue('Content-Type: tra/lala' in message.payload)
self.assertTrue('Content-Id: <trololo>' in message.payload)
# class TestRecaptcha(unittest.TestCase):
# def test_Recaptcha(self):
# from html import FORM
# form = FORM(Recaptcha(public_key='public_key', private_key='private_key'))
# self.assertEqual(form.xml(),
# '<form action="#" enctype="multipart/form-data" method="post"><div id="recaptcha"><script><!--\nvar RecaptchaOptions = {};\n//--></script><script src="http://www.google.com/recaptcha/api/challenge?k=public_key" type="text/javascript"></script><noscript><iframe frameborder="0" height="300" src="http://www.google.com/recaptcha/api/noscript?k=public_key" width="500"></iframe><br /><input name="recaptcha_response_field" type="hidden" value="manual_challenge" /></noscript></div></form>')
#
#
# class TestRecaptcha2(unittest.TestCase):
# def test_Recaptcha2(self):
# from html import FORM
# form = FORM(Recaptcha2(public_key='public_key', private_key='private_key'))
# rtn = '<form action="#" enctype="multipart/form-data" method="post"><div><script async="" defer="" src="https://www.google.com/recaptcha/api.js"></script><div class="g-recaptcha" data-sitekey="public_key"></div><noscript>\n<div style="width: 302px; height: 352px;">\n<div style="width: 302px; height: 352px; position: relative;">\n <div style="width: 302px; height: 352px; position: absolute;">\n <iframe src="https://www.google.com/recaptcha/api/fallback?k=public_key"\n frameborder="0" scrolling="no"\n style="width: 302px; height:352px; border-style: none;">\n </iframe>\n </div>\n <div style="width: 250px; height: 80px; position: absolute; border-style: none;\n bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">\n <textarea id="g-recaptcha-response" name="g-recaptcha-response"\n class="g-recaptcha-response"\n style="width: 250px; height: 80px; border: 1px solid #c1c1c1;\n margin: 0px; padding: 0px; resize: none;" value="">\n </textarea>\n </div>\n</div>\n</div></noscript></div></form>'
# self.assertEqual(form.xml(), rtn)
# TODO: class TestAuthJWT(unittest.TestCase):
@unittest.skipIf(IS_IMAP, "TODO: Imap raises 'Connection refused'")
class TestAuth(unittest.TestCase):
def setUp(self):
request = Request(env={})
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.folder = 'applications/admin'
response = Response()
session = Session()
T = translator('', 'en')
session.connect(request, response)
from gluon.globals import current
current.request = request
current.response = response
current.session = session
current.T = T
self.db = DAL(DEFAULT_URI, check_reserved=['all'])
self.auth = Auth(self.db)
self.auth.define_tables(username=True, signature=False)
self.db.define_table('t0', Field('tt'), self.auth.signature)
self.auth.enable_record_versioning(self.db)
# Create a user
self.auth.get_or_create_user(dict(first_name='Bart',
last_name='Simpson',
username='bart',
email='bart@simpson.com',
password='bart_password',
registration_key='bart',
registration_id=''
))
# self.auth.settings.registration_requires_verification = False
# self.auth.settings.registration_requires_approval = False
def test_assert_setup(self):
self.assertEqual(self.db(self.db.auth_user.username == 'bart').select().first()['username'], 'bart')
self.assertTrue('auth_user' in self.db)
self.assertTrue('auth_group' in self.db)
self.assertTrue('auth_membership' in self.db)
self.assertTrue('auth_permission' in self.db)
self.assertTrue('auth_event' in self.db)
def test_enable_record_versioning(self):
self.assertTrue('t0_archive' in self.db)
def test_basic_blank_forms(self):
for f in ['login', 'retrieve_password',
'retrieve_username',
# 'register' # register complain about : client_side=self.settings.client_side
]:
html_form = getattr(self.auth, f)().xml()
self.assertTrue('name="_formkey"' in html_form)
# NOTE: Not sure it is the proper way to logout_bare() as there is not methods for that and auth.logout() failed
self.auth.logout_bare()
# self.assertTrue(self.auth.is_logged_in())
for f in ['logout', 'verify_email', 'reset_password',
'change_password', 'profile', 'groups']:
self.assertRaisesRegexp(HTTP, "303*", getattr(self.auth, f))
self.assertRaisesRegexp(HTTP, "401*", self.auth.impersonate)
try:
for t in ['t0_archive', 't0', 'auth_cas', 'auth_event',
'auth_membership', 'auth_permission', 'auth_group',
'auth_user']:
self.db[t].drop()
except SyntaxError as e:
# GAE doesn't support drop
pass
return
def test_get_or_create_user(self):
self.db.auth_user.insert(email='user1@test.com', username='user1', password='password_123')
self.db.commit()
# True case
self.assertEqual(self.auth.get_or_create_user({'email': 'user1@test.com',
'username': 'user1',
'password': 'password_123'
})['username'], 'user1')
# user2 doesn't exist yet and get created
self.assertEqual(self.auth.get_or_create_user({'email': 'user2@test.com',
'username': 'user2'})['username'], 'user2')
# user3 for corner case
self.assertEqual(self.auth.get_or_create_user({'first_name': 'Omer',
'last_name': 'Simpson',
'email': 'user3@test.com',
'registration_id': 'user3',
'username': 'user3'})['username'], 'user3')
# False case
self.assertEqual(self.auth.get_or_create_user({'email': ''}), None)
self.db.auth_user.truncate()
self.db.commit()
def test_login_bare(self):
# The following test case should succeed but failed as I never received the user record but False
self.auth.login_bare(username='bart@simpson.com', password='bart_password')
self.assertTrue(self.auth.is_logged_in())
# Failing login because bad_password
self.assertEqual(self.auth.login_bare(username='bart', password='wrong_password'), False)
self.db.auth_user.truncate()
def test_register_bare(self):
# corner case empty register call register_bare without args
self.assertRaises(ValueError, self.auth.register_bare)
# failing register_bare user already exist
self.assertEqual(self.auth.register_bare(username='bart', password='wrong_password'), False)
# successful register_bare
self.assertEqual(self.auth.register_bare(username='user2',
email='user2@test.com',
password='password_123')['username'], 'user2')
# raise ValueError
self.assertRaises(ValueError, self.auth.register_bare,
**dict(wrong_field_name='user3', password='password_123'))
# raise ValueError wrong email
self.assertRaises(ValueError, self.auth.register_bare,
**dict(email='user4@', password='password_123'))
self.db.auth_user.truncate()
self.db.commit()
def test_bulk_register(self):
self.auth.login_bare(username='bart', password='bart_password')
self.auth.settings.bulk_register_enabled = True
bulk_register_form = self.auth.bulk_register(max_emails=10).xml()
self.assertTrue('name="_formkey"' in bulk_register_form)
def test_change_password(self):
self.auth.login_bare(username='bart', password='bart_password')
change_password_form = getattr(self.auth, 'change_password')().xml()
self.assertTrue('name="_formkey"' in change_password_form)
def test_profile(self):
self.auth.login_bare(username='bart', password='bart_password')
profile_form = getattr(self.auth, 'profile')().xml()
self.assertTrue('name="_formkey"' in profile_form)
# def test_impersonate(self):
# # Create a user to be impersonated
# self.auth.get_or_create_user(dict(first_name='Omer',
# last_name='Simpson',
# username='omer',
# email='omer@test.com',
# password='password_omer',
# registration_key='',
# registration_id=''))
# # Create impersonate group, assign bart to impersonate group and add impersonate permission over auth_user
# self.auth.add_group('impersonate')
# self.auth.add_membership(user_id=1,
# group_id=self.db(self.db.auth_user.username == 'bart'
# ).select(self.db.auth_user.id).first().id)
# self.auth.add_permission(group_id=self.db(self.db.auth_group.role == 'impersonate'
# ).select(self.db.auth_group.id).first().id,
# name='impersonate',
# table_name='auth_user',
# record_id=0)
# # Bart login
# self.auth.login_bare(username='bart', password='bart_password')
# self.assertTrue(self.auth.is_logged_in())
# # Bart impersonate Omer
# omer_id = self.db(self.db.auth_user.username == 'omer').select(self.db.auth_user.id).first().id
# impersonate_form = self.auth.impersonate(user_id=omer_id)
# self.assertTrue(self.auth.is_impersonating())
# self.assertEqual(impersonate_form, 'test')
# def test_impersonate(self):
# request = Request(env={})
# request.application = 'a'
# request.controller = 'c'
# request.function = 'f'
# request.folder = 'applications/admin'
# response = Response()
# session = Session()
# T = translator('', 'en')
# session.connect(request, response)
# from gluon.globals import current
# current.request = request
# current.response = response
# current.session = session
# current.T = T
# db = DAL(DEFAULT_URI, check_reserved=['all'])
# auth = Auth(db)
# auth.define_tables(username=True, signature=False)
# db.define_table('t0', Field('tt'), auth.signature)
# auth.enable_record_versioning(db)
# # Create a user
# auth.get_or_create_user(dict(first_name='Bart',
# last_name='Simpson',
# username='bart',
# email='bart@simpson.com',
# password='bart_password',
# registration_key='bart',
# registration_id=''
# ))
# # Create a user to be impersonated
# auth.get_or_create_user(dict(first_name='Omer',
# last_name='Simpson',
# username='omer',
# email='omer@test.com',
# password='password_omer',
# registration_key='',
# registration_id=''))
# # Create impersonate group, assign bart to impersonate group and add impersonate permission over auth_user
# auth.add_group('impersonate')
# auth.add_membership(user_id=1,
# group_id=db(db.auth_user.username == 'bart'
# ).select(db.auth_user.id).first().id)
# auth.add_permission(group_id=db(db.auth_group.role == 'impersonate'
# ).select(db.auth_group.id).first().id,
# name='impersonate',
# table_name='auth_user',
# record_id=0)
# # Bart login
# auth.login_bare(username='bart', password='bart_password')
# # Bart impersonate Omer
# omer_id = db(db.auth_user.username == 'omer').select(db.auth_user.id).first().id
# impersonate_form = auth.impersonate(user_id=omer_id)
# self.assertTrue(auth.is_impersonating())
# self.assertEqual(impersonate_form, 'test')
# TODO: class TestCrud(unittest.TestCase):
# It deprecated so far from a priority
# TODO: class TestService(unittest.TestCase):
# TODO: class TestPluginManager(unittest.TestCase):
# TODO: class TestExpose(unittest.TestCase):
# TODO: class TestWiki(unittest.TestCase):
# TODO: class TestConfig(unittest.TestCase):
# TODO: class TestToolsFunctions(unittest.TestCase):
# For all the tools.py functions
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
+38
View File
@@ -160,6 +160,14 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ({u'a': 100}, None)) self.assertEqual(rtn, ({u'a': 100}, None))
rtn = IS_JSON()('spam1234') rtn = IS_JSON()('spam1234')
self.assertEqual(rtn, ('spam1234', 'Invalid json')) self.assertEqual(rtn, ('spam1234', 'Invalid json'))
rtn = IS_JSON(native_json=True)('{"a": 100}')
self.assertEqual(rtn, ('{"a": 100}', None))
rtn = IS_JSON().formatter(None)
self.assertEqual(rtn, None)
rtn = IS_JSON().formatter({'a': 100})
self.assertEqual(rtn, '{"a": 100}')
rtn = IS_JSON(native_json=True).formatter({'a': 100})
self.assertEqual(rtn, {'a': 100})
def test_IS_IN_SET(self): def test_IS_IN_SET(self):
rtn = IS_IN_SET(['max', 'john'])('max') rtn = IS_IN_SET(['max', 'john'])('max')
@@ -174,6 +182,14 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ('id1', None)) self.assertEqual(rtn, ('id1', None))
rtn = IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1') rtn = IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1')
self.assertEqual(rtn, ('id1', None)) self.assertEqual(rtn, ('id1', None))
rtn = IS_IN_SET(['id1', 'id2'], error_message='oops', multiple=True)(None)
self.assertEqual(rtn, ([], None))
rtn = IS_IN_SET(['id1', 'id2'], error_message='oops', multiple=True)('')
self.assertEqual(rtn, ([], None))
rtn = IS_IN_SET(['id1', 'id2'], error_message='oops', multiple=True)('id1')
self.assertEqual(rtn, (['id1'], None))
rtn = IS_IN_SET(['id1', 'id2'], error_message='oops', multiple=(1,2))(None)
self.assertEqual(rtn, ([], 'oops'))
import itertools import itertools
rtn = IS_IN_SET(itertools.chain(['1','3','5'],['2','4','6']))('1') rtn = IS_IN_SET(itertools.chain(['1','3','5'],['2','4','6']))('1')
self.assertEqual(rtn, ('1', None)) self.assertEqual(rtn, ('1', None))
@@ -181,6 +197,10 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ('id1', None)) self.assertEqual(rtn, ('id1', None))
rtn = IS_IN_SET([('id1','first label'), ('id2','second label')]).options(zero=False) rtn = IS_IN_SET([('id1','first label'), ('id2','second label')]).options(zero=False)
self.assertEqual(rtn, [('id1', 'first label'), ('id2', 'second label')]) self.assertEqual(rtn, [('id1', 'first label'), ('id2', 'second label')])
rtn = IS_IN_SET(['id1', 'id2']).options(zero=False)
self.assertEqual(rtn, [('id1', 'id1'), ('id2', 'id2')])
rtn = IS_IN_SET(['id2', 'id1'], sort=True).options(zero=False)
self.assertEqual(rtn, [('id1', 'id1'), ('id2', 'id2')])
def test_IS_IN_DB(self): def test_IS_IN_DB(self):
from gluon.dal import DAL, Field from gluon.dal import DAL, Field
@@ -190,6 +210,10 @@ class TestValidators(unittest.TestCase):
costanza_id = db.person.insert(name='costanza') costanza_id = db.person.insert(name='costanza')
rtn = IS_IN_DB(db, 'person.id', '%(name)s')(george_id) rtn = IS_IN_DB(db, 'person.id', '%(name)s')(george_id)
self.assertEqual(rtn, (george_id, None)) self.assertEqual(rtn, (george_id, None))
rtn = IS_IN_DB(db, db.person, '%(name)s')(george_id)
self.assertEqual(rtn, (george_id, None))
rtn = IS_IN_DB(db(db.person.id > 0), db.person, '%(name)s')(george_id)
self.assertEqual(rtn, (george_id, None))
rtn = IS_IN_DB(db, 'person.id', '%(name)s', error_message='oops')(george_id+costanza_id) rtn = IS_IN_DB(db, 'person.id', '%(name)s', error_message='oops')(george_id+costanza_id)
self.assertEqual(rtn, (george_id+costanza_id, 'oops')) self.assertEqual(rtn, (george_id+costanza_id, 'oops'))
rtn = IS_IN_DB(db, db.person.id, '%(name)s')(george_id) rtn = IS_IN_DB(db, db.person.id, '%(name)s')(george_id)
@@ -200,8 +224,14 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ([george_id,costanza_id], None)) self.assertEqual(rtn, ([george_id,costanza_id], None))
rtn = IS_IN_DB(db, 'person.id', '%(name)s', multiple=True, delimiter=',')('%d,%d' % (george_id, costanza_id)) rtn = IS_IN_DB(db, 'person.id', '%(name)s', multiple=True, delimiter=',')('%d,%d' % (george_id, costanza_id))
self.assertEqual(rtn, ( ('%d,%d' % (george_id, costanza_id)).split(','), None)) self.assertEqual(rtn, ( ('%d,%d' % (george_id, costanza_id)).split(','), None))
rtn = IS_IN_DB(db, 'person.id', '%(name)s', multiple=(1,3), delimiter=',')('%d,%d' % (george_id, costanza_id))
self.assertEqual(rtn, ( ('%d,%d' % (george_id, costanza_id)).split(','), None))
rtn = IS_IN_DB(db, 'person.id', '%(name)s', multiple=(1,2), delimiter=',', error_message='oops')('%d,%d' % (george_id, costanza_id))
self.assertEqual(rtn, ( ('%d,%d' % (george_id, costanza_id)).split(','), 'oops'))
rtn = IS_IN_DB(db, db.person.id, '%(name)s', error_message='oops').options(zero=False) rtn = IS_IN_DB(db, db.person.id, '%(name)s', error_message='oops').options(zero=False)
self.assertEqual(sorted(rtn), [('%d' % george_id, 'george'), ('%d' % costanza_id, 'costanza')]) self.assertEqual(sorted(rtn), [('%d' % george_id, 'george'), ('%d' % costanza_id, 'costanza')])
rtn = IS_IN_DB(db, db.person.id, db.person.name, error_message='oops', sort=True).options(zero=True)
self.assertEqual(rtn, [('', ''), ('%d' % costanza_id, 'costanza'), ('%d' % george_id, 'george')])
db.person.drop() db.person.drop()
def test_IS_NOT_IN_DB(self): def test_IS_NOT_IN_DB(self):
@@ -211,8 +241,14 @@ class TestValidators(unittest.TestCase):
db.person.insert(name='george') db.person.insert(name='george')
rtn = IS_NOT_IN_DB(db, 'person.name', error_message='oops')('george') rtn = IS_NOT_IN_DB(db, 'person.name', error_message='oops')('george')
self.assertEqual(rtn, ('george', 'oops')) self.assertEqual(rtn, ('george', 'oops'))
rtn = IS_NOT_IN_DB(db, 'person.name', error_message='oops', allowed_override=['george'])('george')
self.assertEqual(rtn, ('george', None))
rtn = IS_NOT_IN_DB(db, 'person.name', error_message='oops')(' ')
self.assertEqual(rtn, (' ', 'oops'))
rtn = IS_NOT_IN_DB(db, 'person.name')('jerry') rtn = IS_NOT_IN_DB(db, 'person.name')('jerry')
self.assertEqual(rtn, ('jerry', None)) self.assertEqual(rtn, ('jerry', None))
rtn = IS_NOT_IN_DB(db, 'person.name')(u'jerry')
self.assertEqual(rtn, ('jerry', None))
db.person.drop() db.person.drop()
def test_IS_INT_IN_RANGE(self): def test_IS_INT_IN_RANGE(self):
@@ -668,6 +704,8 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ('a@b.co', None)) self.assertEqual(rtn, ('a@b.co', None))
rtn = ANY_OF([IS_DATE(),IS_EMAIL()])('1982-12-14') rtn = ANY_OF([IS_DATE(),IS_EMAIL()])('1982-12-14')
self.assertEqual(rtn, (datetime.date(1982, 12, 14), None)) self.assertEqual(rtn, (datetime.date(1982, 12, 14), None))
rtn = ANY_OF([IS_DATE(format='%m/%d/%Y'),IS_EMAIL()]).formatter(datetime.date(1834, 12, 14))
self.assertEqual(rtn, '12/14/1834')
def test_IS_EMPTY_OR(self): def test_IS_EMPTY_OR(self):
rtn = IS_EMPTY_OR(IS_EMAIL())('abc@def.com') rtn = IS_EMPTY_OR(IS_EMAIL())('abc@def.com')
+18 -10
View File
@@ -820,12 +820,12 @@ class Recaptcha(DIV):
Examples: Examples:
Use as:: Use as::
form = FORM(Recaptcha(public_key='...',private_key='...')) form = FORM(Recaptcha(public_key='...', private_key='...'))
or:: or::
form = SQLFORM(...) form = SQLFORM(...)
form.append(Recaptcha(public_key='...',private_key='...')) form.append(Recaptcha(public_key='...', private_key='...'))
""" """
@@ -984,17 +984,17 @@ class Recaptcha2(DIV):
Examples: Examples:
Use as:: Use as::
form = FORM(Recaptcha2(public_key='...',private_key='...')) form = FORM(Recaptcha2(public_key='...', private_key='...'))
or:: or::
form = SQLFORM(...) form = SQLFORM(...)
form.append(Recaptcha2(public_key='...',private_key='...')) form.append(Recaptcha2(public_key='...', private_key='...'))
to protect the login page instead, use:: to protect the login page instead, use::
from gluon.tools import Recaptcha2 from gluon.tools import Recaptcha2
auth.settings.captcha = Recaptcha2(request, public_key='...',private_key='...') auth.settings.captcha = Recaptcha2(request, public_key='...', private_key='...')
""" """
@@ -1735,6 +1735,7 @@ class Auth(object):
host = host_names[0] host = host_names[0]
else: else:
host = 'localhost' host = 'localhost'
return host
def __init__(self, environment=None, db=None, mailer=True, def __init__(self, environment=None, db=None, mailer=True,
hmac_key=None, controller='default', function='user', hmac_key=None, controller='default', function='user',
@@ -2575,7 +2576,7 @@ class Auth(object):
update_keys[key] = keys[key] update_keys[key] = keys[key]
user.update_record(**update_keys) user.update_record(**update_keys)
elif checks: elif checks:
if not 'first_name' in keys and 'first_name' in table_user.fields: if 'first_name' not in keys and 'first_name' in table_user.fields:
guess = keys.get('email', 'anonymous').split('@')[0] guess = keys.get('email', 'anonymous').split('@')[0]
keys['first_name'] = keys.get('username', guess) keys['first_name'] = keys.get('username', guess)
vars = table_user._filter_fields(keys) vars = table_user._filter_fields(keys)
@@ -2698,8 +2699,7 @@ class Auth(object):
fields[settings.passfield] = \ fields[settings.passfield] = \
settings.table_user[settings.passfield].validate(fields[settings.passfield])[0] settings.table_user[settings.passfield].validate(fields[settings.passfield])[0]
if not fields.get(settings.userfield): if not fields.get(settings.userfield):
raise ValueError('register_bare: ' + raise ValueError('register_bare: userfield not provided or invalid')
'userfield not provided or invalid')
user = self.get_or_create_user(fields, login=False, get=False, user = self.get_or_create_user(fields, login=False, get=False,
update_fields=self.settings.update_fields) update_fields=self.settings.update_fields)
if not user: if not user:
@@ -3251,12 +3251,16 @@ class Auth(object):
next = cas.logout_url(next) next = cas.logout_url(next)
current.session.auth = None current.session.auth = None
self.user = None
if self.settings.renew_session_onlogout: if self.settings.renew_session_onlogout:
current.session.renew(clear_session=not self.settings.keep_session_onlogout) current.session.renew(clear_session=not self.settings.keep_session_onlogout)
current.session.flash = self.messages.logged_out current.session.flash = self.messages.logged_out
if next is not None: if next is not None:
redirect(next) redirect(next)
def logout_bare(self):
self.logout(next=None, onlogout=None, log=None)
def register(self, def register(self,
next=DEFAULT, next=DEFAULT,
onvalidation=DEFAULT, onvalidation=DEFAULT,
@@ -4124,6 +4128,7 @@ class Auth(object):
raise HTTP(401, "Not Authorized") raise HTTP(401, "Not Authorized")
current_id = auth.user.id current_id = auth.user.id
requested_id = user_id requested_id = user_id
user = None
if user_id is DEFAULT: if user_id is DEFAULT:
user_id = current.request.post_vars.user_id user_id = current.request.post_vars.user_id
if user_id and user_id != self.user.id and user_id != '0': if user_id and user_id != self.user.id and user_id != '0':
@@ -4152,7 +4157,10 @@ class Auth(object):
return None return None
if requested_id is DEFAULT and not request.post_vars: if requested_id is DEFAULT and not request.post_vars:
return SQLFORM.factory(Field('user_id', 'integer')) return SQLFORM.factory(Field('user_id', 'integer'))
return SQLFORM(table_user, user.id, readonly=True) elif not user:
return None
else:
return SQLFORM(table_user, user.id, readonly=True)
def update_groups(self): def update_groups(self):
if not self.user: if not self.user:
@@ -4504,7 +4512,7 @@ class Auth(object):
ignore_common_filters=True).select( ignore_common_filters=True).select(
limitby=(0, 1), orderby_on_limitby=False).first() limitby=(0, 1), orderby_on_limitby=False).first()
if record: if record:
if hasattr(record, 'is_active') and not record.is_ctive: if hasattr(record, 'is_active') and not record.is_active:
record.update_record(is_active=True) record.update_record(is_active=True)
id = record.id id = record.id
else: else:
+7 -4
View File
@@ -471,8 +471,6 @@ class IS_IN_SET(Validator):
thestrset = [str(x) for x in self.theset] thestrset = [str(x) for x in self.theset]
failures = [x for x in values if not str(x) in thestrset] failures = [x for x in values if not str(x) in thestrset]
if failures and self.theset: if failures and self.theset:
if self.multiple and (value is None or value == ''):
return ([], None)
return (value, translate(self.error_message)) return (value, translate(self.error_message))
if self.multiple: if self.multiple:
if isinstance(self.multiple, (tuple, list)) and \ if isinstance(self.multiple, (tuple, list)) and \
@@ -1200,7 +1198,12 @@ class IS_EMAIL(Validator):
self.error_message = error_message self.error_message = error_message
def __call__(self, value): def __call__(self, value):
match = self.regex.match(value) try:
match = self.regex.match(value)
except TypeError:
# Value may not be a string where we can look for matches.
# Example: we're calling ANY_OF formatter and IS_EMAIL is asked to validate a date.
match = None
if match: if match:
domain = value.split('@')[1] domain = value.split('@')[1]
if (not self.banned or not self.banned.match(domain)) \ if (not self.banned or not self.banned.match(domain)) \
@@ -2608,7 +2611,7 @@ class ANY_OF(Validator):
# Use the formatter of the first subvalidator # Use the formatter of the first subvalidator
# that validates the value and has a formatter # that validates the value and has a formatter
for validator in self.subs: for validator in self.subs:
if hasattr(validator, 'formatter') and validator(value)[1] != None: if hasattr(validator, 'formatter') and validator(value)[1] is None:
return validator.formatter(value) return validator.formatter(value)
+10 -11
View File
@@ -212,21 +212,20 @@ if [ "$nopassword" -eq 0 ]
then then
sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)" sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)"
fi fi
systemctl enable emperor.uwsgi.service
systemctl start emperor.uwsgi.service /etc/init.d/nginx start
/etc/init.d/nginx restart start uwsgi-emperor
echo <<EOF echo <<EOF
you can reload uwsgi with you can stop uwsgi and nginx with
systemctl restart emperor.uwsgi sudo /etc/init.d/nginx stop
sudo stop uwsgi-emperor
and start it with
and stop it with sudo /etc/init.d/nginx start
sudo start uwsgi-emperor
systemctl stop emperor.uwsgi
to reload web2py only (without restarting uwsgi)
touch /etc/uwsgi/web2py.ini
EOF EOF