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

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
- new Auth(…,host_names=[…]) to prevent host header injection
@@ -45,6 +44,7 @@
sessiondb = RedisSession(redis_conn=rconn, session_expiry=False)
session.connect(request, response, db = sessiondb)
Many thanks to Richard and Simone for their work and dedication.
## 2.13.*

View File

@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked"
src:
### 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
make clean
### clean up baisc apps

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

View File

@@ -10,7 +10,7 @@ session.forget()
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():
return response.render()
@@ -19,14 +19,13 @@ def index():
def what():
import urllib
try:
images = XML(urllib.urlopen(
'http://www.web2py.com/poweredby/default/images').read())
images = XML(urllib.urlopen('http://www.web2py.com/poweredby/default/images').read())
except:
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():
return response.render()
@@ -74,14 +73,15 @@ def license():
filename = os.path.join(request.env.gluon_parent, 'LICENSE')
return response.render(dict(license=MARKMIN(read_file(filename))))
def version():
if request.args(0)=='raw':
if request.args(0) == 'raw':
return request.env.web2py_version
from gluon.fileutils import parse_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' % (
a,b,c,build.year,build.month,build.day,
build.hour,build.minute,build.second,pre_release)
return 'Version %i.%i.%i (%.4i-%.2i-%.2i %.2i:%.2i:%.2i) %s' % \
(a, b, c, build.year, build.month, build.day, build.hour, build.minute, build.second, pre_release)
@cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def examples():

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
response.menu = [
(T('Home'), False, URL('default', 'index')),
(T('About'), False, URL('default', 'what')),
(T('Download'), False, URL('default', 'download')),
(T('Docs & Resources'), False, URL('default', 'documentation')),
(T('Support'), False, URL('default', 'support')),
(T('Contributors'), False, URL('default', 'who'))]
(T('Home'), request.controller == 'default' and request.function == 'index', URL('default', 'index')),
(T('About'), request.controller == 'default' and request.function == 'what', URL('default', 'what')),
(T('Download'), request.controller == 'default' and request.function == 'download', URL('default', 'download')),
(T('Docs & Resources'), request.controller == 'default' and request.function == 'documentation', URL('default', 'documentation')),
(T('Support'), request.controller == 'default' and request.function == 'support', URL('default', 'support')),
(T('Contributors'), request.controller == 'default' and request.function == 'who', URL('default', 'who'))]
#########################################################################
## Changes the menu active item

View File

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

View File

@@ -6,7 +6,7 @@
/*** basic styles ***/
*, *: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}
p, li {margin-bottom:0.5em}
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}
table {border-collapse:collapse}
tbody tr:hover {background-color:#fbf6d9}
td, th {padding:5px; vertical-align:top; text-align:left; border:0}
thead tr {background-color:#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}
tbody th {vertical-align:top}
header, footer {with:100%}
@media (max-width:599px) {
@media all and (max-width:599px) {
h1{font-size:2em}
h2{font-size:1.8em}
h3{font-size:1.6em}
@@ -41,20 +39,20 @@ header, footer {with:100%}
}
/*** 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.small, table .btn {padding:0.25em 0.5em !important; font-size:0.8em}
.btn.large {padding:1em 2em !important; font-size:1.2em}
.btn.small, table .btn {padding:0.25em 0.5em; font-size:0.8em}
.btn.large {padding:1em 2em; font-size:1.2em}
.btn.oval {border-radius:50%}
/*** helpers ***/
.rounded {-moz-border-radius:5px; border-radius:5px}
.padded {padding:10px 20px !important}
.center {text-align:center !important; margin-left:auto; margin-right:auto}
.padded {padding:10px 20px}
.center {text-align:center; margin-left:auto; margin-right:auto}
.center>div {text-align:left}
.right {right:0; text-align:right}
.middle div {vertical-align:middle !important}
.bottom div {vertical-align:bottom !important}
.middle div {vertical-align:middle}
.bottom div {vertical-align:bottom}
.xscroll {overflow-x:scroll}
.yscroll {overflow-y:scroll}
.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}
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:invalid, input.error {background:#cc1f00!important;color:white}
input:invalid, input.error {background:#cc1f00;color:white}
/*** grid ***/
.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>.fill {width:100%; margin-right:-20px}
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters {display:inline-block; padding: 0 20px 0 0; vertical-align:top}
.container>.fill{display: inline-block}
.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}
.quarter {width:25%; margin-right:-5px}
.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}
.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}
.half.compressible {width:50%; margin-right:-10px}
.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}
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%;}
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}
@@ -115,7 +114,7 @@ input:invalid, input.error {background:#cc1f00!important;color:white}
display:block;
width:120%;
background-color:#acece6;
border-radius:0 !important;
border-radius:0;
background-clip:padding-box;
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 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.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>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.right {float:left; text-align:left}
header .menu ul ul {top:2.5em; left:-1px}
}
@media (min-width:600px) {
@media all and (min-width:600px) {
.ham {display:none!important}
.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]:hover:after,[data-tooltip]:hover:before {display:block}
[data-tooltip]:hover:before {
border-bottom:.6em solid black;
border-bottom:.6em solid black;
border-bottom:.6em solid #111111;
border-bottom:.6em solid #111111;
border-left:7px solid transparent;
border-right:7px solid transparent;
content:"";
@@ -288,7 +287,7 @@ a:not(.btn):not(.noeffect):after {
content:attr(data-tooltip);
left:0;
top:2px;
margin-left:-20;
margin-left:-20px;
margin-top:1.5em;
padding:5px 15px;
white-space:pre-wrap;
@@ -339,9 +338,6 @@ a:not(.btn):not(.noeffect):after {
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 > span {
padding: 4px 9px;
@@ -350,10 +346,13 @@ a:not(.btn):not(.noeffect):after {
background-color: #26a69a;
border-radius: 5px;
font-size:12px;
margin: 5px 5px 5px 0 !important;
line-height: 32px;
margin: 2px 5px 2px 0;
display: inline-block;
}
.tags.dismissible > span:hover {opacity: 0.5}
.tags.dismissible > span:not(.off):after {content:" ✕"}
.tags > span.off {background-color: #ccc}
.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}

View File

@@ -17,7 +17,7 @@
<tbody>
<tr>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a>
@@ -69,9 +69,9 @@
<h3>Instructions</h3>
<p>After download, unzip it and click on web2py.exe (windows) or web2py.app (osx).
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>
{{=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>
@@ -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>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>
<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>
<center>

View File

@@ -3,22 +3,22 @@
<div class="container">
<div class="twothirds">
<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>
<table width="100%">
<tr>
<td>
<a href="http://web2py.com/book">
<a class="noeffect" href="http://web2py.com/book">
<img src="{{=URL('static','images/book-5th.png')}}" />
</a>
</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')}}" />
</a>
</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')}}" />
</a>
</td>
@@ -29,8 +29,8 @@
</div>
<div class="third">
<div class="padded center">
<a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23">
<img src="{{=URL('static','images/infoworld2012.jpeg')}}">
<a class="noeffect" href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23">
<img class="spaced-vertical" src="{{=URL('static','images/infoworld2012.jpeg')}}">
</a>
<a class="btn rounded red fill" href="{{=URL('download')}}">
Download Now

View File

@@ -32,7 +32,6 @@
<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.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.geekondemand.it/"> GeekOnDemand</a> (Italy)</li>
<li><a target="_blank" href="http://stifix.com"> Stifix</a> (Indonesia)</li>

View File

@@ -82,6 +82,7 @@
</li><li>Keith Yang (openid)
</li><li><a href="http://dev.s-cubism.com/web2py_plugins">Kenji Hosoda</a> (plugins)
</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="https://github.com/lucasdavila">Lucas D'Ávila</a>
</li><li>Marc Abramowitz (tests and travis continuous integration)

View File

@@ -1,22 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<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/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">
<style>
th, td {color: black!important}
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>
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
{{
left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
@@ -29,7 +24,7 @@
<header class="black padded">
<div class="container middle max900">
<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">
<input type="checkbox" id="menu"/>
{{=MENU(response.menu,_class='menu')}}

View File

@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations
#########################################################################
## This is a sample controller
## - index is the default action of any application
## - user is required for authentication and authorization
## - download is for downloading files uploaded in the db (does streaming)
#########################################################################
# -------------------------------------------------------------------------
# This is a sample controller
# - index is the default action of any application
# - user is required for authentication and authorization
# - download is for downloading files uploaded in the db (does streaming)
# -------------------------------------------------------------------------
def index():
"""

View File

@@ -1,60 +1,84 @@
# -*- coding: utf-8 -*-
#########################################################################
## This scaffolding model makes your app work on Google App Engine too
## File is released under public domain and you can use without limitations
#########################################################################
# -------------------------------------------------------------------------
# This scaffolding model makes your app work on Google App Engine too
# File is released under public domain and you can use without limitations
# -------------------------------------------------------------------------
if request.global_settings.web2py_version < "2.14.1":
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()
## app configuration made easy. Look inside private/appconfig.ini
# -------------------------------------------------------------------------
# app configuration made easy. Look inside private/appconfig.ini
# -------------------------------------------------------------------------
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)
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'),
pool_size = myconf.get('db.pool_size'),
migrate_enabled = myconf.get('db.migrate'),
check_reserved = ['all'])
# ---------------------------------------------------------------------
# if NOT running on Google App Engine use SQLite or other DB
# ---------------------------------------------------------------------
db = DAL(myconf.get('db.uri'),
pool_size=myconf.get('db.pool_size'),
migrate_enabled=myconf.get('db.migrate'),
check_reserved=['all'])
else:
## connect to Google BigTable (optional 'google:datastore://namespace')
# ---------------------------------------------------------------------
# connect to Google BigTable (optional 'google:datastore://namespace')
# ---------------------------------------------------------------------
db = DAL('google:datastore+ndb')
## store sessions and tickets there
# ---------------------------------------------------------------------
# store sessions and tickets there
# ---------------------------------------------------------------------
session.connect(request, response, db=db)
## or store session in Memcache, Redis, etc.
## from gluon.contrib.memdb import MEMDB
## from google.appengine.api.memcache import Client
## session.connect(request, response, db = MEMDB(Client()))
# ---------------------------------------------------------------------
# or store session in Memcache, Redis, etc.
# from gluon.contrib.memdb import MEMDB
# 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 []
## choose a style for forms
# -------------------------------------------------------------------------
# choose a style for forms
# -------------------------------------------------------------------------
response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
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_js = 'concat,minify,inline'
## (optional) static assets folder versioning
# -------------------------------------------------------------------------
# (optional) static assets folder versioning
# -------------------------------------------------------------------------
# response.static_version = '0.0.0'
#########################################################################
## Here is sample code if you need for
## - email capabilities
## - authentication (registration, login, logout, ... )
## - authorization (role based authorization)
## - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss)
## - old style crud actions
## (more options discussed in gluon/tools.py)
#########################################################################
# -------------------------------------------------------------------------
# Here is sample code if you need for
# - email capabilities
# - authentication (registration, login, logout, ... )
# - authorization (role based authorization)
# - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss)
# - old style crud actions
# (more options discussed in gluon/tools.py)
# -------------------------------------------------------------------------
from gluon.tools import Auth, Service, PluginManager
@@ -63,10 +87,14 @@ auth = Auth(db, host_names=myconf.get('host.names'))
service = Service()
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)
## configure email
# -------------------------------------------------------------------------
# configure email
# -------------------------------------------------------------------------
mail = auth.settings.mailer
mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server')
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.ssl = myconf.get('smtp.ssl') or False
## configure auth policy
# -------------------------------------------------------------------------
# configure auth policy
# -------------------------------------------------------------------------
auth.settings.registration_requires_verification = False
auth.settings.registration_requires_approval = False
auth.settings.reset_password_requires_verification = True
#########################################################################
## Define your tables below (or better in another model file) for example
##
## >>> db.define_table('mytable',Field('myfield','string'))
##
## Fields can be 'string','text','password','integer','double','boolean'
## 'date','time','datetime','blob','upload', 'reference TABLENAME'
## There is an implicit 'id integer autoincrement' field
## Consult manual for more options, validators, etc.
##
## More API examples for controllers:
##
## >>> db.mytable.insert(myfield='value')
## >>> rows=db(db.mytable.myfield=='value').select(db.mytable.ALL)
## >>> for row in rows: print row.id, row.myfield
#########################################################################
# -------------------------------------------------------------------------
# Define your tables below (or better in another model file) for example
#
# >>> db.define_table('mytable', Field('myfield', 'string'))
#
# Fields can be 'string','text','password','integer','double','boolean'
# 'date','time','datetime','blob','upload', 'reference TABLENAME'
# There is an implicit 'id integer autoincrement' field
# Consult manual for more options, validators, etc.
#
# More API examples for controllers:
#
# >>> db.mytable.insert(myfield='value')
# >>> rows = db(db.mytable.myfield == 'value').select(db.mytable.ALL)
# >>> 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)

View File

@@ -1,28 +1,32 @@
# -*- coding: utf-8 -*-
# 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;'),
_class="navbar-brand",_href="http://www.web2py.com/",
response.logo = A(B('web', SPAN(2), 'py'), XML('&trade;&nbsp;'),
_class="navbar-brand", _href="http://www.web2py.com/",
_id="web2py-logo")
response.title = request.application.replace('_',' ').title()
response.title = request.application.replace('_', ' ').title()
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.description = myconf.get('app.description')
response.meta.keywords = myconf.get('app.keywords')
response.meta.generator = myconf.get('app.generator')
## your http://google.com/analytics id
# ----------------------------------------------------------------------------------------------------------------------
# your http://google.com/analytics id
# ----------------------------------------------------------------------------------------------------------------------
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 = [
(T('Home'), False, URL('default', 'index'), [])
@@ -30,109 +34,118 @@ response.menu = [
DEVELOPMENT_MENU = True
#########################################################################
## provide shortcuts for development. remove in production
#########################################################################
# ----------------------------------------------------------------------------------------------------------------------
# provide shortcuts for development. remove in production
# ----------------------------------------------------------------------------------------------------------------------
def _():
# ------------------------------------------------------------------------------------------------------------------
# shortcuts
# ------------------------------------------------------------------------------------------------------------------
app = request.application
ctr = request.controller
# ------------------------------------------------------------------------------------------------------------------
# useful links to internal and external resources
# ------------------------------------------------------------------------------------------------------------------
response.menu += [
(T('My Sites'), False, URL('admin', 'default', 'site')),
(T('This App'), False, '#', [
(T('Design'), False, URL('admin', 'default', 'design/%s' % app)),
LI(_class="divider"),
(T('Controller'), False,
URL(
'admin', 'default', 'edit/%s/controllers/%s.py' % (app, ctr))),
(T('View'), False,
URL(
'admin', 'default', 'edit/%s/views/%s' % (app, response.view))),
(T('DB Model'), False,
URL(
'admin', 'default', 'edit/%s/models/db.py' % app)),
(T('Menu Model'), False,
URL(
'admin', 'default', 'edit/%s/models/menu.py' % app)),
(T('Config.ini'), False,
URL(
'admin', 'default', 'edit/%s/private/appconfig.ini' % app)),
(T('Layout'), False,
URL(
'admin', 'default', 'edit/%s/views/layout.html' % app)),
(T('Stylesheet'), False,
URL(
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % app)),
(T('Database'), False, URL(app, 'appadmin', 'index')),
(T('Errors'), False, URL(
'admin', 'default', 'errors/' + app)),
(T('About'), False, URL(
'admin', 'default', 'about/' + app)),
]),
('web2py.com', False, '#', [
(T('Download'), False,
'http://www.web2py.com/examples/default/download'),
(T('Support'), False,
'http://www.web2py.com/examples/default/support'),
(T('Demo'), False, 'http://web2py.com/demo_admin'),
(T('Quick Examples'), False,
'http://web2py.com/examples/default/examples'),
(T('FAQ'), False, 'http://web2py.com/AlterEgo'),
(T('Videos'), False,
'http://www.web2py.com/examples/default/videos/'),
(T('Free Applications'),
False, 'http://web2py.com/appliances'),
(T('Plugins'), False, 'http://web2py.com/plugins'),
(T('Recipes'), False, 'http://web2pyslices.com/'),
]),
(T('Documentation'), False, '#', [
(T('Online book'), False, 'http://www.web2py.com/book'),
LI(_class="divider"),
(T('Preface'), False,
'http://www.web2py.com/book/default/chapter/00'),
(T('Introduction'), False,
'http://www.web2py.com/book/default/chapter/01'),
(T('Python'), False,
'http://www.web2py.com/book/default/chapter/02'),
(T('Overview'), False,
'http://www.web2py.com/book/default/chapter/03'),
(T('The Core'), False,
'http://www.web2py.com/book/default/chapter/04'),
(T('The Views'), False,
'http://www.web2py.com/book/default/chapter/05'),
(T('Database'), False,
'http://www.web2py.com/book/default/chapter/06'),
(T('Forms and Validators'), False,
'http://www.web2py.com/book/default/chapter/07'),
(T('Email and SMS'), False,
'http://www.web2py.com/book/default/chapter/08'),
(T('Access Control'), False,
'http://www.web2py.com/book/default/chapter/09'),
(T('Services'), False,
'http://www.web2py.com/book/default/chapter/10'),
(T('Ajax Recipes'), False,
'http://www.web2py.com/book/default/chapter/11'),
(T('Components and Plugins'), False,
'http://www.web2py.com/book/default/chapter/12'),
(T('Deployment Recipes'), False,
'http://www.web2py.com/book/default/chapter/13'),
(T('Other Recipes'), False,
'http://www.web2py.com/book/default/chapter/14'),
(T('Helping web2py'), False,
'http://www.web2py.com/book/default/chapter/15'),
(T("Buy web2py's book"), False,
'http://stores.lulu.com/web2py'),
]),
(T('Community'), False, None, [
(T('Groups'), False,
'http://www.web2py.com/examples/default/usergroups'),
(T('Twitter'), False, 'http://twitter.com/web2py'),
(T('Live Chat'), False,
'http://webchat.freenode.net/?channels=web2py'),
]),
]
if DEVELOPMENT_MENU: _()
(T('This App'), False, '#', [
(T('Design'), False, URL('admin', 'default', 'design/%s' % app)),
LI(_class="divider"),
(T('Controller'), False,
URL(
'admin', 'default', 'edit/%s/controllers/%s.py' % (app, ctr))),
(T('View'), False,
URL(
'admin', 'default', 'edit/%s/views/%s' % (app, response.view))),
(T('DB Model'), False,
URL(
'admin', 'default', 'edit/%s/models/db.py' % app)),
(T('Menu Model'), False,
URL(
'admin', 'default', 'edit/%s/models/menu.py' % app)),
(T('Config.ini'), False,
URL(
'admin', 'default', 'edit/%s/private/appconfig.ini' % app)),
(T('Layout'), False,
URL(
'admin', 'default', 'edit/%s/views/layout.html' % app)),
(T('Stylesheet'), False,
URL(
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % app)),
(T('Database'), False, URL(app, 'appadmin', 'index')),
(T('Errors'), False, URL(
'admin', 'default', 'errors/' + app)),
(T('About'), False, URL(
'admin', 'default', 'about/' + app)),
]),
('web2py.com', False, '#', [
(T('Download'), False,
'http://www.web2py.com/examples/default/download'),
(T('Support'), False,
'http://www.web2py.com/examples/default/support'),
(T('Demo'), False, 'http://web2py.com/demo_admin'),
(T('Quick Examples'), False,
'http://web2py.com/examples/default/examples'),
(T('FAQ'), False, 'http://web2py.com/AlterEgo'),
(T('Videos'), False,
'http://www.web2py.com/examples/default/videos/'),
(T('Free Applications'),
False, 'http://web2py.com/appliances'),
(T('Plugins'), False, 'http://web2py.com/plugins'),
(T('Recipes'), False, 'http://web2pyslices.com/'),
]),
(T('Documentation'), False, '#', [
(T('Online book'), False, 'http://www.web2py.com/book'),
LI(_class="divider"),
(T('Preface'), False,
'http://www.web2py.com/book/default/chapter/00'),
(T('Introduction'), False,
'http://www.web2py.com/book/default/chapter/01'),
(T('Python'), False,
'http://www.web2py.com/book/default/chapter/02'),
(T('Overview'), False,
'http://www.web2py.com/book/default/chapter/03'),
(T('The Core'), False,
'http://www.web2py.com/book/default/chapter/04'),
(T('The Views'), False,
'http://www.web2py.com/book/default/chapter/05'),
(T('Database'), False,
'http://www.web2py.com/book/default/chapter/06'),
(T('Forms and Validators'), False,
'http://www.web2py.com/book/default/chapter/07'),
(T('Email and SMS'), False,
'http://www.web2py.com/book/default/chapter/08'),
(T('Access Control'), False,
'http://www.web2py.com/book/default/chapter/09'),
(T('Services'), False,
'http://www.web2py.com/book/default/chapter/10'),
(T('Ajax Recipes'), False,
'http://www.web2py.com/book/default/chapter/11'),
(T('Components and Plugins'), False,
'http://www.web2py.com/book/default/chapter/12'),
(T('Deployment Recipes'), False,
'http://www.web2py.com/book/default/chapter/13'),
(T('Other Recipes'), False,
'http://www.web2py.com/book/default/chapter/14'),
(T('Helping web2py'), False,
'http://www.web2py.com/book/default/chapter/15'),
(T("Buy web2py's book"), False,
'http://stores.lulu.com/web2py'),
]),
(T('Community'), False, None, [
(T('Groups'), False,
'http://www.web2py.com/examples/default/usergroups'),
(T('Twitter'), False, 'http://twitter.com/web2py'),
(T('Live Chat'), False,
'http://webchat.freenode.net/?channels=web2py'),
]),
]
if "auth" in locals(): auth.wikimenu()
if DEVELOPMENT_MENU:
_()
if "auth" in locals():
auth.wikimenu()

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------------------------------------------------
# This is an app-specific example router
#
# This simple router is used for setting languages from app/languages directory
@@ -8,31 +9,33 @@
# a default_language
#
# See <web2py-root-dir>/examples/routes.parametric.example.py for parameter's detail
#-------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------------------
# To enable this route file you must do the steps:
#
# 1. rename <web2py-root-dir>/examples/routes.parametric.example.py to routes.py
# 2. rename this APP/routes.example.py to APP/routes.py
# (where APP - is your application directory)
# 3. restart web2py (or reload routes in web2py admin interfase)
# 2. rename this APP/routes.example.py to APP/routes.py (where APP - is your application directory)
# 3. restart web2py (or reload routes in web2py admin interface)
#
# YOU CAN COPY THIS FILE TO ANY APPLICATION'S ROOT DIRECTORY WITHOUT CHANGES!
# ----------------------------------------------------------------------------------------------------------------------
from fileutils import abspath
from languages import read_possible_languages
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 = {
app: dict(
default_language = possible_languages['default'][0],
languages = [lang for lang in possible_languages
if lang != 'default']
default_language=possible_languages['default'][0],
languages=[lang for lang in possible_languages 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)

View File

@@ -44,9 +44,9 @@ except ImportError:
have_settings = False
try:
import cPickle as pickle
import cPickle as pickle
except:
import pickle
import pickle
try:
import psutil
@@ -54,6 +54,7 @@ try:
except ImportError:
HAVE_PSUTIL = False
def remove_oldest_entries(storage, percentage=90):
# compute current memory usage (%)
old_mem = psutil.virtual_memory().percent
@@ -66,7 +67,8 @@ def remove_oldest_entries(storage, percentage=90):
# comute used memory again
new_mem = psutil.virtual_memory().percent
# 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
old_mem = new_mem
@@ -78,6 +80,7 @@ __all__ = ['Cache', 'lazy_cache']
DEFAULT_TIME_EXPIRE = 300
class CacheAbstract(object):
"""
Abstract class for cache implementations.
@@ -99,7 +102,7 @@ class CacheAbstract(object):
"""
cache_stats_name = 'web2py_cache_statistics'
max_ram_utilization = None # percent
max_ram_utilization = None # percent
def __init__(self, request=None):
"""Initializes the object
@@ -182,13 +185,14 @@ class CacheInRam(CacheAbstract):
self.request = request
self.storage = OrderedDict() if HAVE_PSUTIL else {}
self.app = request.application if request else ''
def initialize(self):
if self.initialized:
return
else:
self.initialized = True
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] = \
OrderedDict() if HAVE_PSUTIL else {}
self.stats[self.app] = {'hit_total': 0, 'misses': 0}
@@ -205,7 +209,7 @@ class CacheInRam(CacheAbstract):
else:
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.locker.release()
@@ -251,8 +255,8 @@ class CacheInRam(CacheAbstract):
self.locker.acquire()
self.storage[key] = (now, value)
self.stats[self.app]['misses'] += 1
if HAVE_PSUTIL and self.max_ram_utilization!=None and random.random()<0.10:
remove_oldest_entries(self.storage, percentage = self.max_ram_utilization)
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)
self.locker.release()
return value
@@ -292,14 +296,15 @@ class CacheOnDisk(CacheAbstract):
self.folder = folder
self.key_filter_in = 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
self.file_locks = defaultdict(thread.allocate_lock)
# Make sure we use valid filenames.
if sys.platform == "win32":
import base64
def key_filter_in_windows(key):
"""
Windows doesn't allow \ / : * ? "< > | in filenames.
@@ -316,7 +321,6 @@ class CacheOnDisk(CacheAbstract):
self.key_filter_in = key_filter_in_windows
self.key_filter_out = key_filter_out_windows
def wait_portalock(self, val_file):
"""
Wait for the process file lock.
@@ -328,15 +332,12 @@ class CacheOnDisk(CacheAbstract):
except:
time.sleep(self.file_lock_time_wait)
def acquire(self, key):
self.file_locks[key].acquire()
def release(self, key):
self.file_locks[key].release()
def __setitem__(self, key, value):
key = self.key_filter_in(key)
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)
val_file.close()
def __getitem__(self, key):
key = self.key_filter_in(key)
try:
@@ -357,12 +357,10 @@ class CacheOnDisk(CacheAbstract):
val_file.close()
return value
def __contains__(self, key):
key = self.key_filter_in(key)
return (key in self.file_locks) or recfile.exists(key, path=self.folder)
def __delitem__(self, key):
key = self.key_filter_in(key)
try:
@@ -370,13 +368,11 @@ class CacheOnDisk(CacheAbstract):
except IOError:
raise KeyError
def __iter__(self):
for dirpath, dirnames, filenames in os.walk(self.folder):
for filename in filenames:
yield self.key_filter_out(filename)
def safe_apply(self, key, function, default_value=None):
"""
Safely apply a function to the value of a key in storage and set
@@ -403,25 +399,21 @@ class CacheOnDisk(CacheAbstract):
val_file.close()
return new_value
def keys(self):
return list(self.__iter__())
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def __init__(self, request=None, folder=None):
self.initialized = False
self.request = request
self.folder = folder
self.storage = None
def initialize(self):
if self.initialized:
return
@@ -440,7 +432,6 @@ class CacheOnDisk(CacheAbstract):
self.storage = CacheOnDisk.PersistentStorage(folder)
def __call__(self, key, f,
time_expire=DEFAULT_TIME_EXPIRE):
self.initialize()
@@ -487,7 +478,6 @@ class CacheOnDisk(CacheAbstract):
self.storage.release(key)
return value
def clear(self, regex=None):
self.initialize()
storage = self.storage
@@ -504,7 +494,6 @@ class CacheOnDisk(CacheAbstract):
pass
storage.release(key)
def increment(self, key, value=1):
self.initialize()
self.storage.acquire(key)
@@ -513,7 +502,6 @@ class CacheOnDisk(CacheAbstract):
return value
class CacheAction(object):
def __init__(self, func, key, time_expire, cache, cache_model):
self.__name__ = func.__name__
@@ -572,9 +560,9 @@ class Cache(object):
logger.warning('no cache.disk (AttributeError)')
def action(self, time_expire=DEFAULT_TIME_EXPIRE, cache_model=None,
prefix=None, session=False, vars=True, lang=True,
user_agent=False, public=True, valid_statuses=None,
quick=None):
prefix=None, session=False, vars=True, lang=True,
user_agent=False, public=True, valid_statuses=None,
quick=None):
"""Better fit for caching an action
Warning:
@@ -602,6 +590,7 @@ class Cache(object):
"""
from gluon import current
from gluon.http import HTTP
def wrap(func):
def wrapped_f():
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)
if not session_ and 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:
cache_control += ', private'
expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
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]
if session_:
cache_key.append(current.response.session_id)
@@ -644,28 +634,28 @@ class Cache(object):
if prefix:
cache_key = prefix + cache_key
try:
#action returns something
rtn = cache_model(cache_key, lambda : func(), time_expire=time_expire)
# action returns something
rtn = cache_model(cache_key, lambda: func(), time_expire=time_expire)
http, status = None, current.response.status
except HTTP, e:
#action raises HTTP (can still be valid)
rtn = cache_model(cache_key, lambda : e.body, time_expire=time_expire)
# action raises HTTP (can still be valid)
rtn = cache_model(cache_key, lambda: e.body, time_expire=time_expire)
http, status = HTTP(e.status, rtn, **e.headers), e.status
else:
#action raised a generic exception
# action raised a generic exception
http = None
else:
#no server-cache side involved
# no server-cache side involved
try:
#action returns something
# action returns something
rtn = func()
http, status = None, current.response.status
except HTTP, e:
#action raises HTTP (can still be valid)
# action raises HTTP (can still be valid)
status = e.status
http = HTTP(e.status, e.body, **e.headers)
else:
#action raised a generic exception
# action raised a generic exception
http = None
send_headers = False
if http and isinstance(valid_statuses, list):
@@ -675,15 +665,13 @@ class Cache(object):
if str(status)[0] in '123':
send_headers = True
if send_headers:
headers = {
'Pragma' : None,
'Expires' : expires,
'Cache-Control' : cache_control
}
headers = {'Pragma': None,
'Expires': expires,
'Cache-Control': cache_control}
current.response.headers.update(headers)
if cache_model and not send_headers:
#we cached already the value, but the status is not valid
#so we need to delete the cached value
# we cached already the value, but the status is not valid
# so we need to delete the cached value
cache_model(cache_key, None)
if http:
if send_headers:
@@ -740,8 +728,7 @@ class Cache(object):
allow replacing cache.ram with cache.with_prefix(cache.ram,'prefix')
it will add prefix to all the cache keys used.
"""
return lambda key, f, time_expire=DEFAULT_TIME_EXPIRE, prefix=prefix:\
cache_model(prefix + key, f, time_expire)
return lambda key, f, time_expire=DEFAULT_TIME_EXPIRE, prefix=prefix: cache_model(prefix + key, f, time_expire)
def lazy_cache(key=None, time_expire=None, cache_model='ram'):

View File

@@ -7,10 +7,11 @@ import re
import urllib
from cgi import escape
from string import maketrans
try:
from ast import parse as ast_parse
import ast
except ImportError: # python 2.5
from ast import parse as ast_parse
import ast
except ImportError: # python 2.5
from compiler import parse
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.
"""
html_colors=['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green',
'lime', 'maroon', 'navy', 'olive', 'purple', 'red',
'silver', 'teal', 'white', 'yellow']
html_colors = ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green',
'lime', 'maroon', 'navy', 'olive', 'purple', 'red',
'silver', 'teal', 'white', 'yellow']
META = '\x06'
LINK = '\x07'
DISABLED_META = '\x08'
LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" />'
regex_URL=re.compile(r'@/(?P<a>\w*)/(?P<c>\w*)/(?P<f>\w*(\.\w+)?)(/(?P<args>[\w\.\-/]+))?')
regex_env2=re.compile(r'@\{(?P<a>[\w\-\.]+?)(\:(?P<b>.*?))?\}')
regex_expand_meta = re.compile('('+META+'|'+DISABLED_META+'|````)')
regex_dd=re.compile(r'\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('('+META+'|'+DISABLED_META+r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',re.S)
regex_strong=re.compile(r'\*\*(?P<t>[^\s*]+( +[^\s*]+)*)\*\*')
regex_del=re.compile(r'~~(?P<t>[^\s*]+( +[^\s*]+)*)~~')
regex_em=re.compile(r"''(?P<t>([^\s']| |'(?!'))+)''")
regex_num=re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$")
regex_list=re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$')
regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
regex_tq=re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
regex_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_expand_meta = re.compile('(' + META + '|' + DISABLED_META + '|````)')
regex_dd = re.compile(r'\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile(
'(' + META + '|' + DISABLED_META + r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',
re.S)
regex_strong = re.compile(r'\*\*(?P<t>[^\s*]+( +[^\s*]+)*)\*\*')
regex_del = re.compile(r'~~(?P<t>[^\s*]+( +[^\s*]+)*)~~')
regex_em = re.compile(r"''(?P<t>([^\s']| |'(?!'))+)''")
regex_num = re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$")
regex_list = re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$')
regex_bq_headline = re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
regex_tq = re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
regex_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+)', re.M)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w|[\w\-.]+@[\w\-.]+)',re.M)
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_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_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_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_markmin_escape = 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_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05',"'`:*~\\[]{}@$+-.#\n")
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")
regex_quote = re.compile('(?P<name>\w+?)\s*\=\s*')
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):
"""
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')
if isinstance(node_or_string, ast.Expression):
node_or_string = node_or_string.body
def _convert(node):
if isinstance(node, ast.Str):
return node.s
@@ -594,11 +602,11 @@ def safe_eval(node_or_string, env):
if node.id in _safe_names:
return _safe_names[node.id]
elif isinstance(node, ast.BinOp) and \
isinstance(node.op, (Add, Sub)) and \
isinstance(node.right, Num) and \
isinstance(node.right.n, complex) and \
isinstance(node.left, Num) and \
isinstance(node.left.n, (int, long, float)):
isinstance(node.op, (Add, Sub)) and \
isinstance(node.right, Num) and \
isinstance(node.right.n, complex) and \
isinstance(node.left, Num) and \
isinstance(node.left.n, (int, long, float)):
left = node.left.n
right = node.right.n
if isinstance(node.op, Add):
@@ -606,57 +614,66 @@ def safe_eval(node_or_string, env):
else:
return left - right
raise ValueError('malformed string')
return _convert(node_or_string)
def markmin_escape(text):
""" insert \\ before markmin control characters: '`:*~[]{}@$ """
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)
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
def u2(match, env=env):
f = env.get(match.group('a'), match.group(0))
if callable(f):
b = match.group('b')
try:
b = safe_eval(make_dict(b),env)
b = safe_eval(make_dict(b), env)
except:
pass
try:
f = f(**b) if isinstance(b,dict) else f(b)
f = f(**b) if isinstance(b, dict) else f(b)
except Exception, e:
f = 'ERROR: %s' % e
return str(f)
text = regex_env2.sub(u2, text)
return text
def autolinks_simple(url):
"""
it automatically converts the url to link,
image, video or audio tag
"""
u_url=url.lower()
if '@' in url and not '://' in url:
u_url = url.lower()
if '@' in url and '://' not in 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
elif u_url.endswith(('.mp4','.mpeg','.mov','.ogv')):
elif u_url.endswith(('.mp4', '.mpeg', '.mov', '.ogv')):
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 '<a href="%s">%s</a>' % (url,url)
return '<a href="%s">%s</a>' % (url, url)
def protolinks_simple(proto, url):
"""
@@ -667,16 +684,18 @@ def protolinks_simple(proto, url):
proto="iframe"
url="http://www.example.com/path"
"""
if proto in ('iframe','embed'): #== 'iframe':
return '<iframe src="%s" frameborder="0" allowfullscreen></iframe>'%url
#elif proto == 'embed': # NOTE: embed is a synonym to iframe now
if proto in ('iframe', 'embed'): # == 'iframe':
return '<iframe src="%s" frameborder="0" allowfullscreen></iframe>' % url
# elif proto == 'embed': # NOTE: embed is a synonym to iframe now
# return '<a href="%s" class="%sembed">%s></a>'%(url,class_prefix,url)
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 proto+':'+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
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,
extra={},
@@ -925,17 +944,19 @@ def render(text,
>>> render("anchor with name 'NEWLINE': [[NEWLINE [newline] ]]")
'<p>anchor with name \\'NEWLINE\\': <span class="anchor" id="markmin_NEWLINE">newline</span></p>'
"""
if autolinks=="default": autolinks = autolinks_simple
if protolinks=="default": protolinks = protolinks_simple
pp='\n' if pretty_print else ''
if isinstance(text,unicode):
if autolinks == "default":
autolinks = autolinks_simple
if protolinks == "default":
protolinks = protolinks_simple
pp = '\n' if pretty_print else ''
if isinstance(text, unicode):
text = text.encode('utf8')
text = str(text or '')
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:
text = replace_at_urls(text,URL)
text = replace_at_urls(text, URL)
if latex == 'google':
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
#############################################################
segments = []
def mark_code(m):
g = m.group(0)
if g in (META, DISABLED_META ):
if g in (META, DISABLED_META):
segments.append((None, None, None, g))
return m.group()
elif g == '````':
@@ -956,10 +978,12 @@ def render(text,
else:
c = m.group('c') or ''
p = m.group('p') or ''
if 'code' in allowed and not c in allowed['code']: c = ''
code = m.group('t').replace('!`!','`')
if 'code' in allowed and c not in allowed['code']:
c = ''
code = m.group('t').replace('!`!', '`')
segments.append((code, c, p, m.group(0)))
return META
text = regex_code.sub(mark_code, text)
#############################################################
@@ -967,56 +991,58 @@ def render(text,
# store them into links they will be treated as link
#############################################################
links = []
def mark_link(m):
links.append( None if m.group() == LINK
else m.group('s') )
links.append(None if m.group() == LINK
else m.group('s'))
return LINK
text = regex_link.sub(mark_link, text)
text = escape(text)
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:
text = replace_autolinks(text,autolinks)
text = replace_autolinks(text, autolinks)
#############################################################
# normalize spaces
#############################################################
strings=text.split('\n')
strings = text.split('\n')
def parse_title(t, s): #out, lev, etags, tag, s):
hlevel=str(len(t))
def parse_title(t, s): # out, lev, etags, tag, s):
hlevel = str(len(t))
out.extend(etags[::-1])
out.append("<h%s>%s"%(hlevel,s))
etags[:]=["</h%s>%s"%(hlevel,pp)]
lev=0
ltags[:]=[]
tlev[:]=[]
out.append("<h%s>%s" % (hlevel, s))
etags[:] = ["</h%s>%s" % (hlevel, pp)]
lev = 0
ltags[:] = []
tlev[:] = []
return (lev, 'h')
def parse_list(t, p, s, tag, lev, mtag, lineno):
lent=len(t)
if lent<lev: # current item level < previous item level
while ltags[-1]>lent:
lent = len(t)
if lent < lev: # current item level < previous item level
while ltags[-1] > lent:
ltags.pop()
out.append(etags.pop())
lev=lent
tlev[lev:]=[]
lev = lent
tlev[lev:] = []
if lent>lev: # current item level > previous item level
if lev==0: # previous line is not a list (paragraph or title)
if lent > lev: # current item level > previous item level
if lev == 0: # previous line is not a list (paragraph or title)
out.extend(etags[::-1])
ltags[:]=[]
tlev[:]=[]
etags[:]=[]
if pend and mtag == '.': # paragraph in a list:
ltags[:] = []
tlev[:] = []
etags[:] = []
if pend and mtag == '.': # paragraph in a list:
out.append(etags.pop())
ltags.pop()
for i in xrange(lent-lev):
out.append('<'+tag+'>'+pp)
etags.append('</'+tag+'>'+pp)
lev+=1
for i in xrange(lent - lev):
out.append('<' + tag + '>' + pp)
etags.append('</' + tag + '>' + pp)
lev += 1
ltags.append(lev)
tlev.append(tag)
elif lent == lev:
@@ -1025,22 +1051,22 @@ def render(text,
for i in xrange(ltags.count(lent)):
ltags.pop()
out.append(etags.pop())
tlev[-1]=tag
out.append('<'+tag+'>'+pp)
etags.append('</'+tag+'>'+pp)
tlev[-1] = tag
out.append('<' + tag + '>' + pp)
etags.append('</' + tag + '>' + pp)
ltags.append(lev)
else:
if ltags.count(lev)>1:
if ltags.count(lev) > 1:
out.append(etags.pop())
ltags.pop()
mtag='l'
mtag = 'l'
out.append('<li>')
etags.append('</li>'+pp)
etags.append('</li>' + pp)
ltags.append(lev)
if s[:1] == '-':
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
if p and mtag=='l':
(lev,mtag,lineno)=parse_point(t, s, lev, '', lineno)
if p and mtag == 'l':
(lev, mtag, lineno) = parse_point(t, s, lev, '', lineno)
else:
out.append(s)
@@ -1048,28 +1074,28 @@ def render(text,
def parse_point(t, s, lev, mtag, lineno):
""" paragraphs in lists """
lent=len(t)
if lent>lev:
lent = len(t)
if lent > lev:
return parse_list(t, '.', s, 'ul', lev, mtag, lineno)
elif lent<lev:
while ltags[-1]>lent:
elif lent < lev:
while ltags[-1] > lent:
ltags.pop()
out.append(etags.pop())
lev=lent
tlev[lev:]=[]
mtag=''
elif lent==lev:
lev = lent
tlev[lev:] = []
mtag = ''
elif lent == lev:
if pend and mtag == '.':
out.append(etags.pop())
ltags.pop()
if br and mtag in ('l','.'):
if br and mtag in ('l', '.'):
out.append(br)
if s == META:
mtag = ''
mtag = ''
else:
mtag = '.'
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 == '.':
out.append(pbeg)
if pend:
@@ -1083,19 +1109,19 @@ def render(text,
# - is empty -> this is an <hr /> tag
# - consists '|' -> table
# - consists other characters -> blockquote
if (lineno+1 >= strings_len or
not(s.count('-') == len(s) and len(s)>3)):
return (s, mtag, lineno)
if (lineno + 1 >= strings_len or
not (s.count('-') == len(s) and len(s) > 3)):
return (s, mtag, lineno)
lineno+=1
lineno += 1
s = strings[lineno].strip()
if s:
if '|' in s:
# table
tout=[]
thead=[]
tbody=[]
rownum=0
tout = []
thead = []
tbody = []
rownum = 0
t_id = ''
t_cls = ''
@@ -1104,14 +1130,14 @@ def render(text,
s = strings[lineno].strip()
if s[:1] == '=':
# header or footer
if s.count('=')==len(s) and len(s)>3:
if not thead: # if thead list is empty:
if s.count('=') == len(s) and len(s) > 3:
if not thead: # if thead list is empty:
thead = tout
else:
tbody.extend(tout)
tout = []
rownum=0
lineno+=1
rownum = 0
lineno += 1
continue
m = regex_tq.match(s)
@@ -1121,36 +1147,36 @@ def render(text,
break
if rownum % 2:
tr = '<tr class="even">'
tr = '<tr class="even">'
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>' % (
' class="num"'
if regex_num.match(f) else '',
f.strip()
) for f in s.split('|')])+'</tr>'+pp)
rownum+=1
lineno+=1
' class="num"'
if regex_num.match(f) else '',
f.strip()
) for f in s.split('|')]) + '</tr>' + pp)
rownum += 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 ''
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 = ''
if thead:
s += '<thead>'+pp+''.join([l for l in thead])+'</thead>'+pp
if not tbody: # tbody strings are in tout list
s += '<thead>' + pp + ''.join([l for l in thead]) + '</thead>' + pp
if not tbody: # tbody strings are in tout list
tbody = tout
tout = []
if tbody: # if tbody list is not empty:
s += '<tbody>'+pp+''.join([l for l in tbody])+'</tbody>'+pp
if tout: # tfoot is not empty:
s += '<tfoot>'+pp+''.join([l for l in tout])+'</tfoot>'+pp
if tbody: # if tbody list is not empty:
s += '<tbody>' + pp + ''.join([l for l in tbody]) + '</tbody>' + pp
if tout: # tfoot is not empty:
s += '<tfoot>' + pp + ''.join([l for l in tout]) + '</tfoot>' + pp
s = '<table%s%s>%s%s</table>%s' % (t_cls, t_id, pp, s, pp)
mtag='t'
mtag = 't'
else:
# parse blockquote:
bq_begin=lineno
t_mode = False # embedded table
bq_begin = lineno
t_mode = False # embedded table
t_cls = ''
t_id = ''
@@ -1160,57 +1186,57 @@ def render(text,
if not t_mode:
m = regex_tq.match(s)
if m:
if (lineno+1 == strings_len or
'|' not in strings[lineno+1]):
t_cls = m.group('c') or ''
t_id = m.group('p') or ''
break
if (lineno + 1 == strings_len or
'|' not in strings[lineno + 1]):
t_cls = m.group('c') or ''
t_id = m.group('p') or ''
break
if regex_bq_headline.match(s):
if (lineno+1 < strings_len and
strings[lineno+1].strip()):
t_mode = True
lineno+=1
if (lineno + 1 < strings_len and
strings[lineno + 1].strip()):
t_mode = True
lineno += 1
continue
elif regex_tq.match(s):
t_mode=False
lineno+=1
t_mode = False
lineno += 1
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 ''
t_id = ' id="%s%s"'%(id_prefix,t_id) \
t_id = ' id="%s%s"' % (id_prefix, t_id) \
if t_id else ''
s = '<blockquote%s%s>%s</blockquote>%s' \
% (t_cls,
t_id,
'\n'.join(strings[bq_begin:lineno]),pp)
mtag='q'
% (t_cls,
t_id,
'\n'.join(strings[bq_begin:lineno]), pp)
mtag = 'q'
else:
s = '<hr />'
lineno-=1
mtag='q'
lineno -= 1
mtag = 'q'
return (s, 'q', lineno)
if sep == 'p':
pbeg = "<p>"
pend = "</p>"+pp
br = ''
pbeg = "<p>"
pend = "</p>" + pp
br = ''
else:
pbeg = pend = ''
br = "<br />"+pp if sep=='br' else ''
pbeg = pend = ''
br = "<br />" + pp if sep == 'br' else ''
lev = 0 # nesting level of lists
c0 = '' # first character of current line
out = [] # list of processed lines
etags = [] # trailing tags
ltags = [] # level# correspondent to trailing tag
lev = 0 # nesting level of lists
c0 = '' # first character of current line
out = [] # list of processed lines
etags = [] # trailing tags
ltags = [] # level# correspondent to trailing tag
tlev = [] # list of tags for each level ('ul' or 'ol')
mtag = '' # marked tag (~last tag) ('l','.','h','p','t'). Used to set <br/>
# and to avoid <p></p> around tables and blockquotes
# and to avoid <p></p> around tables and blockquotes
lineno = 0
strings_len = len(strings)
while lineno < strings_len:
@@ -1222,65 +1248,67 @@ def render(text,
#### ++++ ---- .... ------- field | field | field <-body
##### +++++ ----- ..... ---------------------:class[id]
"""
pc0=c0 # first character of previous line
c0=s[:1]
if c0: # for non empty strings
if c0 in "#+-.": # first character is one of: # + - .
(t1,t2,p,ss) = regex_list.findall(s)[0]
pc0 = c0 # first character of previous line
c0 = s[:1]
if c0: # for non empty strings
if c0 in "#+-.": # first character is one of: # + - .
(t1, t2, p, ss) = regex_list.findall(s)[0]
# t1 - tag ("###")
# t2 - tag ("+++", "---", "...")
# p - paragraph point ('.')->for "++." or "--."
# ss - other part of string
if t1 or t2:
# headers and lists:
if c0 == '#': # headers
if c0 == '#': # headers
(lev, mtag) = parse_title(t1, ss)
lineno+=1
lineno += 1
continue
elif c0 == '+': # ordered list
(lev, mtag, lineno)= parse_list(t2, p, ss, 'ol', lev, mtag, lineno)
lineno+=1
elif c0 == '+': # ordered list
(lev, mtag, lineno) = parse_list(t2, p, ss, 'ol', lev, mtag, lineno)
lineno += 1
continue
elif c0 == '-': # unordered list, table or blockquote
elif c0 == '-': # unordered list, table or blockquote
if p or ss:
(lev, mtag, lineno) = parse_list(t2, p, ss, 'ul', lev, mtag, lineno)
lineno+=1
lineno += 1
continue
else:
(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)
lineno+=1
lineno += 1
continue
if lev == 0 and (mtag == 'q' or s == META):
# 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
out.extend(etags[::-1])
etags=[]
ltags=[]
tlev=[]
lev=0
if br and mtag == 'p': out.append(br)
etags = []
ltags = []
tlev = []
lev = 0
if br and mtag == 'p':
out.append(br)
if mtag != 'q' and s != META:
if pend: etags=[pend]
out.append(pbeg)
mtag = 'p'
if pend:
etags = [pend]
out.append(pbeg)
mtag = 'p'
else:
mtag = ''
mtag = ''
out.append(s)
else:
if lev>0 and mtag=='.' and s == META:
if lev > 0 and mtag == '.' and s == META:
out.append(etags.pop())
ltags.pop()
out.append(s)
mtag = ''
else:
out.append(' '+s)
lineno+=1
out.append(' ' + s)
lineno += 1
out.extend(etags[::-1])
text = ''.join(out)
@@ -1295,7 +1323,7 @@ def render(text,
# deal with images, videos, audios and links
#############################################################
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:
return m.group(0)
k = escape(k)
@@ -1305,40 +1333,40 @@ def render(text,
p_begin = p_end = ''
if p == 'center':
p_begin = '<p style="text-align:center">'
p_end = '</p>'+pp
p_end = '</p>' + pp
elif p == 'blockleft':
p_begin = '<p style="text-align:left">'
p_end = '</p>'+pp
p_end = '</p>' + pp
elif p == 'blockright':
p_begin = '<p style="text-align:right">'
p_end = '</p>'+pp
elif p in ('left','right'):
style = ('float:%s' % p)+(';%s' % style if style else '')
p_end = '</p>' + pp
elif p in ('left', 'right'):
style = ('float:%s' % p) + (';%s' % style if style else '')
if t and regex_auto.match(t):
p_begin = p_begin + '<a href="%s">' % t
p_end = '</a>' + p_end
t = ''
if style:
style = ' style="%s"' % style
if p in ('video','audio'):
if p in ('video', 'audio'):
t = render(t, {}, {}, 'br', URL, environment, latex,
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>' \
% dict(p=p, title=title, style=style, k=k, t=t)
alt = ' alt="%s"'%escape(t).replace(META, DISABLED_META) if t else ''
% dict(p=p, title=title, style=style, k=k, t=t)
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' \
% 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):
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:
return m.group(0)
t = t or ''
a = escape(a) if a else ''
if k:
if '#' in k and not ':' in k.split('#')[0]:
if '#' in k and ':' not in k.split('#')[0]:
# wikipage, not external url
k=k.replace('#','#'+id_prefix)
k = k.replace('#', '#' + id_prefix)
k = escape(k)
title = ' title="%s"' % a.replace(META, DISABLED_META) if a 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>' \
% dict(k=k, title=title, target=target, t=t)
if t == 'NEWLINE' and not a:
return '<br />'+pp
return '<br />' + pp
return '<span class="anchor" id="%s">%s</span>' % (
escape(id_prefix+t),
render(a, {},{},'br', URL,
escape(id_prefix + t),
render(a, {}, {}, 'br', URL,
environment, latex, autolinks,
protolinks, class_prefix,
id_prefix, pretty_print))
parts = text.split(LINK)
text = parts[0]
for i,s in enumerate(links):
if s == None:
for i, s in enumerate(links):
if s is None:
html = LINK
else:
html = regex_media_level2.sub(sub_media, s)
@@ -1366,51 +1394,53 @@ def render(text,
html = regex_link_level2.sub(sub_link, html)
if html == s:
# return unprocessed string as a signal of an error
html = '[[%s]]'%s
text += html + parts[i+1]
html = '[[%s]]' % s
text += html + parts[i + 1]
#############################################################
# process all code text
#############################################################
def expand_meta(m):
code,b,p,s = segments.pop(0)
if code==None or m.group() == DISABLED_META:
code, b, p, s = segments.pop(0)
if code is None or m.group() == DISABLED_META:
return escape(s)
if b in extra:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if code[:1] == '\n':
code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
if p:
return str(extra[b](code,p))
return str(extra[b](code, p))
else:
return str(extra[b](code))
elif b=='cite':
return '['+','.join('<a href="#%s" class="%s">%s</a>' \
% (id_prefix+d,b,d) \
for d in escape(code).split(','))+']'
elif b=='latex':
elif b == 'cite':
return '[' + ','.join('<a href="#%s" class="%s">%s</a>' %
(id_prefix + d, b, d) for d in escape(code).split(',')) + ']'
elif b == 'latex':
return LATEX % urllib.quote(code)
elif b in html_colors:
return '<span style="color: %s">%s</span>' \
% (b, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
% (b, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
elif b in ('c', 'color') and p:
c=p.split(':')
fg='color: %s;' % c[0] if c[0] else ''
bg='background-color: %s;' % c[1] if len(c)>1 and c[1] else ''
return '<span style="%s%s">%s</span>' \
% (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
cls = ' class="%s%s"'%(class_prefix,b) if b and b != 'id' else ''
id = ' id="%s%s"'%(id_prefix,escape(p)) if p else ''
beg=(code[:1]=='\n')
end=[None,-1][code[-1:]=='\n']
c = p.split(':')
fg = 'color: %s;' % c[0] if c[0] else ''
bg = 'background-color: %s;' % c[1] if len(c) > 1 and c[1] else ''
return '<span style="%s%s">%s</span>' \
% (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
cls = ' class="%s%s"' % (class_prefix, b) if b and b != 'id' else ''
id = ' id="%s%s"' % (id_prefix, escape(p)) if p else ''
beg = (code[:1] == '\n')
end = [None, -1][code[-1:] == '\n']
if beg and end:
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, escape(code[1:-1]), pp)
return '<code%s%s>%s</code>' % (cls, id, escape(code[beg:end]))
text = regex_expand_meta.sub(expand_meta, text)
if environment:
text = replace_components(text,environment)
text = replace_components(text, environment)
return text.translate(ttab_out)
@@ -1423,16 +1453,18 @@ def markmin2html(text, extra={}, allowed={}, sep='p',
class_prefix=class_prefix, id_prefix=id_prefix,
pretty_print=pretty_print)
def run_doctests():
import doctest
doctest.testmod()
if __name__ == '__main__':
import sys
import doctest
from textwrap import dedent
html=dedent("""
html = dedent("""
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
@@ -1446,7 +1478,7 @@ if __name__ == '__main__':
</html>""")[1:]
if sys.argv[1:2] == ['-h']:
style=dedent("""
style = dedent("""
<style>
blockquote { background-color: #FFFAAE; padding: 7px; }
table { border-collapse: collapse; }
@@ -1467,22 +1499,23 @@ if __name__ == '__main__':
body=markmin2html(__doc__, pretty_print=True))
elif sys.argv[1:2] == ['-t']:
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__)":'
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:
fargv = open(sys.argv[1],'r')
fargv = open(sys.argv[1], 'r')
try:
markmin_text=fargv.read()
markmin_text = fargv.read()
# embed css file from second parameter into html file
if len(sys.argv) > 2:
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:
fargv2 = open(sys.argv[2],'r')
fargv2 = open(sys.argv[2], 'r')
try:
markmin_style = "<style>\n" + fargv2.read() + "</style>"
finally:
@@ -1496,10 +1529,9 @@ if __name__ == '__main__':
fargv.close()
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 " -t - timeit __doc__ (for testing purpuse only)"
print " file.markmin [file.css] - process file.markmin + built in file.css (optional)"
print " file.markmin [@path_to/css] - process file.markmin + link path_to/css (optional)"
run_doctests()

View File

@@ -7,53 +7,57 @@ import sys
import doctest
from optparse import OptionParser
__all__ = ['render','markmin2latex']
__all__ = ['render', 'markmin2latex']
META = 'META'
regex_newlines = re.compile('(\n\r)|(\r\n)')
regex_dd=re.compile('\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('('+META+')|(``(?P<t>.*?)``(:(?P<c>\w+))?)',re.S)
regex_title = re.compile('^#{1} (?P<t>[^\n]+)',re.M)
regex_dd = re.compile('\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('(' + META + ')|(``(?P<t>.*?)``(:(?P<c>\w+))?)', re.S)
regex_title = re.compile('^#{1} (?P<t>[^\n]+)', re.M)
regex_maps = [
(re.compile('[ \t\r]+\n'),'\n'),
(re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'),'{\\\\bf \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('^#{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('^#{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('^\- +(?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('\n\s+\n'),'\n\n')]
regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n',re.M|re.S)
(re.compile('[ \t\r]+\n'), '\n'),
(re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'), '{\\\\bf \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('^#{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('^#{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('^\- +(?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('\n\s+\n'), '\n\n')]
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_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 = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center)\]\]')
#regex_video = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +video\]\]')
#regex_audio = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +audio\]\]')
# regex_video = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +video\]\]')
# regex_audio = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +audio\]\]')
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_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}')
for c in '^_&$%{}': text=text.replace(c,'\\'+c)
text=text.replace('\\{\\textbackslash\\}','{\\textbackslash}')
if pound: text=text.replace('#','\\#')
# regex_quote_left = re.compile('"(?=\w)')
# regex_quote_right = re.compile('(?=\w\.)"')
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
def render(text,
extra={},
allowed={},
sep='p',
image_mapper=lambda x:x,
image_mapper=lambda x: x,
chapters=False):
#############################################################
# replace all blocks marked with ``...``:class with META
@@ -61,62 +65,68 @@ def render(text,
#############################################################
text = str(text or '')
segments, i = [], 0
text = regex_dd.sub('``\g<latex>``:latex ',text)
text = regex_newlines.sub('\n',text)
text = regex_dd.sub('``\g<latex>``:latex ', text)
text = regex_newlines.sub('\n', text)
while True:
item = regex_code.search(text,i)
if not item: break
if item.group()==META:
segments.append((None,None))
text = text[:item.start()]+META+text[item.end():]
item = regex_code.search(text, i)
if not item:
break
if item.group() == META:
segments.append((None, None))
text = text[:item.start()] + META + text[item.end():]
else:
c = item.group('c') or ''
if 'code' in allowed and not c in allowed['code']: c = ''
code = item.group('t').replace('!`!','`')
segments.append((code,c))
text = text[:item.start()]+META+text[item.end():]
i=item.start()+3
if 'code' in allowed and c not in allowed['code']:
c = ''
code = item.group('t').replace('!`!', '`')
segments.append((code, c))
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
#############################################################
title = regex_title.search(text)
if not title: title='Title'
else: title=title.group('t')
if not title:
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])
if len(texts)==2:
if len(texts) == 2:
text += '\n\\begin{thebibliography}{999}\n'
text += regex_bibitem.sub('\n\\\\bibitem{\g<t>}', texts[1])
text += '\n\\end{thebibliography}\n'
text = '\n'.join(t.strip() for t in text.split('\n'))
for regex, sub in regex_maps:
text = regex.sub(sub,text)
text=text.replace('#','\\#')
text=text.replace('`',"'")
text = regex.sub(sub, text)
text = text.replace('#', '\\#')
text = text.replace('`', "'")
#############################################################
# process tables and blockquotes
#############################################################
while True:
item = regex_table.search(text)
if not item: break
if not item:
break
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')
if ' | ' in content:
rows = content.replace('\n','\\\\\n').replace(' | ',' & ')
row0,row2 = rows.split('\\\\\n',1)
cols=row0.count(' & ')+1
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}'
if row2.count('\n')>20: tabular='\\newpage\n'+tabular
rows = content.replace('\n', '\\\\\n').replace(' | ', ' & ')
row0, row2 = rows.split('\\\\\n', 1)
cols = row0.count(' & ') + 1
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}'
if row2.count('\n') > 20:
tabular = '\\newpage\n' + tabular
text = text[:item.start()] + tabular + text[item.end():]
else:
text = text[:item.start()] + '\\begin{quote}' + content + '\\end{quote}' + text[item.end():]
@@ -126,29 +136,32 @@ def render(text,
#############################################################
def sub(x):
f=image_mapper(x.group('k'))
if not f: return None
return '\n\\begin{center}\\includegraphics[width=8cm]{%s}\\end{center}\n' % (f)
text = regex_image_width.sub(sub,text)
text = regex_image.sub(sub,text)
f = image_mapper(x.group('k'))
if not f:
return None
return '\n\\begin{center}\\includegraphics[width=8cm]{%s}\\end{center}\n' % f
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_commas.sub('\g<t>',text)
text = regex_noindent.sub('\n\\\\noindent \g<t>',text)
text = regex_commas.sub('\g<t>', text)
text = regex_noindent.sub('\n\\\\noindent \g<t>', text)
### fix paths in images
regex=re.compile('\\\\_\w*\.(eps|png|jpg|gif)')
# ## fix paths in images
regex = re.compile('\\\\_\w*\.(eps|png|jpg|gif)')
while True:
match=regex.search(text)
if not match: break
text=text[:match.start()]+text[match.start()+1:]
#text = regex_quote_left.sub('``',text)
#text = regex_quote_right.sub("''",text)
match = regex.search(text)
if not match:
break
text = text[:match.start()] + text[match.start() + 1:]
# text = regex_quote_left.sub('``',text)
# text = regex_quote_right.sub("''",text)
if chapters:
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'\section*{', r'\chapter*{')
text = text.replace(r'\section{', r'\chapter{')
text = text.replace(r'subsection{', r'section{')
#############################################################
# process all code text
@@ -156,57 +169,64 @@ def render(text,
parts = text.split(META)
text = parts[0]
authors = []
for i,(code,b) in enumerate(segments):
if code==None:
for i, (code, b) in enumerate(segments):
if code is None:
html = META
else:
if b=='hidden':
html=''
elif b=='author':
if b == 'hidden':
html = ''
elif b == 'author':
author = latex_escape(code.strip())
authors.append(author)
html=''
elif b=='inxx':
html='\inxx{%s}' % latex_escape(code)
elif b=='cite':
html='~\cite{%s}' % latex_escape(code.strip())
elif b=='ref':
html='~\ref{%s}' % latex_escape(code.strip())
elif b=='latex':
html = ''
elif b == 'inxx':
html = '\inxx{%s}' % latex_escape(code)
elif b == 'cite':
html = '~\cite{%s}' % latex_escape(code.strip())
elif b == 'ref':
html = '~\ref{%s}' % latex_escape(code.strip())
elif b == 'latex':
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:
html='$%s$' % code.strip()
elif b=='latex_eqnarray':
code=code.strip()
code='\\\\'.join(x.replace('=','&=&',1) for x in code.split('\\\\'))
html='\n\\begin{eqnarray}\n%s\n\\end{eqnarray}\n' % code
html = '$%s$' % code.strip()
elif b == 'latex_eqnarray':
code = code.strip()
code = '\\\\'.join(x.replace('=', '&=&', 1) for x in code.split('\\\\'))
html = '\n\\begin{eqnarray}\n%s\n\\end{eqnarray}\n' % code
elif b.startswith('latex_'):
key=b[6:]
html='\\begin{%s}%s\\end{%s}' % (key,code,key)
key = b[6:]
html = '\\begin{%s}%s\\end{%s}' % (key, code, key)
elif b in extra:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if code[:1] == '\n':
code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
html = extra[b](code)
elif code[:1]=='\n' or code[:-1]=='\n':
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
elif code[:1] == '\n' or code[:-1] == '\n':
if code[:1] == '\n':
code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
if code.startswith('<') or code.startswith('{{') or code.startswith('http'):
html = '\\begin{lstlisting}[keywords={}]\n%s\n\\end{lstlisting}' % code
else:
html = '\\begin{lstlisting}\n%s\n\\end{lstlisting}' % code
else:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if code[:1] == '\n':
code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
html = '{\\ft %s}' % latex_escape(code)
try:
text = text+html+parts[i+1]
text = text + html + parts[i + 1]
except:
text = text + '... WIKI PROCESSING ERROR ...'
break
text = text.replace(' ~\\cite','~\\cite')
text = text.replace(' ~\\cite', '~\\cite')
return text, title, authors
WRAPPER = """
\\documentclass[12pt]{article}
\\usepackage{hyperref}
@@ -239,12 +259,14 @@ WRAPPER = """
\\end{document}
"""
def markmin2latex(data, image_mapper=lambda x:x, extra={},
def markmin2latex(data, image_mapper=lambda x: x, extra={},
wrapper=WRAPPER):
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)
if __name__ == '__main__':
parser = OptionParser()
parser.add_option("-i", "--info", dest="info",
@@ -252,40 +274,39 @@ if __name__ == '__main__':
parser.add_option("-t", "--test", dest="test", action="store_true",
default=False)
parser.add_option("-n", "--no_wrapper", dest="no_wrapper",
action="store_true",default=False)
parser.add_option("-c", "--chapters", dest="chapters",action="store_true",
default=False,help="switch section for chapter")
action="store_true", default=False)
parser.add_option("-c", "--chapters", dest="chapters", action="store_true",
default=False, help="switch section for chapter")
parser.add_option("-w", "--wrapper", dest="wrapper", default=False,
help="latex file containing header and footer")
(options, args) = parser.parse_args()
if options.info:
import markmin2html
markmin2latex(markmin2html.__doc__)
elif options.test:
doctest.testmod()
else:
if options.wrapper:
fwrapper = open(options.wrapper,'rb')
fwrapper = open(options.wrapper, 'rb')
try:
wrapper = fwrapper.read()
finally:
fwrapper.close()
elif options.no_wrapper:
wrapper = '%(body)s'
wrapper = '%(body)s'
else:
wrapper = WRAPPER
for f in args:
fargs = open(f,'r')
fargs = open(f, 'r')
content_data = []
try:
content_data.append(fargs.read())
finally:
fargs.close()
content = '\n'.join(content_data)
output= markmin2latex(content,
wrapper=wrapper,
chapters=options.chapters)
output = markmin2latex(content,
wrapper=wrapper,
chapters=options.chapters)
print output

View File

@@ -13,21 +13,22 @@ from markmin2latex import markmin2latex
__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__):
try:
__func__(path)
except OSError, (errno, strerror):
print ERROR_STR % {'path' : path, 'error': strerror }
print ERROR_STR % {'path': path, 'error': strerror}
files=[path]
files = [path]
while files:
file=files[0]
file = files[0]
if os.path.isfile(file):
f=os.remove
f = os.remove
rmgeneric(file, os.remove)
del files[0]
elif os.path.isdir(file):
@@ -36,7 +37,7 @@ def removeall(path):
rmgeneric(file, os.rmdir)
del files[0]
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):
@@ -49,13 +50,13 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
- passes: defines how often pdflates should be run in the texfile.
"""
pdflatex=pdflatex
passes=passes
warnings=[]
pdflatex = pdflatex
passes = passes
warnings = []
# setup the envoriment
tmpdir = mkdtemp()
texfile = open(tmpdir+'/test.tex','wb')
texfile = open(tmpdir + '/test.tex', 'wb')
texfile.write(latex)
texfile.seek(0)
texfile.close()
@@ -63,8 +64,8 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
# start doing some work
for i in range(0, passes):
logfd,logname = mkstemp()
outfile=os.fdopen(logfd)
logfd, logname = mkstemp()
outfile = os.fdopen(logfd)
try:
ret = subprocess.call([pdflatex,
'-interaction=nonstopmode',
@@ -75,18 +76,18 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
stderr=subprocess.PIPE)
finally:
outfile.close()
re_errors=re.compile('^\!(.*)$',re.M)
re_warnings=re.compile('^LaTeX Warning\:(.*)$',re.M)
re_errors = re.compile('^\!(.*)$', re.M)
re_warnings = re.compile('^LaTeX Warning\:(.*)$', re.M)
flog = open(logname)
try:
loglines = flog.read()
finally:
flog.close()
errors=re_errors.findall(loglines)
warnings=re_warnings.findall(loglines)
errors = re_errors.findall(loglines)
warnings = re_warnings.findall(loglines)
os.unlink(logname)
pdffile=texfile.rsplit('.',1)[0]+'.pdf'
pdffile = texfile.rsplit('.', 1)[0] + '.pdf'
if os.path.isfile(pdffile):
fpdf = open(pdffile, 'rb')
try:
@@ -100,31 +101,31 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
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__':
import sys
import doctest
import markmin2html
if sys.argv[1:2]==['-h']:
if sys.argv[1:2] == ['-h']:
data, warnings, errors = markmin2pdf(markmin2html.__doc__)
if errors:
print 'ERRORS:'+'\n'.join(errors)
print 'WARNGINS:'+'\n'.join(warnings)
print 'ERRORS:' + '\n'.join(errors)
print 'WARNGINS:' + '\n'.join(warnings)
else:
print data
elif len(sys.argv)>1:
fargv = open(sys.argv[1],'rb')
elif len(sys.argv) > 1:
fargv = open(sys.argv[1], 'rb')
try:
data, warnings, errors = markmin2pdf(fargv.read())
finally:
fargv.close()
if errors:
print 'ERRORS:'+'\n'.join(errors)
print 'WARNGINS:'+'\n'.join(warnings)
print 'ERRORS:' + '\n'.join(errors)
print 'WARNGINS:' + '\n'.join(warnings)
else:
print data
else:
doctest.testmod()

View File

@@ -308,7 +308,7 @@ def URL(a=None,
else:
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':
extension = None
# add static version to url
@@ -955,7 +955,6 @@ class DIV(XmlComponent):
# get the xml for the inner components
co = join([xmlescape(component) for component in
self.components])
return (fa, co)
def xml(self):
@@ -990,7 +989,7 @@ class DIV(XmlComponent):
Examples:
>>> markdown = lambda text,tag=None,attributes={}: \
>>> markdown = lambda text, tag=None, attributes={}: \
{None: re.sub('\s+',' ',text), \
'h1':'#'+text+'\\n\\n', \
'p':text+'\\n'}.get(tag,text)
@@ -1171,13 +1170,13 @@ class DIV(XmlComponent):
return i
else:
self[i] = replace(self[i]) if callable(replace) else replace
return i+1
return i + 1
# loop the components
if find_text or find_components:
i = 0
while i < len(self.components):
c = self[i]
j = i+1
j = i + 1
if check and find_text and isinstance(c, str) and \
((is_regex and find_text.search(c)) or (str(find_text) in c)):
j = replace_component(i)
@@ -2573,7 +2572,7 @@ class MENU(DIV):
item[3], select, prefix=CAT(prefix, item[0], '/'))
select['_onchange'] = 'window.location=this.value'
# 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
def xml(self):
@@ -2805,7 +2804,7 @@ class MARKMIN(XmlComponent):
self.extra = extra or {}
self.allowed = allowed or {}
self.sep = sep
self.url = URL if url == True else url
self.url = URL if url is True else url
self.environment = environment
self.latex = latex
self.autolinks = autolinks

File diff suppressed because it is too large Load Diff

View File

@@ -181,7 +181,7 @@ class JobGraph(object):
task_child=task_child,
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
are no mutual dependencies among tasks.
Commits at the end if successfull, or it rollbacks the entire

View File

@@ -1897,33 +1897,39 @@ class SQLFORM(FORM):
else:
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)
if field_type in ['boolean', 'double', 'time', 'integer']:
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':
iso_format = {'_data-w2p_date_format': '%Y-%m-%d'}
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':
iso_format = {'_data-w2p_datetime_format': '%Y-%m-%d %H:%M:%S'}
widget_ = SQLFORM.widgets.datetime
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format)
elif (field_type.startswith('reference ') or
field_type.startswith('list:reference ')) and \
hasattr(field.requires, 'options') or \
hasattr(field.requires, 'options'):
value_input = widget_.widget(field, field.default, _id=_id,
_class=widget_._class + ' form-control',
**iso_format)
elif hasattr(field.requires, 'options'):
value_input = SELECT(
*[OPTION(v, _value=k)
for k, v in field.requires.options()],
_class='form-control',
**dict(_id=_id))
elif field_type.startswith('reference ') or \
field_type.startswith('list:integer') or \
field_type.startswith('list:reference '):
elif (field_type.startswith('integer') or
field_type.startswith('reference ') or
field_type.startswith('list:integer') or
field_type.startswith('list:reference ')):
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:
value_input = INPUT(
_type='text', _id=_id,
@@ -3037,7 +3043,16 @@ class SQLFORM(FORM):
query = query & constraints[table._tablename]
if isinstance(links, dict):
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 table._tablename in kwargs[key]:
kwargs[key] = kwargs[key][table._tablename]

View File

@@ -21,6 +21,7 @@ from test_contribs import *
from test_web import *
from test_dal import *
from test_tools import *
from test_scheduler import *
if sys.version[:3] == '2.7':
from test_old_doctests import *

View File

@@ -37,10 +37,11 @@ def tearDownModule():
pass
class TestCache(unittest.TestCase):
def testCacheInRam(self):
# TODO: test_CacheAbstract(self):
def test_CacheInRam(self):
# defaults to mode='http'
cache = CacheInRam()
@@ -53,22 +54,21 @@ class TestCache(unittest.TestCase):
cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4)
#test singleton behaviour
# test singleton behaviour
cache = CacheInRam()
cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4)
#test key deletion
# test key deletion
cache('a', None)
self.assertEqual(cache('a', lambda: 5, 100), 5)
#test increment
# test increment
self.assertEqual(cache.increment('a'), 6)
self.assertEqual(cache('a', lambda: 1, 100), 6)
cache.increment('b')
self.assertEqual(cache('b', lambda: 'x', 100), 1)
def testCacheOnDisk(self):
def test_CacheOnDisk(self):
# defaults to mode='http'
s = Storage({'application': 'admin',
@@ -83,30 +83,36 @@ class TestCache(unittest.TestCase):
cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4)
#test singleton behaviour
# test singleton behaviour
cache = CacheOnDisk(s)
cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4)
#test key deletion
# test key deletion
cache('a', None)
self.assertEqual(cache('a', lambda: 5, 100), 5)
#test increment
# test increment
self.assertEqual(cache.increment('a'), 6)
self.assertEqual(cache('a', lambda: 1, 100), 6)
cache.increment('b')
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',
'folder': 'applications/admin'})
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: 2, 100), 1)
self.assertEqual(cache.ram('prefixa', lambda: 2, 100), 1)
def testRegex(self):
def test_Regex(self):
cache = CacheInRam()
self.assertEqual(cache('a1', lambda: 1, 0), 1)
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('a2', lambda: 3, 100), 3)
def testDALcache(self):
def test_DALcache(self):
s = Storage({'application': 'admin',
'folder': 'applications/admin'})
cache = Cache(s)

View File

@@ -106,16 +106,18 @@ class TestDALAdapters(unittest.TestCase):
def test_mysql(self):
if os.environ.get('APPVEYOR'):
return
os.environ["DB"] = "mysql://root:@localhost/pydal"
result = self._run_tests()
self.assertTrue(result)
if os.environ.get('TRAVIS'):
os.environ["DB"] = "mysql://root:@localhost/pydal"
result = self._run_tests()
self.assertTrue(result)
def test_pg8000(self):
if os.environ.get('APPVEYOR'):
return
os.environ["DB"] = "postgres:pg8000://postgres:@localhost/pydal"
result = self._run_tests()
self.assertTrue(result)
if os.environ.get('TRAVIS'):
os.environ["DB"] = "postgres:pg8000://postgres:@localhost/pydal"
result = self._run_tests()
self.assertTrue(result)
if __name__ == '__main__':

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()

439
gluon/tests/test_sqlhtml.py Normal file

File diff suppressed because one or more lines are too long

View File

@@ -9,10 +9,11 @@ from fix_path import fix_sys_path
fix_sys_path(__file__)
import template
from template import render
class TestVirtualFields(unittest.TestCase):
class TestTemplate(unittest.TestCase):
def testRun(self):
self.assertEqual(render(content='{{for i in range(n):}}{{=i}}{{pass}}',
@@ -61,6 +62,80 @@ class TestVirtualFields(unittest.TestCase):
self.assertRaises(
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__':
unittest.main()

View File

@@ -20,7 +20,7 @@ DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
from gluon.dal import DAL, Field
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 storage import Storage
from languages import translator
@@ -29,57 +29,6 @@ from gluon.http import HTTP
python_version = sys.version[:3]
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):
"""
@@ -102,7 +51,7 @@ class TestMail(unittest.TestCase):
users = {}
def __init__(self, address, port, **kwargs):
self.address=address
self.address = address
self.port = port
self.has_quit = False
self.tls = False
@@ -110,14 +59,14 @@ class TestMail(unittest.TestCase):
def login(self, username, password):
if username not in self.users or self.users[username] != password:
raise smtplib.SMTPAuthenticationError
self.username=username
self.password=password
self.username = username
self.password = password
def sendmail(self, sender, to, payload):
self.inbox.append(TestMail.Message(sender, to, payload))
def quit(self):
self.has_quit=True
self.has_quit = True
def ehlo(self, hostname=None):
pass
@@ -125,7 +74,6 @@ class TestMail(unittest.TestCase):
def starttls(self):
self.tls = True
def setUp(self):
self.original_SMTP = smtplib.SMTP
self.original_SMTP_SSL = smtplib.SMTP_SSL
@@ -141,10 +89,10 @@ class TestMail(unittest.TestCase):
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
message = TestMail.DummySMTP.inbox.pop()
self.assertEqual(message.sender, mail.settings.sender)
self.assertEqual(message.to, ['somebody@example.com'])
@@ -158,10 +106,10 @@ class TestMail(unittest.TestCase):
mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password'
self.assertFalse(mail.send(to=['somebody@example.com'],
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
def test_login(self):
TestMail.DummySMTP.users['username'] = 'password'
@@ -170,10 +118,10 @@ class TestMail(unittest.TestCase):
mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password'
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
del TestMail.DummySMTP.users['username']
TestMail.DummySMTP.inbox.pop()
@@ -182,10 +130,10 @@ class TestMail(unittest.TestCase):
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='<html><head></head><body></body></html>'))
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='<html><head></head><body></body></html>'))
message = TestMail.DummySMTP.inbox.pop()
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.ssl = True
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
TestMail.DummySMTP.inbox.pop()
def test_tls(self):
@@ -207,16 +155,302 @@ class TestMail(unittest.TestCase):
mail.settings.sender = 'you@example.com'
mail.settings.tls = True
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
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__':
unittest.main()

View File

@@ -160,6 +160,14 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ({u'a': 100}, None))
rtn = IS_JSON()('spam1234')
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):
rtn = IS_IN_SET(['max', 'john'])('max')
@@ -174,6 +182,14 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ('id1', None))
rtn = IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1')
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
rtn = IS_IN_SET(itertools.chain(['1','3','5'],['2','4','6']))('1')
self.assertEqual(rtn, ('1', None))
@@ -181,6 +197,10 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ('id1', None))
rtn = IS_IN_SET([('id1','first label'), ('id2','second label')]).options(zero=False)
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):
from gluon.dal import DAL, Field
@@ -190,6 +210,10 @@ class TestValidators(unittest.TestCase):
costanza_id = db.person.insert(name='costanza')
rtn = IS_IN_DB(db, 'person.id', '%(name)s')(george_id)
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)
self.assertEqual(rtn, (george_id+costanza_id, 'oops'))
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))
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))
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)
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()
def test_IS_NOT_IN_DB(self):
@@ -211,8 +241,14 @@ class TestValidators(unittest.TestCase):
db.person.insert(name='george')
rtn = IS_NOT_IN_DB(db, 'person.name', error_message='oops')('george')
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')
self.assertEqual(rtn, ('jerry', None))
rtn = IS_NOT_IN_DB(db, 'person.name')(u'jerry')
self.assertEqual(rtn, ('jerry', None))
db.person.drop()
def test_IS_INT_IN_RANGE(self):
@@ -668,6 +704,8 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ('a@b.co', None))
rtn = ANY_OF([IS_DATE(),IS_EMAIL()])('1982-12-14')
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):
rtn = IS_EMPTY_OR(IS_EMAIL())('abc@def.com')

View File

@@ -820,12 +820,12 @@ class Recaptcha(DIV):
Examples:
Use as::
form = FORM(Recaptcha(public_key='...',private_key='...'))
form = FORM(Recaptcha(public_key='...', private_key='...'))
or::
form = SQLFORM(...)
form.append(Recaptcha(public_key='...',private_key='...'))
form.append(Recaptcha(public_key='...', private_key='...'))
"""
@@ -984,17 +984,17 @@ class Recaptcha2(DIV):
Examples:
Use as::
form = FORM(Recaptcha2(public_key='...',private_key='...'))
form = FORM(Recaptcha2(public_key='...', private_key='...'))
or::
form = SQLFORM(...)
form.append(Recaptcha2(public_key='...',private_key='...'))
form.append(Recaptcha2(public_key='...', private_key='...'))
to protect the login page instead, use::
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]
else:
host = 'localhost'
return host
def __init__(self, environment=None, db=None, mailer=True,
hmac_key=None, controller='default', function='user',
@@ -2575,7 +2576,7 @@ class Auth(object):
update_keys[key] = keys[key]
user.update_record(**update_keys)
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]
keys['first_name'] = keys.get('username', guess)
vars = table_user._filter_fields(keys)
@@ -2698,8 +2699,7 @@ class Auth(object):
fields[settings.passfield] = \
settings.table_user[settings.passfield].validate(fields[settings.passfield])[0]
if not fields.get(settings.userfield):
raise ValueError('register_bare: ' +
'userfield not provided or invalid')
raise ValueError('register_bare: userfield not provided or invalid')
user = self.get_or_create_user(fields, login=False, get=False,
update_fields=self.settings.update_fields)
if not user:
@@ -3251,12 +3251,16 @@ class Auth(object):
next = cas.logout_url(next)
current.session.auth = None
self.user = None
if self.settings.renew_session_onlogout:
current.session.renew(clear_session=not self.settings.keep_session_onlogout)
current.session.flash = self.messages.logged_out
if next is not None:
redirect(next)
def logout_bare(self):
self.logout(next=None, onlogout=None, log=None)
def register(self,
next=DEFAULT,
onvalidation=DEFAULT,
@@ -4124,6 +4128,7 @@ class Auth(object):
raise HTTP(401, "Not Authorized")
current_id = auth.user.id
requested_id = user_id
user = None
if user_id is DEFAULT:
user_id = current.request.post_vars.user_id
if user_id and user_id != self.user.id and user_id != '0':
@@ -4152,7 +4157,10 @@ class Auth(object):
return None
if requested_id is DEFAULT and not request.post_vars:
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):
if not self.user:
@@ -4504,7 +4512,7 @@ class Auth(object):
ignore_common_filters=True).select(
limitby=(0, 1), orderby_on_limitby=False).first()
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)
id = record.id
else:

View File

@@ -471,8 +471,6 @@ class IS_IN_SET(Validator):
thestrset = [str(x) for x in self.theset]
failures = [x for x in values if not str(x) in thestrset]
if failures and self.theset:
if self.multiple and (value is None or value == ''):
return ([], None)
return (value, translate(self.error_message))
if self.multiple:
if isinstance(self.multiple, (tuple, list)) and \
@@ -1200,7 +1198,12 @@ class IS_EMAIL(Validator):
self.error_message = error_message
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:
domain = value.split('@')[1]
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
# that validates the value and has a formatter
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)

View File

@@ -212,21 +212,20 @@ if [ "$nopassword" -eq 0 ]
then
sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)"
fi
systemctl enable emperor.uwsgi.service
systemctl start emperor.uwsgi.service
/etc/init.d/nginx restart
/etc/init.d/nginx start
start uwsgi-emperor
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