From fe1a1eb79bf0f1df8bbc56d2402e32061af79d06 Mon Sep 17 00:00:00 2001 From: Sebastian Silva Date: Wed, 12 Oct 2011 00:54:31 +0000 Subject: Tidy up code a bit - added documentation --- diff --git a/icons/document-generic.png b/icons/document-generic.png new file mode 100644 index 0000000..1518d8a --- /dev/null +++ b/icons/document-generic.png Binary files differ diff --git a/icons/folder.png b/icons/folder.png new file mode 100644 index 0000000..bc757d4 --- /dev/null +++ b/icons/folder.png Binary files differ diff --git a/icons/image-x-generic.png b/icons/image-x-generic.png new file mode 100644 index 0000000..962b684 --- /dev/null +++ b/icons/image-x-generic.png Binary files differ diff --git a/icons/text-uri-list.png b/icons/text-uri-list.png new file mode 100644 index 0000000..64e848d --- /dev/null +++ b/icons/text-uri-list.png Binary files differ diff --git a/icons/text-x-generic.png b/icons/text-x-generic.png new file mode 100644 index 0000000..0eaf1f1 --- /dev/null +++ b/icons/text-x-generic.png Binary files differ diff --git a/icons/text-x-python.png b/icons/text-x-python.png new file mode 100644 index 0000000..a5f7984 --- /dev/null +++ b/icons/text-x-python.png Binary files differ diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..8322998 --- /dev/null +++ b/run.sh @@ -0,0 +1,5 @@ +#!/bin/sh +export PYTHONPATH=`pwd`/websdk:$PYTHONPATH +exec python studio/studio.py 5000 & +sleep 2 +exec python -c "import webbrowser;webbrowser.open(\"http://localhost:5000\")" diff --git a/studio/__init__.py b/studio/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/studio/__init__.py diff --git a/studio/static/css/main.css b/studio/static/css/main.css new file mode 100644 index 0000000..b094667 --- /dev/null +++ b/studio/static/css/main.css @@ -0,0 +1,111 @@ +body { + background-color: #c0c0c0; + height: 100%; + margin: 0; + font-size: 88%; + font-family: DejaVu Sans; +} + +p.subtitle { + font-size: 10pt; + margin-top: 5px; +} + +li { + margin-bottom: 1em +} + +hr { + border: 0; + color: #9E9E9E; + background-color: #9E9E9E; + height: 1px; + width: 100%; + text-align: left; +} + +#content { + margin: 30px; +} + +#beta { + font-size: 12pt; + color: red; + display: none; +} + +#editor { + margin: 0; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 85%; + margin-left: 15%; +} + +#editor .wymeditor { +} + +#editor-sidebar { + width: 15%; + padding: 5px; +} + +.bling { + display: none; +} + +div#filer { + display: none; +} + +#filer ul{ + list-style: none; + width: 100%; + padding: 0; + margin-left: 10px; +} + +#filer ul li { + text-align: center; + vertical-align: top; +} + + +#filer ul li a:hover{ + color: white; + background-color: #808080; +} + +#filer ul li a{ + color: black; + text-decoration: none; + font-size: 8pt; + width: 50px; + height: 70px; + float:left; + padding:22px; + padding-top:6px; +} + +#filer ul li a img { + border: none; + height: 56px; +} + +#filer-header { + background-color: black; + color: white; + margin: 0; + padding: 0; + padding-top: 1px; + padding-bottom: 7px; + padding-left: 15px; + width: 100%; +} + +#filer-header div{ + padding-left: 7px; +} diff --git a/studio/static/css/sugar-theme/images/ui-bg_flat_100_c0c0c0_40x100.png b/studio/static/css/sugar-theme/images/ui-bg_flat_100_c0c0c0_40x100.png new file mode 100644 index 0000000..299f267 --- /dev/null +++ b/studio/static/css/sugar-theme/images/ui-bg_flat_100_c0c0c0_40x100.png Binary files differ diff --git a/studio/static/css/sugar-theme/images/ui-bg_flat_50_aaaaaa_40x100.png b/studio/static/css/sugar-theme/images/ui-bg_flat_50_aaaaaa_40x100.png new file mode 100644 index 0000000..5b5dab2 --- /dev/null +++ b/studio/static/css/sugar-theme/images/ui-bg_flat_50_aaaaaa_40x100.png Binary files differ diff --git a/studio/static/css/sugar-theme/images/ui-bg_flat_65_ffffff_40x100.png b/studio/static/css/sugar-theme/images/ui-bg_flat_65_ffffff_40x100.png new file mode 100644 index 0000000..ac8b229 --- /dev/null +++ b/studio/static/css/sugar-theme/images/ui-bg_flat_65_ffffff_40x100.png Binary files differ diff --git a/studio/static/css/sugar-theme/images/ui-bg_flat_75_282828_40x100.png b/studio/static/css/sugar-theme/images/ui-bg_flat_75_282828_40x100.png new file mode 100644 index 0000000..89c6362 --- /dev/null +++ b/studio/static/css/sugar-theme/images/ui-bg_flat_75_282828_40x100.png Binary files differ diff --git a/studio/static/css/sugar-theme/images/ui-bg_flat_75_808080_40x100.png b/studio/static/css/sugar-theme/images/ui-bg_flat_75_808080_40x100.png new file mode 100644 index 0000000..6864463 --- /dev/null +++ b/studio/static/css/sugar-theme/images/ui-bg_flat_75_808080_40x100.png Binary files differ diff --git a/studio/static/css/sugar-theme/images/ui-bg_glow-ball_20_282828_600x600.png b/studio/static/css/sugar-theme/images/ui-bg_glow-ball_20_282828_600x600.png new file mode 100644 index 0000000..d05eb5b --- /dev/null +++ b/studio/static/css/sugar-theme/images/ui-bg_glow-ball_20_282828_600x600.png Binary files differ diff --git a/studio/static/css/sugar-theme/images/ui-bg_highlight-hard_5_282828_1x100.png b/studio/static/css/sugar-theme/images/ui-bg_highlight-hard_5_282828_1x100.png new file mode 100644 index 0000000..68a36c5 --- /dev/null +++ b/studio/static/css/sugar-theme/images/ui-bg_highlight-hard_5_282828_1x100.png Binary files differ diff --git a/studio/static/css/sugar-theme/images/ui-bg_highlight-hard_95_c0c0c0_1x100.png b/studio/static/css/sugar-theme/images/ui-bg_highlight-hard_95_c0c0c0_1x100.png new file mode 100644 index 0000000..81722a4 --- /dev/null +++ b/studio/static/css/sugar-theme/images/ui-bg_highlight-hard_95_c0c0c0_1x100.png Binary files differ diff --git a/studio/static/css/sugar-theme/images/ui-icons_000000_256x240.png b/studio/static/css/sugar-theme/images/ui-icons_000000_256x240.png new file mode 100644 index 0000000..7c211aa --- /dev/null +++ b/studio/static/css/sugar-theme/images/ui-icons_000000_256x240.png Binary files differ diff --git a/studio/static/css/sugar-theme/images/ui-icons_2e83ff_256x240.png b/studio/static/css/sugar-theme/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 0000000..09d1cdc --- /dev/null +++ b/studio/static/css/sugar-theme/images/ui-icons_2e83ff_256x240.png Binary files differ diff --git a/studio/static/css/sugar-theme/images/ui-icons_cd0a0a_256x240.png b/studio/static/css/sugar-theme/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 0000000..2ab019b --- /dev/null +++ b/studio/static/css/sugar-theme/images/ui-icons_cd0a0a_256x240.png Binary files differ diff --git a/studio/static/css/sugar-theme/images/ui-icons_ffffff_256x240.png b/studio/static/css/sugar-theme/images/ui-icons_ffffff_256x240.png new file mode 100644 index 0000000..42f8f99 --- /dev/null +++ b/studio/static/css/sugar-theme/images/ui-icons_ffffff_256x240.png Binary files differ diff --git a/studio/static/css/sugar-theme/jquery-ui-1.8.16.sugar.css b/studio/static/css/sugar-theme/jquery-ui-1.8.16.sugar.css new file mode 100644 index 0000000..c3877c2 --- /dev/null +++ b/studio/static/css/sugar-theme/jquery-ui-1.8.16.sugar.css @@ -0,0 +1,568 @@ +/* + * jQuery UI CSS Framework 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* + * jQuery UI CSS Framework 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=DejaVu%20Sans&fwDefault=normal&fsDefault=1.1em&cornerRadius=10px&bgColorHeader=282828&bgTextureHeader=01_flat.png&bgImgOpacityHeader=75&borderColorHeader=282828&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=e5e5e5&bgTextureContent=01_flat.png&bgImgOpacityContent=100&borderColorContent=c0c0c0&fcContent=000000&iconColorContent=000000&bgColorDefault=808080&bgTextureDefault=01_flat.png&bgImgOpacityDefault=75&borderColorDefault=808080&fcDefault=ffffff&iconColorDefault=ffffff&bgColorHover=808080&bgTextureHover=01_flat.png&bgImgOpacityHover=75&borderColorHover=808080&fcHover=ffffff&iconColorHover=ffffff&bgColorActive=ffffff&bgTextureActive=01_flat.png&bgImgOpacityActive=65&borderColorActive=c0c0c0&fcActive=000000&iconColorActive=000000&bgColorHighlight=282828&bgTextureHighlight=04_highlight_hard.png&bgImgOpacityHighlight=5&borderColorHighlight=000&fcHighlight=fff&iconColorHighlight=2e83ff&bgColorError=c0c0c0&bgTextureError=04_highlight_hard.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=282828&bgTextureOverlay=21_glow_ball.png&bgImgOpacityOverlay=20&opacityOverlay=80&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=50&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: DejaVu Sans; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: DejaVu Sans; font-size: 1em; } +.ui-widget-content { border: 1px solid #c0c0c0; background: #e5e5e5 url(images/ui-bg_flat_100_e5e5e5_40x100.png) 50% 50% repeat-x; color: #000000; } +.ui-widget-content a { color: #000000; } +.ui-widget-header { border: 1px solid #282828; background: #282828 url(images/ui-bg_flat_75_282828_40x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } +.ui-widget-header a { color: #ffffff; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #808080; background: #808080 url(images/ui-bg_flat_75_808080_40x100.png) 50% 50% repeat-x; font-weight: normal; color: #ffffff; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #ffffff; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #808080; background: #808080 url(images/ui-bg_flat_75_808080_40x100.png) 50% 50% repeat-x; font-weight: normal; color: #ffffff; } +.ui-state-hover a, .ui-state-hover a:hover { color: #ffffff; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #c0c0c0; background: #ffffff url(images/ui-bg_flat_65_ffffff_40x100.png) 50% 50% repeat-x; font-weight: normal; color: #000000; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #000000; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #000; background: #282828 url(images/ui-bg_highlight-hard_5_282828_1x100.png) 50% top repeat-x; color: #fff; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #fff; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #c0c0c0 url(images/ui-bg_highlight-hard_95_c0c0c0_1x100.png) 50% top repeat-x; color: #cd0a0a; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_000000_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_000000_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_ffffff_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_000000_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 10px; -webkit-border-top-left-radius: 10px; -khtml-border-top-left-radius: 10px; border-top-left-radius: 10px; } +.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 10px; -webkit-border-top-right-radius: 10px; -khtml-border-top-right-radius: 10px; border-top-right-radius: 10px; } +.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 10px; -webkit-border-bottom-left-radius: 10px; -khtml-border-bottom-left-radius: 10px; border-bottom-left-radius: 10px; } +.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 10px; -webkit-border-bottom-right-radius: 10px; -khtml-border-bottom-right-radius: 10px; border-bottom-right-radius: 10px; } + +/* Overlays */ +.ui-widget-overlay { background: #282828 url(images/ui-bg_glow-ball_20_282828_600x600.png) 50% 35% repeat-x; opacity: .80;filter:Alpha(Opacity=80); } +.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_50_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/* + * jQuery UI Resizable 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Resizable#theming + */ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block; } +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* + * jQuery UI Selectable 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectable#theming + */ +.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } +/* + * jQuery UI Accordion 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Accordion#theming + */ +/* IE/Win - Fix animation bug - #4615 */ +.ui-accordion { width: 100%; } +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } +.ui-accordion .ui-accordion-content-active { display: block; } +/* + * jQuery UI Autocomplete 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete { position: absolute; cursor: default; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* + * jQuery UI Menu 1.8.16 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menu#theming + */ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; + float: left; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} +/* + * jQuery UI Button 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Button#theming + */ +.ui-button { display: inline-block; position: relative; padding: 0; margin-top: 6pt; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } + +/*button text element */ +.ui-button .ui-button-text { display: block; line-height: 1.4; } +.ui-button-text-only .ui-button-text { padding: .4em 1em; } +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } +.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } +.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } +/* no icon support for input elements, provide padding by default */ +input.ui-button { padding: .4em 1em; } + +/*button icon element(s) */ +.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } +.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } +.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } + +/*button sets*/ +.ui-buttonset { margin-right: 7px; } +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } + +/* workarounds */ +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ +/* + * jQuery UI Dialog 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog#theming + */ +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } +.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } +.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* + * jQuery UI Slider 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* + * jQuery UI Tabs 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs#theming + */ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } +/* + * jQuery UI Datepicker 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Datepicker#theming + */ +.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* + * jQuery UI Progressbar 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar#theming + */ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } diff --git a/studio/static/doc/flask-docs/.buildinfo b/studio/static/doc/flask-docs/.buildinfo new file mode 100644 index 0000000..ad0796f --- /dev/null +++ b/studio/static/doc/flask-docs/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 39b2407f81d28483972c20d3689be37c +tags: fbb0d17656682115ca4d033fb2f83ba1 diff --git a/studio/static/doc/flask-docs/_images/debugger.png b/studio/static/doc/flask-docs/_images/debugger.png new file mode 100644 index 0000000..4f47229 --- /dev/null +++ b/studio/static/doc/flask-docs/_images/debugger.png Binary files differ diff --git a/studio/static/doc/flask-docs/_images/debugger1.png b/studio/static/doc/flask-docs/_images/debugger1.png new file mode 100644 index 0000000..4f47229 --- /dev/null +++ b/studio/static/doc/flask-docs/_images/debugger1.png Binary files differ diff --git a/studio/static/doc/flask-docs/_images/debugger2.png b/studio/static/doc/flask-docs/_images/debugger2.png new file mode 100644 index 0000000..4f47229 --- /dev/null +++ b/studio/static/doc/flask-docs/_images/debugger2.png Binary files differ diff --git a/studio/static/doc/flask-docs/_images/debugger3.png b/studio/static/doc/flask-docs/_images/debugger3.png new file mode 100644 index 0000000..4f47229 --- /dev/null +++ b/studio/static/doc/flask-docs/_images/debugger3.png Binary files differ diff --git a/studio/static/doc/flask-docs/_images/debugger4.png b/studio/static/doc/flask-docs/_images/debugger4.png new file mode 100644 index 0000000..4f47229 --- /dev/null +++ b/studio/static/doc/flask-docs/_images/debugger4.png Binary files differ diff --git a/studio/static/doc/flask-docs/_images/debugger5.png b/studio/static/doc/flask-docs/_images/debugger5.png new file mode 100644 index 0000000..4f47229 --- /dev/null +++ b/studio/static/doc/flask-docs/_images/debugger5.png Binary files differ diff --git a/studio/static/doc/flask-docs/_images/flaskr.png b/studio/static/doc/flask-docs/_images/flaskr.png new file mode 100644 index 0000000..07d027d --- /dev/null +++ b/studio/static/doc/flask-docs/_images/flaskr.png Binary files differ diff --git a/studio/static/doc/flask-docs/_images/logo-full.png b/studio/static/doc/flask-docs/_images/logo-full.png new file mode 100644 index 0000000..5deaf1b --- /dev/null +++ b/studio/static/doc/flask-docs/_images/logo-full.png Binary files differ diff --git a/studio/static/doc/flask-docs/_images/logo-full1.png b/studio/static/doc/flask-docs/_images/logo-full1.png new file mode 100644 index 0000000..5deaf1b --- /dev/null +++ b/studio/static/doc/flask-docs/_images/logo-full1.png Binary files differ diff --git a/studio/static/doc/flask-docs/_images/logo-full2.png b/studio/static/doc/flask-docs/_images/logo-full2.png new file mode 100644 index 0000000..5deaf1b --- /dev/null +++ b/studio/static/doc/flask-docs/_images/logo-full2.png Binary files differ diff --git a/studio/static/doc/flask-docs/_images/no.png b/studio/static/doc/flask-docs/_images/no.png new file mode 100644 index 0000000..4ac1083 --- /dev/null +++ b/studio/static/doc/flask-docs/_images/no.png Binary files differ diff --git a/studio/static/doc/flask-docs/_images/yes.png b/studio/static/doc/flask-docs/_images/yes.png new file mode 100644 index 0000000..ac27c4e --- /dev/null +++ b/studio/static/doc/flask-docs/_images/yes.png Binary files differ diff --git a/studio/static/doc/flask-docs/_sources/api.txt b/studio/static/doc/flask-docs/_sources/api.txt new file mode 100644 index 0000000..7695788 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/api.txt @@ -0,0 +1,624 @@ +.. _api: + +API +=== + +.. module:: flask + +This part of the documentation covers all the interfaces of Flask. For +parts where Flask depends on external libraries, we document the most +important right here and provide links to the canonical documentation. + + +Application Object +------------------ + +.. autoclass:: Flask + :members: + :inherited-members: + + +Blueprint Objects +----------------- + +.. autoclass:: Blueprint + :members: + :inherited-members: + +Incoming Request Data +--------------------- + +.. autoclass:: Request + :members: + + .. attribute:: form + + A :class:`~werkzeug.datastructures.MultiDict` with the parsed form data from `POST` + or `PUT` requests. Please keep in mind that file uploads will not + end up here, but instead in the :attr:`files` attribute. + + .. attribute:: args + + A :class:`~werkzeug.datastructures.MultiDict` with the parsed contents of the query + string. (The part in the URL after the question mark). + + .. attribute:: values + + A :class:`~werkzeug.datastructures.CombinedMultiDict` with the contents of both + :attr:`form` and :attr:`args`. + + .. attribute:: cookies + + A :class:`dict` with the contents of all cookies transmitted with + the request. + + .. attribute:: stream + + If the incoming form data was not encoded with a known mimetype + the data is stored unmodified in this stream for consumption. Most + of the time it is a better idea to use :attr:`data` which will give + you that data as a string. The stream only returns the data once. + + .. attribute:: headers + + The incoming request headers as a dictionary like object. + + .. attribute:: data + + Contains the incoming request data as string in case it came with + a mimetype Flask does not handle. + + .. attribute:: files + + A :class:`~werkzeug.datastructures.MultiDict` with files uploaded as part of a + `POST` or `PUT` request. Each file is stored as + :class:`~werkzeug.datastructures.FileStorage` object. It basically behaves like a + standard file object you know from Python, with the difference that + it also has a :meth:`~werkzeug.datastructures.FileStorage.save` function that can + store the file on the filesystem. + + .. attribute:: environ + + The underlying WSGI environment. + + .. attribute:: method + + The current request method (``POST``, ``GET`` etc.) + + .. attribute:: path + .. attribute:: script_root + .. attribute:: url + .. attribute:: base_url + .. attribute:: url_root + + Provides different ways to look at the current URL. Imagine your + application is listening on the following URL:: + + http://www.example.com/myapplication + + And a user requests the following URL:: + + http://www.example.com/myapplication/page.html?x=y + + In this case the values of the above mentioned attributes would be + the following: + + ============= ====================================================== + `path` ``/page.html`` + `script_root` ``/myapplication`` + `base_url` ``http://www.example.com/myapplication/page.html`` + `url` ``http://www.example.com/myapplication/page.html?x=y`` + `url_root` ``http://www.example.com/myapplication/`` + ============= ====================================================== + + .. attribute:: is_xhr + + `True` if the request was triggered via a JavaScript + `XMLHttpRequest`. This only works with libraries that support the + ``X-Requested-With`` header and set it to `XMLHttpRequest`. + Libraries that do that are prototype, jQuery and Mochikit and + probably some more. + +.. class:: request + + To access incoming request data, you can use the global `request` + object. Flask parses incoming request data for you and gives you + access to it through that global object. Internally Flask makes + sure that you always get the correct data for the active thread if you + are in a multithreaded environment. + + This is a proxy. See :ref:`notes-on-proxies` for more information. + + The request object is an instance of a :class:`~werkzeug.wrappers.Request` + subclass and provides all of the attributes Werkzeug defines. This + just shows a quick overview of the most important ones. + + +Response Objects +---------------- + +.. autoclass:: flask.Response + :members: set_cookie, data, mimetype + + .. attribute:: headers + + A :class:`Headers` object representing the response headers. + + .. attribute:: status_code + + The response status as integer. + + +Sessions +-------- + +If you have the :attr:`Flask.secret_key` set you can use sessions in Flask +applications. A session basically makes it possible to remember +information from one request to another. The way Flask does this is by +using a signed cookie. So the user can look at the session contents, but +not modify it unless they know the secret key, so make sure to set that +to something complex and unguessable. + +To access the current session you can use the :class:`session` object: + +.. class:: session + + The session object works pretty much like an ordinary dict, with the + difference that it keeps track on modifications. + + This is a proxy. See :ref:`notes-on-proxies` for more information. + + The following attributes are interesting: + + .. attribute:: new + + `True` if the session is new, `False` otherwise. + + .. attribute:: modified + + `True` if the session object detected a modification. Be advised + that modifications on mutable structures are not picked up + automatically, in that situation you have to explicitly set the + attribute to `True` yourself. Here an example:: + + # this change is not picked up because a mutable object (here + # a list) is changed. + session['objects'].append(42) + # so mark it as modified yourself + session.modified = True + + .. attribute:: permanent + + If set to `True` the session lives for + :attr:`~flask.Flask.permanent_session_lifetime` seconds. The + default is 31 days. If set to `False` (which is the default) the + session will be deleted when the user closes the browser. + + +Session Interface +----------------- + +.. versionadded:: 0.8 + +The session interface provides a simple way to replace the session +implementation that Flask is using. + +.. currentmodule:: flask.sessions + +.. autoclass:: SessionInterface + :members: + +.. autoclass:: SecureCookieSessionInterface + :members: + +.. autoclass:: NullSession + :members: + +.. autoclass:: SessionMixin + :members: + +.. admonition:: Notice + + The ``PERMANENT_SESSION_LIFETIME`` config key can also be an integer + starting with Flask 0.8. Either catch this down yourself or use + the :attr:`~flask.Flask.permanent_session_lifetime` attribute on the + app which converts the result to an integer automatically. + + +Test Client +----------- + +.. currentmodule:: flask.testing + +.. autoclass:: FlaskClient + :members: + + +Application Globals +------------------- + +.. currentmodule:: flask + +To share data that is valid for one request only from one function to +another, a global variable is not good enough because it would break in +threaded environments. Flask provides you with a special object that +ensures it is only valid for the active request and that will return +different values for each request. In a nutshell: it does the right +thing, like it does for :class:`request` and :class:`session`. + +.. data:: g + + Just store on this whatever you want. For example a database + connection or the user that is currently logged in. + + This is a proxy. See :ref:`notes-on-proxies` for more information. + + +Useful Functions and Classes +---------------------------- + +.. data:: current_app + + Points to the application handling the request. This is useful for + extensions that want to support multiple applications running side + by side. + + This is a proxy. See :ref:`notes-on-proxies` for more information. + +.. autofunction:: has_request_context + +.. autofunction:: url_for + +.. function:: abort(code) + + Raises an :exc:`~werkzeug.exceptions.HTTPException` for the given + status code. For example to abort request handling with a page not + found exception, you would call ``abort(404)``. + + :param code: the HTTP error code. + +.. autofunction:: redirect + +.. autofunction:: make_response + +.. autofunction:: send_file + +.. autofunction:: send_from_directory + +.. autofunction:: safe_join + +.. autofunction:: escape + +.. autoclass:: Markup + :members: escape, unescape, striptags + +Message Flashing +---------------- + +.. autofunction:: flash + +.. autofunction:: get_flashed_messages + +Returning JSON +-------------- + +.. autofunction:: jsonify + +.. data:: json + + If JSON support is picked up, this will be the module that Flask is + using to parse and serialize JSON. So instead of doing this yourself:: + + try: + import simplejson as json + except ImportError: + import json + + You can instead just do this:: + + from flask import json + + For usage examples, read the :mod:`json` documentation. + + The :func:`~json.dumps` function of this json module is also available + as filter called ``|tojson`` in Jinja2. Note that inside `script` + tags no escaping must take place, so make sure to disable escaping + with ``|safe`` if you intend to use it inside `script` tags: + + .. sourcecode:: html+jinja + + + + Note that the ``|tojson`` filter escapes forward slashes properly. + +Template Rendering +------------------ + +.. autofunction:: render_template + +.. autofunction:: render_template_string + +.. autofunction:: get_template_attribute + +Configuration +------------- + +.. autoclass:: Config + :members: + +Extensions +---------- + +.. data:: flask.ext + + This module acts as redirect import module to Flask extensions. It was + added in 0.8 as the canonical way to import Flask extensions and makes + it possible for us to have more flexibility in how we distribute + extensions. + + If you want to use an extension named “Flask-Foo” you would import it + from :data:`~flask.ext` as follows:: + + from flask.ext import foo + + .. versionadded:: 0.8 + +Useful Internals +---------------- + +.. autoclass:: flask.ctx.RequestContext + :members: + +.. data:: _request_ctx_stack + + The internal :class:`~werkzeug.local.LocalStack` that is used to implement + all the context local objects used in Flask. This is a documented + instance and can be used by extensions and application code but the + use is discouraged in general. + + The following attributes are always present on each layer of the + stack: + + `app` + the active Flask application. + + `url_adapter` + the URL adapter that was used to match the request. + + `request` + the current request object. + + `session` + the active session object. + + `g` + an object with all the attributes of the :data:`flask.g` object. + + `flashes` + an internal cache for the flashed messages. + + Example usage:: + + from flask import _request_ctx_stack + + def get_session(): + ctx = _request_ctx_stack.top + if ctx is not None: + return ctx.session + +.. autoclass:: flask.blueprints.BlueprintSetupState + :members: + +Signals +------- + +.. when modifying this list, also update the one in signals.rst + +.. versionadded:: 0.6 + +.. data:: signals_available + + `True` if the signalling system is available. This is the case + when `blinker`_ is installed. + +.. data:: template_rendered + + This signal is sent when a template was successfully rendered. The + signal is invoked with the instance of the template as `template` + and the context as dictionary (named `context`). + +.. data:: request_started + + This signal is sent before any request processing started but when the + request context was set up. Because the request context is already + bound, the subscriber can access the request with the standard global + proxies such as :class:`~flask.request`. + +.. data:: request_finished + + This signal is sent right before the response is sent to the client. + It is passed the response to be sent named `response`. + +.. data:: got_request_exception + + This signal is sent when an exception happens during request processing. + It is sent *before* the standard exception handling kicks in and even + in debug mode, where no exception handling happens. The exception + itself is passed to the subscriber as `exception`. + +.. data:: request_tearing_down + + This signal is sent when the application is tearing down the request. + This is always called, even if an error happened. No arguments are + provided. + +.. currentmodule:: None + +.. class:: flask.signals.Namespace + + An alias for :class:`blinker.base.Namespace` if blinker is available, + otherwise a dummy class that creates fake signals. This class is + available for Flask extensions that want to provide the same fallback + system as Flask itself. + + .. method:: signal(name, doc=None) + + Creates a new signal for this namespace if blinker is available, + otherwise returns a fake signal that has a send method that will + do nothing but will fail with a :exc:`RuntimeError` for all other + operations, including connecting. + +.. _blinker: http://pypi.python.org/pypi/blinker + +Class Based Views +----------------- + +.. versionadded:: 0.7 + +.. currentmodule:: None + +.. autoclass:: flask.views.View + :members: + +.. autoclass:: flask.views.MethodView + :members: + +.. _url-route-registrations: + +URL Route Registrations +----------------------- + +Generally there are three ways to define rules for the routing system: + +1. You can use the :meth:`flask.Flask.route` decorator. +2. You can use the :meth:`flask.Flask.add_url_rule` function. +3. You can directly access the underlying Werkzeug routing system + which is exposed as :attr:`flask.Flask.url_map`. + +Variable parts in the route can be specified with angular brackets +(``/user/``). By default a variable part in the URL accepts any +string without a slash however a different converter can be specified as +well by using ````. + +Variable parts are passed to the view function as keyword arguments. + +The following converters are available: + +=========== =============================================== +`unicode` accepts any text without a slash (the default) +`int` accepts integers +`float` like `int` but for floating point values +`path` like the default but also accepts slashes +=========== =============================================== + +Here are some examples:: + + @app.route('/') + def index(): + pass + + @app.route('/') + def show_user(username): + pass + + @app.route('/post/') + def show_post(post_id): + pass + +An important detail to keep in mind is how Flask deals with trailing +slashes. The idea is to keep each URL unique so the following rules +apply: + +1. If a rule ends with a slash and is requested without a slash by the + user, the user is automatically redirected to the same page with a + trailing slash attached. +2. If a rule does not end with a trailing slash and the user requests the + page with a trailing slash, a 404 not found is raised. + +This is consistent with how web servers deal with static files. This +also makes it possible to use relative link targets safely. + +You can also define multiple rules for the same function. They have to be +unique however. Defaults can also be specified. Here for example is a +definition for a URL that accepts an optional page:: + + @app.route('/users/', defaults={'page': 1}) + @app.route('/users/page/') + def show_users(page): + pass + +This specifies that ``/users/`` will be the URL for page one and +``/users/page/N`` will be the URL for page `N`. + +Here are the parameters that :meth:`~flask.Flask.route` and +:meth:`~flask.Flask.add_url_rule` accept. The only difference is that +with the route parameter the view function is defined with the decorator +instead of the `view_func` parameter. + +=============== ========================================================== +`rule` the URL roule as string +`endpoint` the endpoint for the registered URL rule. Flask itself + assumes that the name of the view function is the name + of the endpoint if not explicitly stated. +`view_func` the function to call when serving a request to the + provided endpoint. If this is not provided one can + specify the function later by storing it in the + :attr:`~flask.Flask.view_functions` dictionary with the + endpoint as key. +`defaults` A dictionary with defaults for this rule. See the + example above for how defaults work. +`subdomain` specifies the rule for the subdomain in case subdomain + matching is in use. If not specified the default + subdomain is assumed. +`**options` the options to be forwarded to the underlying + :class:`~werkzeug.routing.Rule` object. A change to + Werkzeug is handling of method options. methods is a list + of methods this rule should be limited to (`GET`, `POST` + etc.). By default a rule just listens for `GET` (and + implicitly `HEAD`). Starting with Flask 0.6, `OPTIONS` is + implicitly added and handled by the standard request + handling. They have to be specified as keyword arguments. +=============== ========================================================== + +.. _view-func-options: + +View Function Options +--------------------- + +For internal usage the view functions can have some attributes attached to +customize behavior the view function would normally not have control over. +The following attributes can be provided optionally to either override +some defaults to :meth:`~flask.Flask.add_url_rule` or general behavior: + +- `__name__`: The name of a function is by default used as endpoint. If + endpoint is provided explicitly this value is used. Additionally this + will be prefixed with the name of the blueprint by default which + cannot be customized from the function itself. + +- `methods`: If methods are not provided when the URL rule is added, + Flask will look on the view function object itself is an `methods` + attribute exists. If it does, it will pull the information for the + methods from there. + +- `provide_automatic_options`: if this attribute is set Flask will + either force enable or disable the automatic implementation of the + HTTP `OPTIONS` response. This can be useful when working with + decorators that want to customize the `OPTIONS` response on a per-view + basis. + +Full example:: + + def index(): + if request.method == 'OPTIONS': + # custom options handling here + ... + return 'Hello World!' + index.provide_automatic_options = False + index.methods = ['GET', 'OPTIONS'] + + app.add_url_rule('/', index) + +.. versionadded:: 0.8 + The `provide_automatic_options` functionality was added. diff --git a/studio/static/doc/flask-docs/_sources/becomingbig.txt b/studio/static/doc/flask-docs/_sources/becomingbig.txt new file mode 100644 index 0000000..20a0186 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/becomingbig.txt @@ -0,0 +1,88 @@ +.. _becomingbig: + +Becoming Big +============ + +Your application is becoming more and more complex? If you suddenly +realize that Flask does things in a way that does not work out for your +application there are ways to deal with that. + +Flask is powered by Werkzeug and Jinja2, two libraries that are in use at +a number of large websites out there and all Flask does is bring those +two together. Being a microframework Flask does not do much more than +combining existing libraries - there is not a lot of code involved. +What that means for large applications is that it's very easy to take the +code from Flask and put it into a new module within the applications and +expand on that. + +Flask is designed to be extended and modified in a couple of different +ways: + +- Flask extensions. For a lot of reusable functionality you can create + extensions. For extensions a number of hooks exist throughout Flask + with signals and callback functions. + +- Subclassing. The majority of functionality can be changed by creating + a new subclass of the :class:`~flask.Flask` class and overriding + methods provided for this exact purpose. + +- Forking. If nothing else works out you can just take the Flask + codebase at a given point and copy/paste it into your application + and change it. Flask is designed with that in mind and makes this + incredible easy. You just have to take the package and copy it + into your application's code and rename it (for example to + `framework`). Then you can start modifying the code in there. + +Why consider Forking? +--------------------- + +The majority of code of Flask is within Werkzeug and Jinja2. These +libraries do the majority of the work. Flask is just the paste that glues +those together. For every project there is the point where the underlying +framework gets in the way (due to assumptions the original developers +had). This is natural because if this would not be the case, the +framework would be a very complex system to begin with which causes a +steep learning curve and a lot of user frustration. + +This is not unique to Flask. Many people use patched and modified +versions of their framework to counter shortcomings. This idea is also +reflected in the license of Flask. You don't have to contribute any +changes back if you decide to modify the framework. + +The downside of forking is of course that Flask extensions will most +likely break because the new framework has a different import name. +Furthermore integrating upstream changes can be a complex process, +depending on the number of changes. Because of that, forking should be +the very last resort. + +Scaling like a Pro +------------------ + +For many web applications the complexity of the code is less an issue than +the scaling for the number of users or data entries expected. Flask by +itself is only limited in terms of scaling by your application code, the +data store you want to use and the Python implementation and webserver you +are running on. + +Scaling well means for example that if you double the amount of servers +you get about twice the performance. Scaling bad means that if you add a +new server the application won't perform any better or would not even +support a second server. + +There is only one limiting factor regarding scaling in Flask which are +the context local proxies. They depend on context which in Flask is +defined as being either a thread, process or greenlet. If your server +uses some kind of concurrency that is not based on threads or greenlets, +Flask will no longer be able to support these global proxies. However the +majority of servers are using either threads, greenlets or separate +processes to achieve concurrency which are all methods well supported by +the underlying Werkzeug library. + +Dialogue with the Community +--------------------------- + +The Flask developers are very interested to keep everybody happy, so as +soon as you find an obstacle in your way, caused by Flask, don't hesitate +to contact the developers on the mailinglist or IRC channel. The best way +for the Flask and Flask-extension developers to improve it for larger +applications is getting feedback from users. diff --git a/studio/static/doc/flask-docs/_sources/blueprints.txt b/studio/static/doc/flask-docs/_sources/blueprints.txt new file mode 100644 index 0000000..9422fd0 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/blueprints.txt @@ -0,0 +1,203 @@ +.. _blueprints: + +Modular Applications with Blueprints +==================================== + +.. versionadded:: 0.7 + +Flask uses a concept of *blueprints* for making application components and +supporting common patterns within an application or across applications. +Blueprints can greatly simplify how large applications work and provide a +central means for Flask extensions to register operations on applications. +A :class:`Blueprint` object works similarly to a :class:`Flask` +application object, but it is not actually an application. Rather it is a +*blueprint* of how to construct or extend an application. + +Why Blueprints? +--------------- + +Blueprints in Flask are intended for these cases: + +* Factor an application into a set of blueprints. This is ideal for + larger applications; a project could instantiate an application object, + initialize several extensions, and register a collection of blueprints. +* Register a blueprint on an application at a URL prefix and/or subdomain. + Parameters in the URL prefix/subdomain become common view arguments + (with defaults) across all view functions in the blueprint. +* Register a blueprint multiple times on an application with different URL + rules. +* Provide template filters, static files, templates, and other utilities + through blueprints. A blueprint does not have to implement applications + or view functions. +* Register a blueprint on an application for any of these cases when + initializing a Flask extension. + +A blueprint in Flask is not a pluggable app because it is not actually an +application -- it's a set of operations which can be registered on an +application, even multiple times. Why not have multiple application +objects? You can do that (see :ref:`app-dispatch`), but your applications +will have separate configs and will be managed at the WSGI layer. + +Blueprints instead provide separation at the Flask level, share +application config, and can change an application object as necessary with +being registered. The downside is that you cannot unregister a blueprint +once an application was created without having to destroy the whole +application object. + +The Concept of Blueprints +------------------------- + +The basic concept of blueprints is that they record operations to execute +when registered on an application. Flask associates view functions with +blueprints when dispatching requests and generating URLs from one endpoint +to another. + +My First Blueprint +------------------ + +This is what a very basic blueprint looks like. In this case we want to +implement a blueprint that does simple rendering of static templates:: + + from flask import Blueprint, render_template, abort + from jinja2 import TemplateNotFound + + simple_page = Blueprint('simple_page', __name__) + + @simple_page.route('/', defaults={'page': 'index'}) + @simple_page.route('/') + def show(page): + try: + return render_template('pages/%s.html' % page) + except TemplateNotFound: + abort(404) + +When you bind a function with the help of the ``@simple_page.route`` +decorator the blueprint will record the intention of registering the +function `show` on the application when it's later registered. +Additionally it will prefix the endpoint of the function with the +name of the blueprint which was given to the :class:`Blueprint` +constructor (in this case also ``simple_page``). + +Registering Blueprints +---------------------- + +So how do you register that blueprint? Like this:: + + from flask import Flask + from yourapplication.simple_page import simple_page + + app = Flask(__name__) + app.register_blueprint(simple_page) + +If you check the rules registered on the application, you will find +these:: + + [' (HEAD, OPTIONS, GET) -> static>, + ' (HEAD, OPTIONS, GET) -> simple_page.show>, + simple_page.show>] + +The first one is obviously from the application ifself for the static +files. The other two are for the `show` function of the ``simple_page`` +blueprint. As you can see, they are also prefixed with the name of the +blueprint and separated by a dot (``.``). + +Blueprints however can also be mounted at different locations:: + + app.register_blueprint(simple_page, url_prefix='/pages') + +And sure enough, these are the generated rules:: + + [' (HEAD, OPTIONS, GET) -> static>, + ' (HEAD, OPTIONS, GET) -> simple_page.show>, + simple_page.show>] + +On top of that you can register blueprints multiple times though not every +blueprint might respond properly to that. In fact it depends on how the +blueprint is implemented if it can be mounted more than once. + +Blueprint Resources +------------------- + +Blueprints can provide resources as well. Sometimes you might want to +introduce a blueprint only for the resources it provides. + +Blueprint Resource Folder +````````````````````````` + +Like for regular applications, blueprints are considered to be contained +in a folder. While multiple blueprints can originate from the same folder, +it does not have to be the case and it's usually not recommended. + +The folder is inferred from the second argument to :class:`Blueprint` which +is usually `__name__`. This argument specifies what logical Python +module or package corresponds to the blueprint. If it points to an actual +Python package that package (which is a folder on the filesystem) is the +resource folder. If it's a module, the package the module is contained in +will be the resource folder. You can access the +:attr:`Blueprint.root_path` property to see what the resource folder is:: + + >>> simple_page.root_path + '/Users/username/TestProject/yourapplication' + +To quickly open sources from this folder you can use the +:meth:`~Blueprint.open_resource` function:: + + with simple_page.open_resource('static/style.css') as f: + code = f.read() + +Static Files +```````````` + +A blueprint can expose a folder with static files by providing a path to a +folder on the filesystem via the `static_folder` keyword argument. It can +either be an absolute path or one relative to the folder of the +blueprint:: + + admin = Blueprint('admin', __name__, static_folder='static') + +By default the rightmost part of the path is where it is exposed on the +web. Because the folder is called ``static`` here it will be available at +the location of the blueprint + ``/static``. Say the blueprint is +registered for ``/admin`` the static folder will be at ``/admin/static``. + +The endpoint is named `blueprint_name.static` so you can generate URLs to +it like you would do to the static folder of the application:: + + url_for('admin.static', filename='style.css') + +Templates +````````` + +If you want the blueprint to expose templates you can do that by providing +the `template_folder` parameter to the :class:`Blueprint` constructor:: + + admin = Blueprint('admin', __name__, template_folder='templates') + +As for static files, the path can be absolute or relative to the blueprint +resource folder. The template folder is added to the searchpath of +templates but with a lower priority than the actual application's template +folder. That way you can easily override templates that a blueprint +provides in the actual application. + +So if you have a blueprint in the folder ``yourapplication/admin`` and you +want to render the template ``'admin/index.html'`` and you have provided +``templates`` as a `template_folder` you will have to create a file like +this: ``yourapplication/admin/templates/admin/index.html``. + +Building URLs +------------- + +If you want to link from one page to another you can use the +:func:`url_for` function just like you normally would do just that you +prefix the URL endpoint with the name of the blueprint and a dot (``.``):: + + url_for('admin.index') + +Additionally if you are in a view function of a blueprint or a rendered +template and you want to link to another endpoint of the same blueprint, +you can use relative redirects by prefixing the endpoint with a dot only:: + + url_for('.index') + +This will link to ``admin.index`` for instance in case the current request +was dispatched to any other admin blueprint endpoint. diff --git a/studio/static/doc/flask-docs/_sources/changelog.txt b/studio/static/doc/flask-docs/_sources/changelog.txt new file mode 100644 index 0000000..d6c5f48 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/changelog.txt @@ -0,0 +1 @@ +.. include:: ../CHANGES diff --git a/studio/static/doc/flask-docs/_sources/config.txt b/studio/static/doc/flask-docs/_sources/config.txt new file mode 100644 index 0000000..ca724dc --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/config.txt @@ -0,0 +1,384 @@ +.. _config: + +Configuration Handling +====================== + +.. versionadded:: 0.3 + +Applications need some kind of configuration. There are different settings +you might want to change depending on the application environment like +toggling the debug mode, setting the secret key, and other such +environment-specific things. + +The way Flask is designed usually requires the configuration to be +available when the application starts up. You can hardcode the +configuration in the code, which for many small applications is not +actually that bad, but there are better ways. + +Independent of how you load your config, there is a config object +available which holds the loaded configuration values: +The :attr:`~flask.Flask.config` attribute of the :class:`~flask.Flask` +object. This is the place where Flask itself puts certain configuration +values and also where extensions can put their configuration values. But +this is also where you can have your own configuration. + +Configuration Basics +-------------------- + +The :attr:`~flask.Flask.config` is actually a subclass of a dictionary and +can be modified just like any dictionary:: + + app = Flask(__name__) + app.config['DEBUG'] = True + +Certain configuration values are also forwarded to the +:attr:`~flask.Flask` object so you can read and write them from there:: + + app.debug = True + +To update multiple keys at once you can use the :meth:`dict.update` +method:: + + app.config.update( + DEBUG=True, + SECRET_KEY='...' + ) + +Builtin Configuration Values +---------------------------- + +The following configuration values are used internally by Flask: + +.. tabularcolumns:: |p{6.5cm}|p{8.5cm}| + +================================= ========================================= +``DEBUG`` enable/disable debug mode +``TESTING`` enable/disable testing mode +``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the + propagation of exceptions. If not set or + explicitly set to `None` this is + implicitly true if either `TESTING` or + `DEBUG` is true. +``PRESERVE_CONTEXT_ON_EXCEPTION`` By default if the application is in + debug mode the request context is not + popped on exceptions to enable debuggers + to introspect the data. This can be + disabled by this key. You can also use + this setting to force-enable it for non + debug execution which might be useful to + debug production applications (but also + very risky). +``SECRET_KEY`` the secret key +``SESSION_COOKIE_NAME`` the name of the session cookie +``SESSION_COOKIE_DOMAIN`` the domain for the session cookie. If + this is not set, the cookie will be + valid for all subdomains of + ``SERVER_NAME``. +``SESSION_COOKIE_PATH`` the path for the session cookie. If + this is not set the cookie will be valid + for all of ``APPLICATION_ROOT`` or if + that is not set for ``'/'``. +``SESSION_COOKIE_HTTPONLY`` controls if the cookie should be set + with the httponly flag. Defaults to + `True`. +``SESSION_COOKIE_SECURE`` controls if the cookie should be set + with the secure flag. Defaults to + `False`. +``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as + :class:`datetime.timedelta` object. + Starting with Flask 0.8 this can also be + an integer representing seconds. +``USE_X_SENDFILE`` enable/disable x-sendfile +``LOGGER_NAME`` the name of the logger +``SERVER_NAME`` the name and port number of the server. + Required for subdomain support (e.g.: + ``'myapp.dev:5000'``) Note that + localhost does not support subdomains so + setting this to “localhost” does not + help. +``APPLICATION_ROOT`` If the application does not occupy + a whole domain or subdomain this can + be set to the path where the application + is configured to live. This is for + session cookie as path value. If + domains are used, this should be + ``None``. +``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will + reject incoming requests with a + content length greater than this by + returning a 413 status code. +``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will + not execute the error handlers of HTTP + exceptions but instead treat the + exception like any other and bubble it + through the exception stack. This is + helpful for hairy debugging situations + where you have to find out where an HTTP + exception is coming from. +``TRAP_BAD_REQUEST_ERRORS`` Werkzeug's internal data structures that + deal with request specific data will + raise special key errors that are also + bad request exceptions. Likewise many + operations can implicitly fail with a + BadRequest exception for consistency. + Since it's nice for debugging to know + why exactly it failed this flag can be + used to debug those situations. If this + config is set to ``True`` you will get + a regular traceback instead. +================================= ========================================= + +.. admonition:: More on ``SERVER_NAME`` + + The ``SERVER_NAME`` key is used for the subdomain support. Because + Flask cannot guess the subdomain part without the knowledge of the + actual server name, this is required if you want to work with + subdomains. This is also used for the session cookie. + + Please keep in mind that not only Flask has the problem of not knowing + what subdomains are, your web browser does as well. Most modern web + browsers will not allow cross-subdomain cookies to be set on a + server name without dots in it. So if your server name is + ``'localhost'`` you will not be able to set a cookie for + ``'localhost'`` and every subdomain of it. Please chose a different + server name in that case, like ``'myapplication.local'`` and add + this name + the subdomains you want to use into your host config + or setup a local `bind`_. + +.. _bind: https://www.isc.org/software/bind + +.. versionadded:: 0.4 + ``LOGGER_NAME`` + +.. versionadded:: 0.5 + ``SERVER_NAME`` + +.. versionadded:: 0.6 + ``MAX_CONTENT_LENGTH`` + +.. versionadded:: 0.7 + ``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION`` + +.. versionadded:: 0.8 + ``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``, + ``APPLICATION_ROOT``, ``SESSION_COOKIE_DOMAIN``, + ``SESSION_COOKIE_PATH``, ``SESSION_COOKIE_HTTPONLY``, + ``SESSION_COOKIE_SECURE`` + +Configuring from Files +---------------------- + +Configuration becomes more useful if you can store it in a separate file, +ideally located outside the actual application package. This makes +packaging and distributing your application possible via various package +handling tools (:ref:`distribute-deployment`) and finally modifying the +configuration file afterwards. + +So a common pattern is this:: + + app = Flask(__name__) + app.config.from_object('yourapplication.default_settings') + app.config.from_envvar('YOURAPPLICATION_SETTINGS') + +This first loads the configuration from the +`yourapplication.default_settings` module and then overrides the values +with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS` +environment variable points to. This environment variable can be set on +Linux or OS X with the export command in the shell before starting the +server:: + + $ export YOURAPPLICATION_SETTINGS=/path/to/settings.cfg + $ python run-app.py + * Running on http://127.0.0.1:5000/ + * Restarting with reloader... + +On Windows systems use the `set` builtin instead:: + + >set YOURAPPLICATION_SETTINGS=\path\to\settings.cfg + +The configuration files themselves are actual Python files. Only values +in uppercase are actually stored in the config object later on. So make +sure to use uppercase letters for your config keys. + +Here is an example of a configuration file:: + + # Example configuration + DEBUG = False + SECRET_KEY = '?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83' + +Make sure to load the configuration very early on, so that extensions have +the ability to access the configuration when starting up. There are other +methods on the config object as well to load from individual files. For a +complete reference, read the :class:`~flask.Config` object's +documentation. + + +Configuration Best Practices +---------------------------- + +The downside with the approach mentioned earlier is that it makes testing +a little harder. There is no single 100% solution for this problem in +general, but there are a couple of things you can keep in mind to improve +that experience: + +1. create your application in a function and register blueprints on it. + That way you can create multiple instances of your application with + different configurations attached which makes unittesting a lot + easier. You can use this to pass in configuration as needed. + +2. Do not write code that needs the configuration at import time. If you + limit yourself to request-only accesses to the configuration you can + reconfigure the object later on as needed. + + +Development / Production +------------------------ + +Most applications need more than one configuration. There should be at +least separate configurations for the production server and the one used +during development. The easiest way to handle this is to use a default +configuration that is always loaded and part of the version control, and a +separate configuration that overrides the values as necessary as mentioned +in the example above:: + + app = Flask(__name__) + app.config.from_object('yourapplication.default_settings') + app.config.from_envvar('YOURAPPLICATION_SETTINGS') + +Then you just have to add a separate `config.py` file and export +``YOURAPPLICATION_SETTINGS=/path/to/config.py`` and you are done. However +there are alternative ways as well. For example you could use imports or +subclassing. + +What is very popular in the Django world is to make the import explicit in +the config file by adding an ``from yourapplication.default_settings +import *`` to the top of the file and then overriding the changes by hand. +You could also inspect an environment variable like +``YOURAPPLICATION_MODE`` and set that to `production`, `development` etc +and import different hardcoded files based on that. + +An interesting pattern is also to use classes and inheritance for +configuration:: + + class Config(object): + DEBUG = False + TESTING = False + DATABASE_URI = 'sqlite://:memory:' + + class ProductionConfig(Config): + DATABASE_URI = 'mysql://user@localhost/foo' + + class DevelopmentConfig(Config): + DEBUG = True + + class TestingConfig(Config): + TESTING = True + +To enable such a config you just have to call into +:meth:`~flask.Config.from_object`:: + + app.config.from_object('configmodule.ProductionConfig') + +There are many different ways and it's up to you how you want to manage +your configuration files. However here a list of good recommendations: + +- keep a default configuration in version control. Either populate the + config with this default configuration or import it in your own + configuration files before overriding values. +- use an environment variable to switch between the configurations. + This can be done from outside the Python interpreter and makes + development and deployment much easier because you can quickly and + easily switch between different configs without having to touch the + code at all. If you are working often on different projects you can + even create your own script for sourcing that activates a virtualenv + and exports the development configuration for you. +- Use a tool like `fabric`_ in production to push code and + configurations separately to the production server(s). For some + details about how to do that, head over to the + :ref:`fabric-deployment` pattern. + +.. _fabric: http://fabfile.org/ + + +.. _instance-folders: + +Instance Folders +---------------- + +.. versionadded:: 0.8 + +Flask 0.8 introduces instance folders. Flask for a long time made it +possible to refer to paths relative to the application's folder directly +(via :attr:`Flask.root_path`). This was also how many developers loaded +configurations stored next to the application. Unfortunately however this +only works well if applications are not packages in which case the root +path refers to the contents of the package. + +With Flask 0.8 a new attribute was introduced: +:attr:`Flask.instance_path`. It refers to a new concept called the +“instance folder”. The instance folder is designed to not be under +version control and be deployment specific. It's the perfect place to +drop things that either change at runtime or configuration files. + +You can either explicitly provide the path of the instance folder when +creating the Flask application or you can let Flask autodetect the +instance folder. For explicit configuration use the `instance_path` +parameter:: + + app = Flask(__name__, instance_path='/path/to/instance/folder') + +Please keep in mind that this path *must* be absolute when provided. + +If the `instance_path` parameter is not provided the following default +locations are used: + +- Uninstalled module:: + + /myapp.py + /instance + +- Uninstalled package:: + + /myapp + /__init__.py + /instance + +- Installed module or package:: + + $PREFIX/lib/python2.X/site-packages/myapp + $PREFIX/var/myapp-instance + + ``$PREFIX`` is the prefix of your Python installation. This can be + ``/usr`` or the path to your virtualenv. You can print the value of + ``sys.prefix`` to see what the prefix is set to. + +Since the config object provided loading of configuration files from +relative filenames we made it possible to change the loading via filenames +to be relative to the instance path if wanted. The behavior of relative +paths in config files can be flipped between “relative to the application +root” (the default) to “relative to instance folder” via the +`instance_relative_config` switch to the application constructor:: + + app = Flask(__name__, instance_relative_config=True) + +Here is a full example of how to configure Flask to preload the config +from a module and then override the config from a file in the config +folder if it exists:: + + app = Flask(__name__, instance_relative_config=True) + app.config.from_object('yourapplication.default_settings') + app.config.from_pyfile('application.cfg', silent=True) + +The path to the instance folder can be found via the +:attr:`Flask.instance_path`. Flask also provides a shortcut to open a +file from the instance folder with :meth:`Flask.open_instance_resource`. + +Example usage for both:: + + filename = os.path.join(app.instance_root, 'application.cfg') + with open(filename) as f: + config = f.read() + + # or via open_instance_resource: + with app.open_instance_resource('application.cfg') as f: + config = f.read() diff --git a/studio/static/doc/flask-docs/_sources/deploying/cgi.txt b/studio/static/doc/flask-docs/_sources/deploying/cgi.txt new file mode 100644 index 0000000..a2fba90 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/deploying/cgi.txt @@ -0,0 +1,46 @@ +CGI +=== + +If all other deployment methods do not work, CGI will work for sure. +CGI is supported by all major servers but usually has a sub-optimal +performance. + +This is also the way you can use a Flask application on Google's `App +Engine`_, where execution happens in a CGI-like environment. + +.. admonition:: Watch Out + + Please make sure in advance that any ``app.run()`` calls you might + have in your application file are inside an ``if __name__ == + '__main__':`` block or moved to a separate file. Just make sure it's + not called because this will always start a local WSGI server which + we do not want if we deploy that application to CGI / app engine. + +Creating a `.cgi` file +---------------------- + +First you need to create the CGI application file. Let's call it +`yourapplication.cgi`:: + + #!/usr/bin/python + from wsgiref.handlers import CGIHandler + from yourapplication import app + + CGIHandler().run(app) + +Server Setup +------------ + +Usually there are two ways to configure the server. Either just copy the +`.cgi` into a `cgi-bin` (and use `mod_rewrite` or something similar to +rewrite the URL) or let the server point to the file directly. + +In Apache for example you can put a like like this into the config: + +.. sourcecode:: apache + + ScriptAlias /app /path/to/the/application.cgi + +For more information consult the documentation of your webserver. + +.. _App Engine: http://code.google.com/appengine/ diff --git a/studio/static/doc/flask-docs/_sources/deploying/fastcgi.txt b/studio/static/doc/flask-docs/_sources/deploying/fastcgi.txt new file mode 100644 index 0000000..6dace1a --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/deploying/fastcgi.txt @@ -0,0 +1,164 @@ +.. _deploying-fastcgi: + +FastCGI +======= + +FastCGI is a deployment option on servers like `nginx`_, `lighttpd`_, +and `cherokee`_; see :ref:`deploying-uwsgi` and +:ref:`deploying-other-servers` for other options. To use your WSGI +application with any of them you will need a FastCGI server first. The +most popular one is `flup`_ which we will use for this guide. Make sure +to have it installed to follow along. + +.. admonition:: Watch Out + + Please make sure in advance that any ``app.run()`` calls you might + have in your application file are inside an ``if __name__ == + '__main__':`` block or moved to a separate file. Just make sure it's + not called because this will always start a local WSGI server which + we do not want if we deploy that application to FastCGI. + +Creating a `.fcgi` file +----------------------- + +First you need to create the FastCGI server file. Let's call it +`yourapplication.fcgi`:: + + #!/usr/bin/python + from flup.server.fcgi import WSGIServer + from yourapplication import app + + if __name__ == '__main__': + WSGIServer(app).run() + +This is enough for Apache to work, however nginx and older versions of +lighttpd need a socket to be explicitly passed to communicate with the +FastCGI server. For that to work you need to pass the path to the +socket to the :class:`~flup.server.fcgi.WSGIServer`:: + + WSGIServer(application, bindAddress='/path/to/fcgi.sock').run() + +The path has to be the exact same path you define in the server +config. + +Save the `yourapplication.fcgi` file somewhere you will find it again. +It makes sense to have that in `/var/www/yourapplication` or something +similar. + +Make sure to set the executable bit on that file so that the servers +can execute it: + +.. sourcecode:: text + + # chmod +x /var/www/yourapplication/yourapplication.fcgi + +Configuring lighttpd +-------------------- + +A basic FastCGI configuration for lighttpd looks like that:: + + fastcgi.server = ("/yourapplication.fcgi" => + (( + "socket" => "/tmp/yourapplication-fcgi.sock", + "bin-path" => "/var/www/yourapplication/yourapplication.fcgi", + "check-local" => "disable", + "max-procs" => 1 + )) + ) + + alias.url = ( + "/static/" => "/path/to/your/static" + ) + + url.rewrite-once = ( + "^(/static.*)$" => "$1", + "^(/.*)$" => "/yourapplication.fcgi$1" + +Remember to enable the FastCGI, alias and rewrite modules. This +configuration binds the application to `/yourapplication`. If you want +the application to work in the URL root you have to work around a +lighttpd bug with the +:class:`~werkzeug.contrib.fixers.LighttpdCGIRootFix` middleware. + +Make sure to apply it only if you are mounting the application the URL +root. Also, see the Lighty docs for more information on `FastCGI and +Python `_ +(note that explicitly passing a socket to run() is no longer necessary). + + +Configuring nginx +----------------- + +Installing FastCGI applications on nginx is a bit different because by +default no FastCGI parameters are forwarded. + +A basic flask FastCGI configuration for nginx looks like this:: + + location = /yourapplication { rewrite ^ /yourapplication/ last; } + location /yourapplication { try_files $uri @yourapplication; } + location @yourapplication { + include fastcgi_params; + fastcgi_split_path_info ^(/yourapplication)(.*)$; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_pass unix:/tmp/yourapplication-fcgi.sock; + } + +This configuration binds the application to `/yourapplication`. If you +want to have it in the URL root it's a bit simpler because you don't +have to figure out how to calculate `PATH_INFO` and `SCRIPT_NAME`:: + + location / { try_files $uri @yourapplication; } + location @yourapplication { + include fastcgi_params; + fastcgi_param PATH_INFO $fastcgi_script_name; + fastcgi_param SCRIPT_NAME ""; + fastcgi_pass unix:/tmp/yourapplication-fcgi.sock; + } + +Running FastCGI Processes +------------------------- + +Since Nginx and others do not load FastCGI apps, you have to do it by +yourself. `Supervisor can manage FastCGI processes. +`_ +You can look around for other FastCGI process managers or write a script +to run your `.fcgi` file at boot, e.g. using a SysV ``init.d`` script. +For a temporary solution, you can always run the ``.fcgi`` script inside +GNU screen. See ``man screen`` for details, and note that this is a +manual solution which does not persist across system restart:: + + $ screen + $ /var/www/yourapplication/yourapplication.fcgi + +Debugging +--------- + +FastCGI deployments tend to be hard to debug on most webservers. Very +often the only thing the server log tells you is something along the +lines of "premature end of headers". In order to debug the application +the only thing that can really give you ideas why it breaks is switching +to the correct user and executing the application by hand. + +This example assumes your application is called `application.fcgi` and +that your webserver user is `www-data`:: + + $ su www-data + $ cd /var/www/yourapplication + $ python application.fcgi + Traceback (most recent call last): + File "yourapplication.fcgi", line 4, in + ImportError: No module named yourapplication + +In this case the error seems to be "yourapplication" not being on the +python path. Common problems are: + +- Relative paths being used. Don't rely on the current working directory +- The code depending on environment variables that are not set by the + web server. +- Different python interpreters being used. + +.. _nginx: http://nginx.org/ +.. _lighttpd: http://www.lighttpd.net/ +.. _cherokee: http://www.cherokee-project.com/ +.. _flup: http://trac.saddi.com/flup diff --git a/studio/static/doc/flask-docs/_sources/deploying/index.txt b/studio/static/doc/flask-docs/_sources/deploying/index.txt new file mode 100644 index 0000000..d258df8 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/deploying/index.txt @@ -0,0 +1,23 @@ +.. _deployment: + +Deployment Options +================== + +Depending on what you have available there are multiple ways to run +Flask applications. You can use the builtin server during development, +but you should use a full deployment option for production applications. +(Do not use the builtin development server in production.) Several +options are available and documented here. + +If you have a different WSGI server look up the server documentation +about how to use a WSGI app with it. Just remember that your +:class:`Flask` application object is the actual WSGI application. + +.. toctree:: + :maxdepth: 2 + + mod_wsgi + cgi + fastcgi + uwsgi + others diff --git a/studio/static/doc/flask-docs/_sources/deploying/mod_wsgi.txt b/studio/static/doc/flask-docs/_sources/deploying/mod_wsgi.txt new file mode 100644 index 0000000..c85ed64 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/deploying/mod_wsgi.txt @@ -0,0 +1,167 @@ +.. _mod_wsgi-deployment: + +mod_wsgi (Apache) +================= + +If you are using the `Apache`_ webserver, consider using `mod_wsgi`_. + +.. admonition:: Watch Out + + Please make sure in advance that any ``app.run()`` calls you might + have in your application file are inside an ``if __name__ == + '__main__':`` block or moved to a separate file. Just make sure it's + not called because this will always start a local WSGI server which + we do not want if we deploy that application to mod_wsgi. + +.. _Apache: http://httpd.apache.org/ + +Installing `mod_wsgi` +--------------------- + +If you don't have `mod_wsgi` installed yet you have to either install it +using a package manager or compile it yourself. The mod_wsgi +`installation instructions`_ cover source installations on UNIX systems. + +If you are using Ubuntu/Debian you can apt-get it and activate it as +follows: + +.. sourcecode:: text + + # apt-get install libapache2-mod-wsgi + +On FreeBSD install `mod_wsgi` by compiling the `www/mod_wsgi` port or by +using pkg_add: + +.. sourcecode:: text + + # pkg_add -r mod_wsgi + +If you are using pkgsrc you can install `mod_wsgi` by compiling the +`www/ap2-wsgi` package. + +If you encounter segfaulting child processes after the first apache +reload you can safely ignore them. Just restart the server. + +Creating a `.wsgi` file +----------------------- + +To run your application you need a `yourapplication.wsgi` file. This file +contains the code `mod_wsgi` is executing on startup to get the application +object. The object called `application` in that file is then used as +application. + +For most applications the following file should be sufficient:: + + from yourapplication import app as application + +If you don't have a factory function for application creation but a singleton +instance you can directly import that one as `application`. + +Store that file somewhere that you will find it again (e.g.: +`/var/www/yourapplication`) and make sure that `yourapplication` and all +the libraries that are in use are on the python load path. If you don't +want to install it system wide consider using a `virtual python`_ +instance. + +Configuring Apache +------------------ + +The last thing you have to do is to create an Apache configuration file +for your application. In this example we are telling `mod_wsgi` to +execute the application under a different user for security reasons: + +.. sourcecode:: apache + + + ServerName example.com + + WSGIDaemonProcess yourapplication user=user1 group=group1 threads=5 + WSGIScriptAlias / /var/www/yourapplication/yourapplication.wsgi + + + WSGIProcessGroup yourapplication + WSGIApplicationGroup %{GLOBAL} + Order deny,allow + Allow from all + + + +For more information consult the `mod_wsgi wiki`_. + +.. _mod_wsgi: http://code.google.com/p/modwsgi/ +.. _installation instructions: http://code.google.com/p/modwsgi/wiki/QuickInstallationGuide +.. _virtual python: http://pypi.python.org/pypi/virtualenv +.. _mod_wsgi wiki: http://code.google.com/p/modwsgi/wiki/ + +Troubleshooting +--------------- + +If your application does not run, follow this guide to troubleshoot: + +**Problem:** application does not run, errorlog shows SystemExit ignored + You have a ``app.run()`` call in your application file that is not + guarded by an ``if __name__ == '__main__':`` condition. Either + remove that :meth:`~flask.Flask.run` call from the file and move it + into a separate `run.py` file or put it into such an if block. + +**Problem:** application gives permission errors + Probably caused by your application running as the wrong user. Make + sure the folders the application needs access to have the proper + privileges set and the application runs as the correct user + (``user`` and ``group`` parameter to the `WSGIDaemonProcess` + directive) + +**Problem:** application dies with an error on print + Keep in mind that mod_wsgi disallows doing anything with + :data:`sys.stdout` and :data:`sys.stderr`. You can disable this + protection from the config by setting the `WSGIRestrictStdout` to + ``off``: + + .. sourcecode:: apache + + WSGIRestrictStdout Off + + Alternatively you can also replace the standard out in the .wsgi file + with a different stream:: + + import sys + sys.stdout = sys.stderr + +**Problem:** accessing resources gives IO errors + Your application probably is a single .py file you symlinked into + the site-packages folder. Please be aware that this does not work, + instead you either have to put the folder into the pythonpath the + file is stored in, or convert your application into a package. + + The reason for this is that for non-installed packages, the module + filename is used to locate the resources and for symlinks the wrong + filename is picked up. + +Support for Automatic Reloading +------------------------------- + +To help deployment tools you can activate support for automatic +reloading. Whenever something changes the `.wsgi` file, `mod_wsgi` will +reload all the daemon processes for us. + +For that, just add the following directive to your `Directory` section: + +.. sourcecode:: apache + + WSGIScriptReloading On + +Working with Virtual Environments +--------------------------------- + +Virtual environments have the advantage that they never install the +required dependencies system wide so you have a better control over what +is used where. If you want to use a virtual environment with mod_wsgi +you have to modify your `.wsgi` file slightly. + +Add the following lines to the top of your `.wsgi` file:: + + activate_this = '/path/to/env/bin/activate_this.py' + execfile(activate_this, dict(__file__=activate_this)) + +This sets up the load paths according to the settings of the virtual +environment. Keep in mind that the path has to be absolute. diff --git a/studio/static/doc/flask-docs/_sources/deploying/others.txt b/studio/static/doc/flask-docs/_sources/deploying/others.txt new file mode 100644 index 0000000..6f3e5cc --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/deploying/others.txt @@ -0,0 +1,102 @@ +.. _deploying-other-servers: + +Other Servers +============= + +There are popular servers written in Python that allow the execution of WSGI +applications as well. These servers stand alone when they run; you can proxy +to them from your web server. + +Tornado +-------- + +`Tornado`_ is an open source version of the scalable, non-blocking web +server and tools that power `FriendFeed`_. Because it is non-blocking and +uses epoll, it can handle thousands of simultaneous standing connections, +which means it is ideal for real-time web services. Integrating this +service with Flask is a trivial task:: + + from tornado.wsgi import WSGIContainer + from tornado.httpserver import HTTPServer + from tornado.ioloop import IOLoop + from yourapplication import app + + http_server = HTTPServer(WSGIContainer(app)) + http_server.listen(5000) + IOLoop.instance().start() + + +.. _Tornado: http://www.tornadoweb.org/ +.. _FriendFeed: http://friendfeed.com/ + +Gevent +------- + +`Gevent`_ is a coroutine-based Python networking library that uses +`greenlet`_ to provide a high-level synchronous API on top of `libevent`_ +event loop:: + + from gevent.wsgi import WSGIServer + from yourapplication import app + + http_server = WSGIServer(('', 5000), app) + http_server.serve_forever() + +.. _Gevent: http://www.gevent.org/ +.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html +.. _libevent: http://monkey.org/~provos/libevent/ + +Gunicorn +-------- + +`Gunicorn`_ 'Green Unicorn' is a WSGI HTTP Server for UNIX. It's a pre-fork +worker model ported from Ruby's Unicorn project. It supports both `eventlet`_ +and `greenlet`_. Running a Flask application on this server is quite simple:: + + gunicorn myproject:app + +`Gunicorn`_ provides many command-line options -- see ``gunicorn -h``. +For example, to run a Flask application with 4 worker processes (``-w +4``) binding to localhost port 4000 (``-b 127.0.0.1:4000``):: + + gunicorn -w 4 -b 127.0.0.1:4000 myproject:app + +.. _Gunicorn: http://gunicorn.org/ +.. _eventlet: http://eventlet.net/ +.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html + +Proxy Setups +------------ + +If you deploy your application using one of these servers behind an HTTP +proxy you will need to rewrite a few headers in order for the +application to work. The two problematic values in the WSGI environment +usually are `REMOTE_ADDR` and `HTTP_HOST`. Werkzeug ships a fixer that +will solve some common setups, but you might want to write your own WSGI +middleware for specific setups. + +The most common setup invokes the host being set from `X-Forwarded-Host` +and the remote address from `X-Forwarded-For`:: + + from werkzeug.contrib.fixers import ProxyFix + app.wsgi_app = ProxyFix(app.wsgi_app) + +Please keep in mind that it is a security issue to use such a middleware +in a non-proxy setup because it will blindly trust the incoming +headers which might be forged by malicious clients. + +If you want to rewrite the headers from another header, you might want to +use a fixer like this:: + + class CustomProxyFix(object): + + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + host = environ.get('HTTP_X_FHOST', '') + if host: + environ['HTTP_HOST'] = host + return self.app(environ, start_response) + + app.wsgi_app = CustomProxyFix(app.wsgi_app) diff --git a/studio/static/doc/flask-docs/_sources/deploying/uwsgi.txt b/studio/static/doc/flask-docs/_sources/deploying/uwsgi.txt new file mode 100644 index 0000000..bdee15b --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/deploying/uwsgi.txt @@ -0,0 +1,68 @@ +.. _deploying-uwsgi: + +uWSGI +===== + +uWSGI is a deployment option on servers like `nginx`_, `lighttpd`_, and +`cherokee`_; see :ref:`deploying-fastcgi` and +:ref:`deploying-other-servers` for other options. To use your WSGI +application with uWSGI protocol you will need a uWSGI server +first. uWSGI is both a protocol and an application server; the +application server can serve uWSGI, FastCGI, and HTTP protocols. + +The most popular uWSGI server is `uwsgi`_, which we will use for this +guide. Make sure to have it installed to follow along. + +.. admonition:: Watch Out + + Please make sure in advance that any ``app.run()`` calls you might + have in your application file are inside an ``if __name__ == + '__main__':`` block or moved to a separate file. Just make sure it's + not called because this will always start a local WSGI server which + we do not want if we deploy that application to uWSGI. + +Starting your app with uwsgi +---------------------------- + +`uwsgi` is designed to operate on WSGI callables found in python modules. + +Given a flask application in myapp.py, use the following command: + +.. sourcecode:: text + + $ uwsgi -s /tmp/uwsgi.sock --module myapp --callable app + +Or, if you prefer: + +.. sourcecode:: text + + $ uwsgi -s /tmp/uwsgi.sock -w myapp:app + +Configuring nginx +----------------- + +A basic flask uWSGI configuration for nginx looks like this:: + + location = /yourapplication { rewrite ^ /yourapplication/; } + location /yourapplication { try_files $uri @yourapplication; } + location @yourapplication { + include uwsgi_params; + uwsgi_param SCRIPT_NAME /yourapplication; + uwsgi_modifier1 30; + uwsgi_pass unix:/tmp/uwsgi.sock; + } + +This configuration binds the application to `/yourapplication`. If you want +to have it in the URL root it's a bit simpler because you don't have to tell +it the WSGI `SCRIPT_NAME` or set the uwsgi modifier to make use of it:: + + location / { try_files $uri @yourapplication; } + location @yourapplication { + include uwsgi_params; + uwsgi_pass unix:/tmp/uwsgi.sock; + } + +.. _nginx: http://nginx.org/ +.. _lighttpd: http://www.lighttpd.net/ +.. _cherokee: http://www.cherokee-project.com/ +.. _uwsgi: http://projects.unbit.it/uwsgi/ diff --git a/studio/static/doc/flask-docs/_sources/design.txt b/studio/static/doc/flask-docs/_sources/design.txt new file mode 100644 index 0000000..6ca363a --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/design.txt @@ -0,0 +1,191 @@ +.. _design: + +Design Decisions in Flask +========================= + +If you are curious why Flask does certain things the way it does and not +differently, this section is for you. This should give you an idea about +some of the design decisions that may appear arbitrary and surprising at +first, especially in direct comparison with other frameworks. + + +The Explicit Application Object +------------------------------- + +A Python web application based on WSGI has to have one central callable +object that implements the actual application. In Flask this is an +instance of the :class:`~flask.Flask` class. Each Flask application has +to create an instance of this class itself and pass it the name of the +module, but why can't Flask do that itself? + +Without such an explicit application object the following code:: + + from flask import Flask + app = Flask(__name__) + + @app.route('/') + def index(): + return 'Hello World!' + +Would look like this instead:: + + from hypothetical_flask import route + + @route('/') + def index(): + return 'Hello World!' + +There are three major reasons for this. The most important one is that +implicit application objects require that there may only be one instance at +the time. There are ways to fake multiple applications with a single +application object, like maintaining a stack of applications, but this +causes some problems I won't outline here in detail. Now the question is: +when does a microframework need more than one application at the same +time? A good example for this is unittesting. When you want to test +something it can be very helpful to create a minimal application to test +specific behavior. When the application object is deleted everything it +allocated will be freed again. + +Another thing that becomes possible when you have an explicit object lying +around in your code is that you can subclass the base class +(:class:`~flask.Flask`) to alter specific behaviour. This would not be +possible without hacks if the object were created ahead of time for you +based on a class that is not exposed to you. + +But there is another very important reason why Flask depends on an +explicit instantiation of that class: the package name. Whenever you +create a Flask instance you usually pass it `__name__` as package name. +Flask depends on that information to properly load resources relative +to your module. With Python's outstanding support for reflection it can +then access the package to figure out where the templates and static files +are stored (see :meth:`~flask.Flask.open_resource`). Now obviously there +are frameworks around that do not need any configuration and will still be +able to load templates relative to your application module. But they have +to use the current working directory for that, which is a very unreliable +way to determine where the application is. The current working directory +is process-wide and if you are running multiple applications in one +process (which could happen in a webserver without you knowing) the paths +will be off. Worse: many webservers do not set the working directory to +the directory of your application but to the document root which does not +have to be the same folder. + +The third reason is "explicit is better than implicit". That object is +your WSGI application, you don't have to remember anything else. If you +want to apply a WSGI middleware, just wrap it and you're done (though +there are better ways to do that so that you do not lose the reference +to the application object :meth:`~flask.Flask.wsgi_app`). + +Furthermore this design makes it possible to use a factory function to +create the application which is very helpful for unittesting and similar +things (:ref:`app-factories`). + +The Routing System +------------------ + +Flask uses the Werkzeug routing system which has was designed to +automatically order routes by complexity. This means that you can declare +routes in arbitrary order and they will still work as expected. This is a +requirement if you want to properly implement decorator based routing +since decorators could be fired in undefined order when the application is +split into multiple modules. + +Another design decision with the Werkzeug routing system is that routes +in Werkzeug try to ensure that there is that URLs are unique. Werkzeug +will go quite far with that in that it will automatically redirect to a +canonical URL if a route is ambiguous. + + +One Template Engine +------------------- + +Flask decides on one template engine: Jinja2. Why doesn't Flask have a +pluggable template engine interface? You can obviously use a different +template engine, but Flask will still configure Jinja2 for you. While +that limitation that Jinja2 is *always* configured will probably go away, +the decision to bundle one template engine and use that will not. + +Template engines are like programming languages and each of those engines +has a certain understanding about how things work. On the surface they +all work the same: you tell the engine to evaluate a template with a set +of variables and take the return value as string. + +But that's about where similarities end. Jinja2 for example has an +extensive filter system, a certain way to do template inheritance, support +for reusable blocks (macros) that can be used from inside templates and +also from Python code, uses Unicode for all operations, supports +iterative template rendering, configurable syntax and more. On the other +hand an engine like Genshi is based on XML stream evaluation, template +inheritance by taking the availability of XPath into account and more. +Mako on the other hand treats templates similar to Python modules. + +When it comes to connecting a template engine with an application or +framework there is more than just rendering templates. For instance, +Flask uses Jinja2's extensive autoescaping support. Also it provides +ways to access macros from Jinja2 templates. + +A template abstraction layer that would not take the unique features of +the template engines away is a science on its own and a too large +undertaking for a microframework like Flask. + +Furthermore extensions can then easily depend on one template language +being present. You can easily use your own templating language, but an +extension could still depend on Jinja itself. + + +Micro with Dependencies +----------------------- + +Why does Flask call itself a microframework and yet it depends on two +libraries (namely Werkzeug and Jinja2). Why shouldn't it? If we look +over to the Ruby side of web development there we have a protocol very +similar to WSGI. Just that it's called Rack there, but besides that it +looks very much like a WSGI rendition for Ruby. But nearly all +applications in Ruby land do not work with Rack directly, but on top of a +library with the same name. This Rack library has two equivalents in +Python: WebOb (formerly Paste) and Werkzeug. Paste is still around but +from my understanding it's sort of deprecated in favour of WebOb. The +development of WebOb and Werkzeug started side by side with similar ideas +in mind: be a good implementation of WSGI for other applications to take +advantage. + +Flask is a framework that takes advantage of the work already done by +Werkzeug to properly interface WSGI (which can be a complex task at +times). Thanks to recent developments in the Python package +infrastructure, packages with dependencies are no longer an issue and +there are very few reasons against having libraries that depend on others. + + +Thread Locals +------------- + +Flask uses thread local objects (context local objects in fact, they +support greenlet contexts as well) for request, session and an extra +object you can put your own things on (:data:`~flask.g`). Why is that and +isn't that a bad idea? + +Yes it is usually not such a bright idea to use thread locals. They cause +troubles for servers that are not based on the concept of threads and make +large applications harder to maintain. However Flask is just not designed +for large applications or asynchronous servers. Flask wants to make it +quick and easy to write a traditional web application. + +Also see the :ref:`becomingbig` section of the documentation for some +inspiration for larger applications based on Flask. + + +What Flask is, What Flask is Not +-------------------------------- + +Flask will never have a database layer. It will not have a form library +or anything else in that direction. Flask itself just bridges to Werkzeug +to implement a proper WSGI application and to Jinja2 to handle templating. +It also binds to a few common standard library packages such as logging. +Everything else is up for extensions. + +Why is this the case? Because people have different preferences and +requirements and Flask could not meet those if it would force any of this +into the core. The majority of web applications will need a template +engine in some sort. However not every application needs a SQL database. + +The idea of Flask is to build a good foundation for all applications. +Everything else is up to you or extensions. diff --git a/studio/static/doc/flask-docs/_sources/errorhandling.txt b/studio/static/doc/flask-docs/_sources/errorhandling.txt new file mode 100644 index 0000000..debb9d7 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/errorhandling.txt @@ -0,0 +1,237 @@ +.. _application-errors: + +Handling Application Errors +=========================== + +.. versionadded:: 0.3 + +Applications fail, servers fail. Sooner or later you will see an exception +in production. Even if your code is 100% correct, you will still see +exceptions from time to time. Why? Because everything else involved will +fail. Here some situations where perfectly fine code can lead to server +errors: + +- the client terminated the request early and the application was still + reading from the incoming data. +- the database server was overloaded and could not handle the query. +- a filesystem is full +- a harddrive crashed +- a backend server overloaded +- a programming error in a library you are using +- network connection of the server to another system failed. + +And that's just a small sample of issues you could be facing. So how do we +deal with that sort of problem? By default if your application runs in +production mode, Flask will display a very simple page for you and log the +exception to the :attr:`~flask.Flask.logger`. + +But there is more you can do, and we will cover some better setups to deal +with errors. + +Error Mails +----------- + +If the application runs in production mode (which it will do on your +server) you won't see any log messages by default. Why is that? Flask +tries to be a zero-configuration framework. Where should it drop the logs +for you if there is no configuration? Guessing is not a good idea because +chances are, the place it guessed is not the place where the user has +permission to create a logfile. Also, for most small applications nobody +will look at the logs anyways. + +In fact, I promise you right now that if you configure a logfile for the +application errors you will never look at it except for debugging an issue +when a user reported it for you. What you want instead is a mail the +second the exception happened. Then you get an alert and you can do +something about it. + +Flask uses the Python builtin logging system, and it can actually send +you mails for errors which is probably what you want. Here is how you can +configure the Flask logger to send you mails for exceptions:: + + ADMINS = ['yourname@example.com'] + if not app.debug: + import logging + from logging.handlers import SMTPHandler + mail_handler = SMTPHandler('127.0.0.1', + 'server-error@example.com', + ADMINS, 'YourApplication Failed') + mail_handler.setLevel(logging.ERROR) + app.logger.addHandler(mail_handler) + +So what just happened? We created a new +:class:`~logging.handlers.SMTPHandler` that will send mails with the mail +server listening on ``127.0.0.1`` to all the `ADMINS` from the address +*server-error@example.com* with the subject "YourApplication Failed". If +your mail server requires credentials, these can also be provided. For +that check out the documentation for the +:class:`~logging.handlers.SMTPHandler`. + +We also tell the handler to only send errors and more critical messages. +Because we certainly don't want to get a mail for warnings or other +useless logs that might happen during request handling. + +Before you run that in production, please also look at :ref:`logformat` to +put more information into that error mail. That will save you from a lot +of frustration. + + +Logging to a File +----------------- + +Even if you get mails, you probably also want to log warnings. It's a +good idea to keep as much information around that might be required to +debug a problem. Please note that Flask itself will not issue any +warnings in the core system, so it's your responsibility to warn in the +code if something seems odd. + +There are a couple of handlers provided by the logging system out of the +box but not all of them are useful for basic error logging. The most +interesting are probably the following: + +- :class:`~logging.FileHandler` - logs messages to a file on the + filesystem. +- :class:`~logging.handlers.RotatingFileHandler` - logs messages to a file + on the filesystem and will rotate after a certain number of messages. +- :class:`~logging.handlers.NTEventLogHandler` - will log to the system + event log of a Windows system. If you are deploying on a Windows box, + this is what you want to use. +- :class:`~logging.handlers.SysLogHandler` - sends logs to a UNIX + syslog. + +Once you picked your log handler, do like you did with the SMTP handler +above, just make sure to use a lower setting (I would recommend +`WARNING`):: + + if not app.debug: + import logging + from themodule import TheHandlerYouWant + file_handler = TheHandlerYouWant(...) + file_handler.setLevel(logging.WARNING) + app.logger.addHandler(file_handler) + +.. _logformat: + +Controlling the Log Format +-------------------------- + +By default a handler will only write the message string into a file or +send you that message as mail. A log record stores more information, +and it makes a lot of sense to configure your logger to also contain that +information so that you have a better idea of why that error happened, and +more importantly, where it did. + +A formatter can be instantiated with a format string. Note that +tracebacks are appended to the log entry automatically. You don't have to +do that in the log formatter format string. + +Here some example setups: + +Email +````` + +:: + + from logging import Formatter + mail_handler.setFormatter(Formatter(''' + Message type: %(levelname)s + Location: %(pathname)s:%(lineno)d + Module: %(module)s + Function: %(funcName)s + Time: %(asctime)s + + Message: + + %(message)s + ''')) + +File logging +```````````` + +:: + + from logging import Formatter + file_handler.setFormatter(Formatter( + '%(asctime)s %(levelname)s: %(message)s ' + '[in %(pathname)s:%(lineno)d]' + )) + + +Complex Log Formatting +`````````````````````` + +Here is a list of useful formatting variables for the format string. Note +that this list is not complete, consult the official documentation of the +:mod:`logging` package for a full list. + +.. tabularcolumns:: |p{3cm}|p{12cm}| + ++------------------+----------------------------------------------------+ +| Format | Description | ++==================+====================================================+ +| ``%(levelname)s``| Text logging level for the message | +| | (``'DEBUG'``, ``'INFO'``, ``'WARNING'``, | +| | ``'ERROR'``, ``'CRITICAL'``). | ++------------------+----------------------------------------------------+ +| ``%(pathname)s`` | Full pathname of the source file where the | +| | logging call was issued (if available). | ++------------------+----------------------------------------------------+ +| ``%(filename)s`` | Filename portion of pathname. | ++------------------+----------------------------------------------------+ +| ``%(module)s`` | Module (name portion of filename). | ++------------------+----------------------------------------------------+ +| ``%(funcName)s`` | Name of function containing the logging call. | ++------------------+----------------------------------------------------+ +| ``%(lineno)d`` | Source line number where the logging call was | +| | issued (if available). | ++------------------+----------------------------------------------------+ +| ``%(asctime)s`` | Human-readable time when the LogRecord` was | +| | created. By default this is of the form | +| | ``"2003-07-08 16:49:45,896"`` (the numbers after | +| | the comma are millisecond portion of the time). | +| | This can be changed by subclassing the formatter | +| | and overriding the | +| | :meth:`~logging.Formatter.formatTime` method. | ++------------------+----------------------------------------------------+ +| ``%(message)s`` | The logged message, computed as ``msg % args`` | ++------------------+----------------------------------------------------+ + +If you want to further customize the formatting, you can subclass the +formatter. The formatter has three interesting methods: + +:meth:`~logging.Formatter.format`: + handles the actual formatting. It is passed a + :class:`~logging.LogRecord` object and has to return the formatted + string. +:meth:`~logging.Formatter.formatTime`: + called for `asctime` formatting. If you want a different time format + you can override this method. +:meth:`~logging.Formatter.formatException` + called for exception formatting. It is passed an :attr:`~sys.exc_info` + tuple and has to return a string. The default is usually fine, you + don't have to override it. + +For more information, head over to the official documentation. + + +Other Libraries +--------------- + +So far we only configured the logger your application created itself. +Other libraries might log themselves as well. For example, SQLAlchemy uses +logging heavily in its core. While there is a method to configure all +loggers at once in the :mod:`logging` package, I would not recommend using +it. There might be a situation in which you want to have multiple +separate applications running side by side in the same Python interpreter +and then it becomes impossible to have different logging setups for those. + +Instead, I would recommend figuring out which loggers you are interested +in, getting the loggers with the :func:`~logging.getLogger` function and +iterating over them to attach handlers:: + + from logging import getLogger + loggers = [app.logger, getLogger('sqlalchemy'), + getLogger('otherlibrary')] + for logger in loggers: + logger.addHandler(mail_handler) + logger.addHandler(file_handler) diff --git a/studio/static/doc/flask-docs/_sources/extensiondev.txt b/studio/static/doc/flask-docs/_sources/extensiondev.txt new file mode 100644 index 0000000..ee0d5e6 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/extensiondev.txt @@ -0,0 +1,387 @@ +Flask Extension Development +=========================== + +Flask, being a microframework, often requires some repetitive steps to get +a third party library working. Because very often these steps could be +abstracted to support multiple projects the `Flask Extension Registry`_ +was created. + +If you want to create your own Flask extension for something that does not +exist yet, this guide to extension development will help you get your +extension running in no time and to feel like users would expect your +extension to behave. + +.. _Flask Extension Registry: http://flask.pocoo.org/extensions/ + +Anatomy of an Extension +----------------------- + +Extensions are all located in a package called ``flask_something`` +where "something" is the name of the library you want to bridge. So for +example if you plan to add support for a library named `simplexml` to +Flask, you would name your extension's package ``flask_simplexml``. + +The name of the actual extension (the human readable name) however would +be something like "Flask-SimpleXML". Make sure to include the name +"Flask" somewhere in that name and that you check the capitalization. +This is how users can then register dependencies to your extension in +their `setup.py` files. + +Flask sets up a redirect package called :data:`flask.ext` where users +should import the extensions from. If you for instance have a package +called ``flask_something`` users would import it as +``flask.ext.something``. This is done to transition from the old +namespace packages. See :ref:`ext-import-transition` for more details. + +But how do extensions look like themselves? An extension has to ensure +that it works with multiple Flask application instances at once. This is +a requirement because many people will use patterns like the +:ref:`app-factories` pattern to create their application as needed to aid +unittests and to support multiple configurations. Because of that it is +crucial that your application supports that kind of behaviour. + +Most importantly the extension must be shipped with a `setup.py` file and +registered on PyPI. Also the development checkout link should work so +that people can easily install the development version into their +virtualenv without having to download the library by hand. + +Flask extensions must be licensed as BSD or MIT or a more liberal license +to be enlisted on the Flask Extension Registry. Keep in mind that the +Flask Extension Registry is a moderated place and libraries will be +reviewed upfront if they behave as required. + +"Hello Flaskext!" +----------------- + +So let's get started with creating such a Flask extension. The extension +we want to create here will provide very basic support for SQLite3. + +First we create the following folder structure:: + + flask-sqlite3/ + flask_sqlite3.py + LICENSE + README + +Here's the contents of the most important files: + +setup.py +```````` + +The next file that is absolutely required is the `setup.py` file which is +used to install your Flask extension. The following contents are +something you can work with:: + + """ + Flask-SQLite3 + ------------- + + This is the description for that library + """ + from setuptools import setup + + + setup( + name='Flask-SQLite3', + version='1.0', + url='http://example.com/flask-sqlite3/', + license='BSD', + author='Your Name', + author_email='your-email@example.com', + description='Very short description', + long_description=__doc__, + py_modules=['flask_sqlite3'], + # if you would be using a package instead use packages instead + # of py_modules: + # packages=['flask_sqlite3'], + zip_safe=False, + include_package_data=True, + platforms='any', + install_requires=[ + 'Flask' + ], + classifiers=[ + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + 'Topic :: Software Development :: Libraries :: Python Modules' + ] + ) + +That's a lot of code but you can really just copy/paste that from existing +extensions and adapt. + +flask_sqlite3.py +```````````````` + +Now this is where your extension code goes. But how exactly should such +an extension look like? What are the best practices? Continue reading +for some insight. + +Initializing Extensions +----------------------- + +Many extensions will need some kind of initialization step. For example, +consider your application is currently connecting to SQLite like the +documentation suggests (:ref:`sqlite3`) you will need to provide a few +functions and before / after request handlers. So how does the extension +know the name of the application object? + +Quite simple: you pass it to it. + +There are two recommended ways for an extension to initialize: + +initialization functions: + If your extension is called `helloworld` you might have a function + called ``init_helloworld(app[, extra_args])`` that initializes the + extension for that application. It could attach before / after + handlers etc. + +classes: + Classes work mostly like initialization functions but can later be + used to further change the behaviour. For an example look at how the + `OAuth extension`_ works: there is an `OAuth` object that provides + some helper functions like `OAuth.remote_app` to create a reference to + a remote application that uses OAuth. + +What to use depends on what you have in mind. For the SQLite 3 extension +we will use the class based approach because it will provide users with a +manager object that handles opening and closing database connections. + +The Extension Code +------------------ + +Here's the contents of the `flask_sqlite3.py` for copy/paste:: + + from __future__ import absolute_import + import sqlite3 + + from flask import _request_ctx_stack + + class SQLite3(object): + + def __init__(self, app): + self.app = app + self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') + self.app.teardown_request(self.teardown_request) + self.app.before_request(self.before_request) + + def connect(self): + return sqlite3.connect(self.app.config['SQLITE3_DATABASE']) + + def before_request(self): + ctx = _request_ctx_stack.top + ctx.sqlite3_db = self.connect() + + def teardown_request(self, exception): + ctx = _request_ctx_stack.top + ctx.sqlite3_db.close() + + def get_db(self): + ctx = _request_ctx_stack.top + if ctx is not None: + return ctx.sqlite3_db + +So here's what these lines of code do: + +1. The ``__future__`` import is necessary to activate absolute imports. + Otherwise we could not call our module `sqlite3.py` and import the + top-level `sqlite3` module which actually implements the connection to + SQLite. +2. We create a class for our extension that requires a supplied `app` object, + sets a configuration for the database if it's not there + (:meth:`dict.setdefault`), and attaches `before_request` and + `teardown_request` handlers. +3. Next, we define a `connect` function that opens a database connection. +4. Then we set up the request handlers we bound to the app above. Note here + that we're attaching our database connection to the top request context via + `_request_ctx_stack.top`. Extensions should use the top context and not the + `g` object to store things like database connections. +5. Finally, we add a `get_db` function that simplifies access to the context's + database. + +So why did we decide on a class based approach here? Because using our +extension looks something like this:: + + from flask import Flask + from flask_sqlite3 import SQLite3 + + app = Flask(__name__) + app.config.from_pyfile('the-config.cfg') + manager = SQLite3(app) + db = manager.get_db() + +You can then use the database from views like this:: + + @app.route('/') + def show_all(): + cur = db.cursor() + cur.execute(...) + +Opening a database connection from outside a view function is simple. + +>>> from yourapplication import db +>>> cur = db.cursor() +>>> cur.execute(...) + +Adding an `init_app` Function +----------------------------- + +In practice, you'll almost always want to permit users to initialize your +extension and provide an app object after the fact. This can help avoid +circular import problems when a user is breaking their app into multiple files. +Our extension could add an `init_app` function as follows:: + + class SQLite3(object): + + def __init__(self, app=None): + if app is not None: + self.app = app + self.init_app(self.app) + else: + self.app = None + + def init_app(self, app): + self.app = app + self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') + self.app.teardown_request(self.teardown_request) + self.app.before_request(self.before_request) + + def connect(self): + return sqlite3.connect(app.config['SQLITE3_DATABASE']) + + def before_request(self): + ctx = _request_ctx_stack.top + ctx.sqlite3_db = self.connect() + + def teardown_request(self, exception): + ctx = _request_ctx_stack.top + ctx.sqlite3_db.close() + + def get_db(self): + ctx = _request_ctx_stack.top + if ctx is not None: + return ctx.sqlite3_db + +The user could then initialize the extension in one file:: + + manager = SQLite3() + +and bind their app to the extension in another file:: + + manager.init_app(app) + +End-Of-Request Behavior +----------------------- + +Due to the change in Flask 0.7 regarding functions that are run at the end +of the request your extension will have to be extra careful there if it +wants to continue to support older versions of Flask. The following +pattern is a good way to support both:: + + def close_connection(response): + ctx = _request_ctx_stack.top + ctx.sqlite3_db.close() + return response + + if hasattr(app, 'teardown_request'): + app.teardown_request(close_connection) + else: + app.after_request(close_connection) + +Strictly speaking the above code is wrong, because teardown functions are +passed the exception and typically don't return anything. However because +the return value is discarded this will just work assuming that the code +in between does not touch the passed parameter. + +Learn from Others +----------------- + +This documentation only touches the bare minimum for extension +development. If you want to learn more, it's a very good idea to check +out existing extensions on the `Flask Extension Registry`_. If you feel +lost there is still the `mailinglist`_ and the `IRC channel`_ to get some +ideas for nice looking APIs. Especially if you do something nobody before +you did, it might be a very good idea to get some more input. This not +only to get an idea about what people might want to have from an +extension, but also to avoid having multiple developers working on pretty +much the same side by side. + +Remember: good API design is hard, so introduce your project on the +mailinglist, and let other developers give you a helping hand with +designing the API. + +The best Flask extensions are extensions that share common idioms for the +API. And this can only work if collaboration happens early. + +Approved Extensions +------------------- + +Flask also has the concept of approved extensions. Approved extensions +are tested as part of Flask itself to ensure extensions do not break on +new releases. These approved extensions are listed on the `Flask +Extension Registry`_ and marked appropriately. If you want your own +extension to be approved you have to follow these guidelines: + +1. An approved Flask extension must provide exactly one package or module + named ``flask_extensionname``. They might also reside inside a + ``flaskext`` namespace packages though this is discouraged now. +2. It must ship a testing suite that can either be invoked with ``make test`` + or ``python setup.py test``. For test suites invoked with ``make + test`` the extension has to ensure that all dependencies for the test + are installed automatically, in case of ``python setup.py test`` + dependencies for tests alone can be specified in the `setup.py` + file. The test suite also has to be part of the distribution. +3. APIs of approved extensions will be checked for the following + characteristics: + + - an approved extension has to support multiple applications + running in the same Python process. + - it must be possible to use the factory pattern for creating + applications. + +4. The license must be BSD/MIT/WTFPL licensed. +5. The naming scheme for official extensions is *Flask-ExtensionName* or + *ExtensionName-Flask*. +6. Approved extensions must define all their dependencies in the + `setup.py` file unless a dependency cannot be met because it is not + available on PyPI. +7. The extension must have documentation that uses one of the two Flask + themes for Sphinx documentation. +8. The setup.py description (and thus the PyPI description) has to + link to the documentation, website (if there is one) and there + must be a link to automatically install the development version + (``PackageName==dev``). +9. The ``zip_safe`` flag in the setup script must be set to ``False``, + even if the extension would be safe for zipping. +10. An extension currently has to support Python 2.5, 2.6 as well as + Python 2.7 + + +.. _ext-import-transition: + +Extension Import Transition +--------------------------- + +For a while we recommended using namespace packages for Flask extensions. +This turned out to be problematic in practice because many different +competing namespace package systems exist and pip would automatically +switch between different systems and this caused a lot of problems for +users. + +Instead we now recommend naming packages ``flask_foo`` instead of the now +deprecated ``flaskext.foo``. Flask 0.8 introduces a redirect import +system that lets uses import from ``flask.ext.foo`` and it will try +``flask_foo`` first and if that fails ``flaskext.foo``. + +Flask extensions should urge users to import from ``flask.ext.foo`` +instead of ``flask_foo`` or ``flaskext_foo`` so that extensions can +transition to the new package name without affecting users. + + +.. _OAuth extension: http://packages.python.org/Flask-OAuth/ +.. _mailinglist: http://flask.pocoo.org/mailinglist/ +.. _IRC channel: http://flask.pocoo.org/community/irc/ diff --git a/studio/static/doc/flask-docs/_sources/extensions.txt b/studio/static/doc/flask-docs/_sources/extensions.txt new file mode 100644 index 0000000..53dca56 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/extensions.txt @@ -0,0 +1,48 @@ +Flask Extensions +================ + +Flask extensions extend the functionality of Flask in various different +ways. For instance they add support for databases and other common tasks. + +Finding Extensions +------------------ + +Flask extensions are listed on the `Flask Extension Registry`_ and can be +downloaded with ``easy_install`` or ``pip``. If you add a Flask extension +as dependency to your ``requirements.rst`` or ``setup.py`` file they are +usually installed with a simple command or when your application installs. + +Using Extensions +---------------- + +Extensions typically have documentation that goes along that shows how to +use it. There are no general rules in how extensions are supposed to +behave but they are imported from common locations. If you have an +extension called ``Flask-Foo`` or ``Foo-Flask`` it will be always +importable from ``flask.ext.foo``:: + + from flask.ext import foo + +Flask Before 0.8 +---------------- + +If you are using Flask 0.7 or earlier the :data:`flask.ext` package will not +exist, instead you have to import from ``flaskext.foo`` or ``flask_foo`` +depending on how the extension is distributed. If you want to develop an +application that supports Flask 0.7 or earlier you should still import +from the :data:`flask.ext` package. We provide you with a compatibility +module that provides this package for older versions of Flask. You can +download it from github: `flaskext_compat.py`_ + +And here is how you can use it:: + + import flaskext_compat + flaskext_compat.activate() + + from flask.ext import foo + +Once the ``flaskext_compat`` module is activated the :data:`flask.ext` will +exist and you can start importing from there. + +.. _Flask Extension Registry: http://flask.pocoo.org/extensions/ +.. _flaskext_compat.py: https://github.com/mitsuhiko/flask/raw/master/scripts/flaskext_compat.py diff --git a/studio/static/doc/flask-docs/_sources/foreword.txt b/studio/static/doc/flask-docs/_sources/foreword.txt new file mode 100644 index 0000000..10b886b --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/foreword.txt @@ -0,0 +1,100 @@ +Foreword +======== + +Read this before you get started with Flask. This hopefully answers some +questions about the purpose and goals of the project, and when you +should or should not be using it. + +What does "micro" mean? +----------------------- + +To me, the "micro" in microframework refers not only to the simplicity and +small size of the framework, but also the fact that it does not make much +decisions for you. While Flask does pick a templating engine for you, we +won't make such decisions for your datastore or other parts. + +For us however the term “micro” does not mean that the whole implementation +has to fit into a single Python file. + +One of the design decisions with Flask was that simple tasks should be +simple and not take up a lot of code and yet not limit yourself. Because +of that we took a few design choices that some people might find +surprising or unorthodox. For example, Flask uses thread-local objects +internally so that you don't have to pass objects around from function to +function within a request in order to stay threadsafe. While this is a +really easy approach and saves you a lot of time, it might also cause some +troubles for very large applications because changes on these thread-local +objects can happen anywhere in the same thread. In order to solve these +problems we don't hide the thread locals for you but instead embrace them +and provide you with a lot of tools to make it as pleasant as possible to +work with them. + +Flask is also based on convention over configuration, which means that +many things are preconfigured. For example, by convention, templates and +static files are in subdirectories within the Python source tree of the +application. While this can be changed you usually don't have to. + +The main reason however why Flask is called a "microframework" is the idea +to keep the core simple but extensible. There is no database abstraction +layer, no form validation or anything else where different libraries +already exist that can handle that. However Flask knows the concept of +extensions that can add this functionality into your application as if it +was implemented in Flask itself. There are currently extensions for +object relational mappers, form validation, upload handling, various open +authentication technologies and more. + +Since Flask is based on a very solid foundation there is not a lot of code +in Flask itself. As such it's easy to adapt even for lage applications +and we are making sure that you can either configure it as much as +possible by subclassing things or by forking the entire codebase. If you +are interested in that, check out the :ref:`becomingbig` chapter. + +If you are curious about the Flask design principles, head over to the +section about :ref:`design`. + +Web Development is Dangerous +---------------------------- + +I'm not joking. Well, maybe a little. If you write a web +application, you are probably allowing users to register and leave their +data on your server. The users are entrusting you with data. And even if +you are the only user that might leave data in your application, you still +want that data to be stored securely. + +Unfortunately, there are many ways the security of a web application can be +compromised. Flask protects you against one of the most common security +problems of modern web applications: cross-site scripting (XSS). Unless +you deliberately mark insecure HTML as secure, Flask and the underlying +Jinja2 template engine have you covered. But there are many more ways to +cause security problems. + +The documentation will warn you about aspects of web development that +require attention to security. Some of these security concerns +are far more complex than one might think, and we all sometimes underestimate +the likelihood that a vulnerability will be exploited, until a clever +attacker figures out a way to exploit our applications. And don't think +that your application is not important enough to attract an attacker. +Depending on the kind of attack, chances are that automated bots are +probing for ways to fill your database with spam, links to malicious +software, and the like. + +So always keep security in mind when doing web development. + +The Status of Python 3 +---------------------- + +Currently the Python community is in the process of improving libraries to +support the new iteration of the Python programming language. While the +situation is greatly improving there are still some issues that make it +hard for us to switch over to Python 3 just now. These problems are +partially caused by changes in the language that went unreviewed for too +long, partially also because we have not quite worked out how the lower +level API should change for the unicode differences in Python3. + +Werkzeug and Flask will be ported to Python 3 as soon as a solution for +the changes is found, and we will provide helpful tips how to upgrade +existing applications to Python 3. Until then, we strongly recommend +using Python 2.6 and 2.7 with activated Python 3 warnings during +development. If you plan on upgrading to Python 3 in the near future we +strongly recommend that you read `How to write forwards compatible +Python code `_. diff --git a/studio/static/doc/flask-docs/_sources/htmlfaq.txt b/studio/static/doc/flask-docs/_sources/htmlfaq.txt new file mode 100644 index 0000000..1da25f3 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/htmlfaq.txt @@ -0,0 +1,207 @@ +HTML/XHTML FAQ +============== + +The Flask documentation and example applications are using HTML5. You +may notice that in many situations, when end tags are optional they are +not used, so that the HTML is cleaner and faster to load. Because there +is much confusion about HTML and XHTML among developers, this document tries +to answer some of the major questions. + + +History of XHTML +---------------- + +For a while, it appeared that HTML was about to be replaced by XHTML. +However, barely any websites on the Internet are actual XHTML (which is +HTML processed using XML rules). There are a couple of major reasons +why this is the case. One of them is Internet Explorer's lack of proper +XHTML support. The XHTML spec states that XHTML must be served with the MIME +type `application/xhtml+xml`, but Internet Explorer refuses to read files +with that MIME type. +While it is relatively easy to configure Web servers to serve XHTML properly, +few people do. This is likely because properly using XHTML can be quite +painful. + +One of the most important causes of pain is XML's draconian (strict and +ruthless) error handling. When an XML parsing error is encountered, +the browser is supposed to show the user an ugly error message, instead +of attempting to recover from the error and display what it can. Most of +the (X)HTML generation on the web is based on non-XML template engines +(such as Jinja, the one used in Flask) which do not protect you from +accidentally creating invalid XHTML. There are XML based template engines, +such as Kid and the popular Genshi, but they often come with a larger +runtime overhead and, are not as straightforward to use because they have +to obey XML rules. + +The majority of users, however, assumed they were properly using XHTML. +They wrote an XHTML doctype at the top of the document and self-closed all +the necessary tags (``
`` becomes ``
`` or ``

`` in XHTML). +However, even if the document properly validates as XHTML, what really +determines XHTML/HTML processing in browsers is the MIME type, which as +said before is often not set properly. So the valid XHTML was being treated +as invalid HTML. + +XHTML also changed the way JavaScript is used. To properly work with XHTML, +programmers have to use the namespaced DOM interface with the XHTML +namespace to query for HTML elements. + +History of HTML5 +---------------- + +Development of the HTML5 specification was started in 2004 under the name +"Web Applications 1.0" by the Web Hypertext Application Technology Working +Group, or WHATWG (which was formed by the major browser vendors Apple, +Mozilla, and Opera) with the goal of writing a new and improved HTML +specification, based on existing browser behaviour instead of unrealistic +and backwards-incompatible specifications. + +For example, in HTML4 ``Hello``. However, since people were using +XHTML-like tags along the lines of ````, browser vendors implemented +the XHTML syntax over the syntax defined by the specification. + +In 2007, the specification was adopted as the basis of a new HTML +specification under the umbrella of the W3C, known as HTML5. Currently, +it appears that XHTML is losing traction, as the XHTML 2 working group has +been disbanded and HTML5 is being implemented by all major browser vendors. + +HTML versus XHTML +----------------- + +The following table gives you a quick overview of features available in +HTML 4.01, XHTML 1.1 and HTML5. (XHTML 1.0 is not included, as it was +superseded by XHTML 1.1 and the barely-used XHTML5.) + +.. tabularcolumns:: |p{9cm}|p{2cm}|p{2cm}|p{2cm}| + ++-----------------------------------------+----------+----------+----------+ +| | HTML4.01 | XHTML1.1 | HTML5 | ++=========================================+==========+==========+==========+ +| ``value`` | |Y| [1]_ | |N| | |N| | ++-----------------------------------------+----------+----------+----------+ +| ``
`` supported | |N| | |Y| | |Y| [2]_ | ++-----------------------------------------+----------+----------+----------+ +| `` + +Another method is using Google's `AJAX Libraries API +`_ to load jQuery: + +.. sourcecode:: html + + + + +In this case you have to put jQuery into your static folder as a fallback, but it will +first try to load it directly from Google. This has the advantage that your +website will probably load faster for users if they went to at least one +other website before using the same jQuery version from Google because it +will already be in the browser cache. + +Where is My Site? +----------------- + +Do you know where your application is? If you are developing the answer +is quite simple: it's on localhost port something and directly on the root +of that server. But what if you later decide to move your application to +a different location? For example to ``http://example.com/myapp``? On +the server side this never was a problem because we were using the handy +:func:`~flask.url_for` function that could answer that question for +us, but if we are using jQuery we should not hardcode the path to +the application but make that dynamic, so how can we do that? + +A simple method would be to add a script tag to our page that sets a +global variable to the prefix to the root of the application. Something +like this: + +.. sourcecode:: html+jinja + + + +The ``|safe`` is necessary so that Jinja does not escape the JSON encoded +string with HTML rules. Usually this would be necessary, but we are +inside a `script` block here where different rules apply. + +.. admonition:: Information for Pros + + In HTML the `script` tag is declared `CDATA` which means that entities + will not be parsed. Everything until ```` is handled as script. + This also means that there must never be any ``"|tojson|safe }}`` is rendered as + ``"<\/script>"``). + + +JSON View Functions +------------------- + +Now let's create a server side function that accepts two URL arguments of +numbers which should be added together and then sent back to the +application in a JSON object. This is a really ridiculous example and is +something you usually would do on the client side alone, but a simple +example that shows how you would use jQuery and Flask nonetheless:: + + from flask import Flask, jsonify, render_template, request + app = Flask(__name__) + + @app.route('/_add_numbers') + def add_numbers(): + a = request.args.get('a', 0, type=int) + b = request.args.get('b', 0, type=int) + return jsonify(result=a + b) + + @app.route('/') + def index(): + return render_template('index.html') + +As you can see I also added an `index` method here that renders a +template. This template will load jQuery as above and have a little form +we can add two numbers and a link to trigger the function on the server +side. + +Note that we are using the :meth:`~werkzeug.datastructures.MultiDict.get` method here +which will never fail. If the key is missing a default value (here ``0``) +is returned. Furthermore it can convert values to a specific type (like +in our case `int`). This is especially handy for code that is +triggered by a script (APIs, JavaScript etc.) because you don't need +special error reporting in that case. + +The HTML +-------- + +Your index.html template either has to extend a `layout.html` template with +jQuery loaded and the `$SCRIPT_ROOT` variable set, or do that on the top. +Here's the HTML code needed for our little application (`index.html`). +Notice that we also drop the script directly into the HTML here. It is +usually a better idea to have that in a separate script file: + +.. sourcecode:: html + + +

jQuery Example

+

+ + = + ? +

calculate server side + +I won't got into detail here about how jQuery works, just a very quick +explanation of the little bit of code above: + +1. ``$(function() { ... })`` specifies code that should run once the + browser is done loading the basic parts of the page. +2. ``$('selector')`` selects an element and lets you operate on it. +3. ``element.bind('event', func)`` specifies a function that should run + when the user clicked on the element. If that function returns + `false`, the default behaviour will not kick in (in this case, navigate + to the `#` URL). +4. ``$.getJSON(url, data, func)`` sends a `GET` request to `url` and will + send the contents of the `data` object as query parameters. Once the + data arrived, it will call the given function with the return value as + argument. Note that we can use the `$SCRIPT_ROOT` variable here that + we set earlier. + +If you don't get the whole picture, download the `sourcecode +for this example +`_ +from github. diff --git a/studio/static/doc/flask-docs/_sources/patterns/lazyloading.txt b/studio/static/doc/flask-docs/_sources/patterns/lazyloading.txt new file mode 100644 index 0000000..50ad6fa --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/patterns/lazyloading.txt @@ -0,0 +1,104 @@ +Lazily Loading Views +==================== + +Flask is usually used with the decorators. Decorators are simple and you +have the URL right next to the function that is called for that specific +URL. However there is a downside to this approach: it means all your code +that uses decorators has to be imported upfront or Flask will never +actually find your function. + +This can be a problem if your application has to import quick. It might +have to do that on systems like Google's App Engine or other systems. So +if you suddenly notice that your application outgrows this approach you +can fall back to a centralized URL mapping. + +The system that enables having a central URL map is the +:meth:`~flask.Flask.add_url_rule` function. Instead of using decorators, +you have a file that sets up the application with all URLs. + +Converting to Centralized URL Map +--------------------------------- + +Imagine the current application looks somewhat like this:: + + from flask import Flask + app = Flask(__name__) + + @app.route('/') + def index(): + pass + + @app.route('/user/') + def user(username): + pass + +Then the centralized approach you would have one file with the views +(`views.py`) but without any decorator:: + + def index(): + pass + + def user(username): + pass + +And then a file that sets up an application which maps the functions to +URLs:: + + from flask import Flask + from yourapplication import views + app = Flask(__name__) + app.add_url_rule('/', view_func=views.index) + app.add_url_rule('/user/', view_func=views.user) + +Loading Late +------------ + +So far we only split up the views and the routing, but the module is still +loaded upfront. The trick to actually load the view function as needed. +This can be accomplished with a helper class that behaves just like a +function but internally imports the real function on first use:: + + from werkzeug import import_string, cached_property + + class LazyView(object): + + def __init__(self, import_name): + self.__module__, self.__name__ = import_name.rsplit('.', 1) + self.import_name = import_name + + @cached_property + def view(self): + return import_string(self.import_name) + + def __call__(self, *args, **kwargs): + return self.view(*args, **kwargs) + +What's important here is is that `__module__` and `__name__` are properly +set. This is used by Flask internally to figure out how to name the +URL rules in case you don't provide a name for the rule yourself. + +Then you can define your central place to combine the views like this:: + + from flask import Flask + from yourapplication.helpers import LazyView + app = Flask(__name__) + app.add_url_rule('/', + view_func=LazyView('yourapplication.views.index')) + app.add_url_rule('/user/', + view_func=LazyView('yourapplication.views.user')) + +You can further optimize this in terms of amount of keystrokes needed to +write this by having a function that calls into +:meth:`~flask.Flask.add_url_rule` by prefixing a string with the project +name and a dot, and by wrapping `view_func` in a `LazyView` as needed:: + + def url(url_rule, import_name, **options): + view = LazyView('yourapplication.' + import_name) + app.add_url_rule(url_rule, view_func=view, **options) + + url('/', 'views.index') + url('/user/', 'views.user') + +One thing to keep in mind is that before and after request handlers have +to be in a file that is imported upfront to work properly on the first +request. The same goes for any kind of remaining decorator. diff --git a/studio/static/doc/flask-docs/_sources/patterns/mongokit.txt b/studio/static/doc/flask-docs/_sources/patterns/mongokit.txt new file mode 100644 index 0000000..a9c4eef --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/patterns/mongokit.txt @@ -0,0 +1,144 @@ +.. mongokit-pattern: + +MongoKit in Flask +================= + +Using a document database rather than a full DBMS gets more common these days. +This pattern shows how to use MongoKit, a document mapper library, to +integrate with MongoDB. + +This pattern requires a running MongoDB server and the MongoKit library +installed. + +There are two very common ways to use MongoKit. I will outline each of them +here: + + +Declarative +----------- + +The default behaviour of MongoKit is the declarative one that is based on +common ideas from Django or the SQLAlchemy declarative extension. + +Here an example `app.py` module for your application:: + + from flask import Flask + from mongokit import Connection, Document + + # configuration + MONGODB_HOST = 'localhost' + MONGODB_PORT = 27017 + + # create the little application object + app = Flask(__name__) + app.config.from_object(__name__) + + # connect to the database + connection = Connection(app.config['MONGODB_HOST'], + app.config['MONGODB_PORT']) + + +To define your models, just subclass the `Document` class that is imported +from MongoKit. If you've seen the SQLAlchemy pattern you may wonder why we do +not have a session and even do not define a `init_db` function here. On the +one hand, MongoKit does not have something like a session. This sometimes +makes it more to type but also makes it blazingly fast. On the other hand, +MongoDB is schemaless. This means you can modify the data structure from one +insert query to the next without any problem. MongoKit is just schemaless +too, but implements some validation to ensure data integrity. + +Here is an example document (put this also into `app.py`, e.g.):: + + def max_length(length): + def validate(value): + if len(value) <= length: + return True + raise Exception('%s must be at most %s characters long' % length) + return validate + + class User(Document): + structure = { + 'name': unicode, + 'email': unicode, + } + validators = { + 'name': max_length(50), + 'email': max_length(120) + } + use_dot_notation = True + def __repr__(self): + return '' % (self.name) + + # register the User document with our current connection + connection.register([User]) + + +This example shows you how to define your schema (named structure), a +validator for the maximum character length and uses a special MongoKit feature +called `use_dot_notation`. Per default MongoKit behaves like a python +dictionary but with `use_dot_notation` set to `True` you can use your +documents like you use models in nearly any other ORM by using dots to +separate between attributes. + +You can insert entries into the database like this: + +>>> from yourapplication.database import connection +>>> from yourapplication.models import User +>>> collection = connection['test'].users +>>> user = collection.User() +>>> user['name'] = u'admin' +>>> user['email'] = u'admin@localhost' +>>> user.save() + +Note that MongoKit is kinda strict with used column types, you must not use a +common `str` type for either `name` or `email` but unicode. + +Querying is simple as well: + +>>> list(collection.User.find()) +[] +>>> collection.User.find_one({'name': u'admin'}) + + +.. _MongoKit: http://bytebucket.org/namlook/mongokit/ + + +PyMongo Compatibility Layer +--------------------------- + +If you just want to use PyMongo, you can do that with MongoKit as well. You +may use this process if you need the best performance to get. Note that this +example does not show how to couple it with Flask, see the above MongoKit code +for examples:: + + from MongoKit import Connection + + connection = Connection() + +To insert data you can use the `insert` method. We have to get a +collection first, this is somewhat the same as a table in the SQL world. + +>>> collection = connection['test'].users +>>> user = {'name': u'admin', 'email': u'admin@localhost'} +>>> collection.insert(user) + +print list(collection.find()) +print collection.find_one({'name': u'admin'}) + +MongoKit will automatically commit for us. + +To query your database, you use the collection directly: + +>>> list(collection.find()) +[{u'_id': ObjectId('4c271729e13823182f000000'), u'name': u'admin', u'email': u'admin@localhost'}] +>>> collection.find_one({'name': u'admin'}) +{u'_id': ObjectId('4c271729e13823182f000000'), u'name': u'admin', u'email': u'admin@localhost'} + +These results are also dict-like objects: + +>>> r = collection.find_one({'name': u'admin'}) +>>> r['email'] +u'admin@localhost' + +For more information about MongoKit, head over to the +`website `_. diff --git a/studio/static/doc/flask-docs/_sources/patterns/packages.txt b/studio/static/doc/flask-docs/_sources/patterns/packages.txt new file mode 100644 index 0000000..79fd2c5 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/patterns/packages.txt @@ -0,0 +1,115 @@ +.. _larger-applications: + +Larger Applications +=================== + +For larger applications it's a good idea to use a package instead of a +module. That is quite simple. Imagine a small application looks like +this:: + + /yourapplication + /yourapplication.py + /static + /style.css + /templates + layout.html + index.html + login.html + ... + +Simple Packages +--------------- + +To convert that into a larger one, just create a new folder +`yourapplication` inside the existing one and move everything below it. +Then rename `yourapplication.py` to `__init__.py`. (Make sure to delete +all `.pyc` files first, otherwise things would most likely break) + +You should then end up with something like that:: + + /yourapplication + /yourapplication + /__init__.py + /static + /style.css + /templates + layout.html + index.html + login.html + ... + +But how do you run your application now? The naive ``python +yourapplication/__init__.py`` will not work. Let's just say that Python +does not want modules in packages to be the startup file. But that is not +a big problem, just add a new file called `runserver.py` next to the inner +`yourapplication` folder with the following contents:: + + from yourapplication import app + app.run(debug=True) + +What did we gain from this? Now we can restructure the application a bit +into multiple modules. The only thing you have to remember is the +following quick checklist: + +1. the `Flask` application object creation has to be in the + `__init__.py` file. That way each module can import it safely and the + `__name__` variable will resolve to the correct package. +2. all the view functions (the ones with a :meth:`~flask.Flask.route` + decorator on top) have to be imported when in the `__init__.py` file. + Not the object itself, but the module it is in. Import the view module + **after the application object is created**. + +Here's an example `__init__.py`:: + + from flask import Flask + app = Flask(__name__) + + import yourapplication.views + +And this is what `views.py` would look like:: + + from yourapplication import app + + @app.route('/') + def index(): + return 'Hello World!' + +You should then end up with something like that:: + + /yourapplication + /runserver.py + /yourapplication + /__init__.py + /views.py + /static + /style.css + /templates + layout.html + index.html + login.html + ... + +.. admonition:: Circular Imports + + Every Python programmer hates them, and yet we just added some: + circular imports (That's when two modules depend on each other. In this + case `views.py` depends on `__init__.py`). Be advised that this is a + bad idea in general but here it is actually fine. The reason for this is + that we are not actually using the views in `__init__.py` and just + ensuring the module is imported and we are doing that at the bottom of + the file. + + There are still some problems with that approach but if you want to use + decorators there is no way around that. Check out the + :ref:`becomingbig` section for some inspiration how to deal with that. + + +.. _working-with-modules: + +Working with Blueprints +----------------------- + +If you have larger applications it's recommended to divide them into +smaller groups where each group is implemented with the help of a +blueprint. For a gentle introduction into this topic refer to the +:ref:`blueprints` chapter of the documentation. diff --git a/studio/static/doc/flask-docs/_sources/patterns/sqlalchemy.txt b/studio/static/doc/flask-docs/_sources/patterns/sqlalchemy.txt new file mode 100644 index 0000000..5a33d1f --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/patterns/sqlalchemy.txt @@ -0,0 +1,214 @@ +.. _sqlalchemy-pattern: + +SQLAlchemy in Flask +=================== + +Many people prefer `SQLAlchemy`_ for database access. In this case it's +encouraged to use a package instead of a module for your flask application +and drop the models into a separate module (:ref:`larger-applications`). +While that is not necessary, it makes a lot of sense. + +There are four very common ways to use SQLAlchemy. I will outline each +of them here: + +Flask-SQLAlchemy Extension +-------------------------- + +Because SQLAlchemy is a common database abstraction layer and object +relational mapper that requires a little bit of configuration effort, +there is a Flask extension that handles that for you. This is recommended +if you want to get started quickly. + +You can download `Flask-SQLAlchemy`_ from `PyPI +`_. + +.. _Flask-SQLAlchemy: http://packages.python.org/Flask-SQLAlchemy/ + + +Declarative +----------- + +The declarative extension in SQLAlchemy is the most recent method of using +SQLAlchemy. It allows you to define tables and models in one go, similar +to how Django works. In addition to the following text I recommend the +official documentation on the `declarative`_ extension. + +Here the example `database.py` module for your application:: + + from sqlalchemy import create_engine + from sqlalchemy.orm import scoped_session, sessionmaker + from sqlalchemy.ext.declarative import declarative_base + + engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) + db_session = scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=engine)) + Base = declarative_base() + Base.query = db_session.query_property() + + def init_db(): + # import all modules here that might define models so that + # they will be registered properly on the metadata. Otherwise + # you will have to import them first before calling init_db() + import yourapplication.models + Base.metadata.create_all(bind=engine) + +To define your models, just subclass the `Base` class that was created by +the code above. If you are wondering why we don't have to care about +threads here (like we did in the SQLite3 example above with the +:data:`~flask.g` object): that's because SQLAlchemy does that for us +already with the :class:`~sqlalchemy.orm.scoped_session`. + +To use SQLAlchemy in a declarative way with your application, you just +have to put the following code into your application module. Flask will +automatically remove database sessions at the end of the request for you:: + + from yourapplication.database import db_session + + @app.teardown_request + def shutdown_session(exception=None): + db_session.remove() + +Here is an example model (put this into `models.py`, e.g.):: + + from sqlalchemy import Column, Integer, String + from yourapplication.database import Base + + class User(Base): + __tablename__ = 'users' + id = Column(Integer, primary_key=True) + name = Column(String(50), unique=True) + email = Column(String(120), unique=True) + + def __init__(self, name=None, email=None): + self.name = name + self.email = email + + def __repr__(self): + return '' % (self.name) + +To create the database you can use the `init_db` function: + +>>> from yourapplication.database import init_db +>>> init_db() + +You can insert entries into the database like this: + +>>> from yourapplication.database import db_session +>>> from yourapplication.models import User +>>> u = User('admin', 'admin@localhost') +>>> db_session.add(u) +>>> db_session.commit() + +Querying is simple as well: + +>>> User.query.all() +[] +>>> User.query.filter(User.name == 'admin').first() + + +.. _SQLAlchemy: http://www.sqlalchemy.org/ +.. _declarative: + http://www.sqlalchemy.org/docs/orm/extensions/declarative.html + +Manual Object Relational Mapping +-------------------------------- + +Manual object relational mapping has a few upsides and a few downsides +versus the declarative approach from above. The main difference is that +you define tables and classes separately and map them together. It's more +flexible but a little more to type. In general it works like the +declarative approach, so make sure to also split up your application into +multiple modules in a package. + +Here is an example `database.py` module for your application:: + + from sqlalchemy import create_engine, MetaData + from sqlalchemy.orm import scoped_session, sessionmaker + + engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) + metadata = MetaData() + db_session = scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=engine)) + def init_db(): + metadata.create_all(bind=engine) + +As for the declarative approach you need to close the session after +each request. Put this into your application module:: + + from yourapplication.database import db_session + + @app.teardown_request + def shutdown_session(exception=None): + db_session.remove() + +Here is an example table and model (put this into `models.py`):: + + from sqlalchemy import Table, Column, Integer, String + from sqlalchemy.orm import mapper + from yourapplication.database import metadata, db_session + + class User(object): + query = db_session.query_property() + + def __init__(self, name=None, email=None): + self.name = name + self.email = email + + def __repr__(self): + return '' % (self.name, self.email) + + users = Table('users', metadata, + Column('id', Integer, primary_key=True), + Column('name', String(50), unique=True), + Column('email', String(120), unique=True) + ) + mapper(User, users) + +Querying and inserting works exactly the same as in the example above. + + +SQL Abstraction Layer +--------------------- + +If you just want to use the database system (and SQL) abstraction layer +you basically only need the engine:: + + from sqlalchemy import create_engine, MetaData + + engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) + metadata = MetaData(bind=engine) + +Then you can either declare the tables in your code like in the examples +above, or automatically load them:: + + users = Table('users', metadata, autoload=True) + +To insert data you can use the `insert` method. We have to get a +connection first so that we can use a transaction: + +>>> con = engine.connect() +>>> con.execute(users.insert(name='admin', email='admin@localhost')) + +SQLAlchemy will automatically commit for us. + +To query your database, you use the engine directly or use a connection: + +>>> users.select(users.c.id == 1).execute().first() +(1, u'admin', u'admin@localhost') + +These results are also dict-like tuples: + +>>> r = users.select(users.c.id == 1).execute().first() +>>> r['name'] +u'admin' + +You can also pass strings of SQL statements to the +:meth:`~sqlalchemy.engine.base.Connection.execute` method: + +>>> engine.execute('select * from users where id = :1', [1]).first() +(1, u'admin', u'admin@localhost') + +For more information about SQLAlchemy, head over to the +`website `_. diff --git a/studio/static/doc/flask-docs/_sources/patterns/sqlite3.txt b/studio/static/doc/flask-docs/_sources/patterns/sqlite3.txt new file mode 100644 index 0000000..0d02e46 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/patterns/sqlite3.txt @@ -0,0 +1,119 @@ +.. _sqlite3: + +Using SQLite 3 with Flask +========================= + +In Flask you can implement the opening of database connections at the +beginning of the request and closing at the end with the +:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.teardown_request` +decorators in combination with the special :class:`~flask.g` object. + +So here is a simple example of how you can use SQLite 3 with Flask:: + + import sqlite3 + from flask import g + + DATABASE = '/path/to/database.db' + + def connect_db(): + return sqlite3.connect(DATABASE) + + @app.before_request + def before_request(): + g.db = connect_db() + + @app.teardown_request + def teardown_request(exception): + if hasattr(g, 'db'): + g.db.close() + +.. note:: + + Please keep in mind that the teardown request functions are always + executed, even if a before-request handler failed or was never + executed. Because of this we have to make sure here that the database + is there before we close it. + +Connect on Demand +----------------- + +The downside of this approach is that this will only work if Flask +executed the before-request handlers for you. If you are attempting to +use the database from a script or the interactive Python shell you would +have to do something like this:: + + with app.test_request_context(): + app.preprocess_request() + # now you can use the g.db object + +In order to trigger the execution of the connection code. You won't be +able to drop the dependency on the request context this way, but you could +make it so that the application connects when necessary:: + + def get_connection(): + db = getattr(g, '_db', None) + if db is None: + db = g._db = connect_db() + return db + +Downside here is that you have to use ``db = get_connection()`` instead of +just being able to use ``g.db`` directly. + +.. _easy-querying: + +Easy Querying +------------- + +Now in each request handling function you can access `g.db` to get the +current open database connection. To simplify working with SQLite, a +helper function can be useful:: + + def query_db(query, args=(), one=False): + cur = g.db.execute(query, args) + rv = [dict((cur.description[idx][0], value) + for idx, value in enumerate(row)) for row in cur.fetchall()] + return (rv[0] if rv else None) if one else rv + +This handy little function makes working with the database much more +pleasant than it is by just using the raw cursor and connection objects. + +Here is how you can use it:: + + for user in query_db('select * from users'): + print user['username'], 'has the id', user['user_id'] + +Or if you just want a single result:: + + user = query_db('select * from users where username = ?', + [the_username], one=True) + if user is None: + print 'No such user' + else: + print the_username, 'has the id', user['user_id'] + +To pass variable parts to the SQL statement, use a question mark in the +statement and pass in the arguments as a list. Never directly add them to +the SQL statement with string formatting because this makes it possible +to attack the application using `SQL Injections +`_. + +Initial Schemas +--------------- + +Relational databases need schemas, so applications often ship a +`schema.sql` file that creates the database. It's a good idea to provide +a function that creates the database based on that schema. This function +can do that for you:: + + from contextlib import closing + + def init_db(): + with closing(connect_db()) as db: + with app.open_resource('schema.sql') as f: + db.cursor().executescript(f.read()) + db.commit() + +You can then create such a database from the python shell: + +>>> from yourapplication import init_db +>>> init_db() diff --git a/studio/static/doc/flask-docs/_sources/patterns/streaming.txt b/studio/static/doc/flask-docs/_sources/patterns/streaming.txt new file mode 100644 index 0000000..8393b00 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/patterns/streaming.txt @@ -0,0 +1,61 @@ +Streaming Contents +================== + +Sometimes you want to send an enormous amount of data to the client, much +more than you want to keep in memory. When you are generating the data on +the fly though, how do you send that back to the client without the +roundtrip to the filesystem? + +The answer is by using generators and direct responses. + +Basic Usage +----------- + +This is a basic view function that generates a lot of CSV data on the fly. +The trick is to have an inner function that uses a generator to generate +data and to then invoke that function and pass it to a response object:: + + from flask import Response + + @app.route('/large.csv') + def generate_large_csv(): + def generate(): + for row in iter_all_rows(): + yield ','.join(row) + '\n' + return Response(generate(), mimetype='text/csv') + +Each ``yield`` expression is directly sent to the browser. Now though +that some WSGI middlewares might break streaming, so be careful there in +debug environments with profilers and other things you might have enabled. + +Streaming from Templates +------------------------ + +The Jinja2 template engine also supports rendering templates piece by +piece. This functionality is not directly exposed by Flask because it is +quite uncommon, but you can easily do it yourself:: + + from flask import Response + + def stream_template(template_name, **context): + app.update_template_context(context) + t = app.jinja_env.get_template(template_name) + rv = t.stream(context) + rv.enable_buffering(5) + return rv + + @app.route('/my-large-page.html') + def render_large_template(): + rows = iter_all_rows() + return Response(stream_template('the_template.html', rows=rows)) + +The trick here is to get the template object from the Jinja2 environment +on the application and to call :meth:`~jinja2.Template.stream` instead of +:meth:`~jinja2.Template.render` which returns a stream object instead of a +string. Since we're bypassing the Flask template render functions and +using the template object itself we have to make sure to update the render +context ourselves by calling :meth:`~flask.Flask.update_template_context`. +The template is then evaluated as the stream is iterated over. Since each +time you do a yield the server will flush the content to the client you +might want to buffer up a few items in the template which you can do with +``rv.enable_buffering(size)``. ``5`` is a sane default. diff --git a/studio/static/doc/flask-docs/_sources/patterns/templateinheritance.txt b/studio/static/doc/flask-docs/_sources/patterns/templateinheritance.txt new file mode 100644 index 0000000..70015ec --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/patterns/templateinheritance.txt @@ -0,0 +1,69 @@ +.. _template-inheritance: + +Template Inheritance +==================== + +The most powerful part of Jinja is template inheritance. Template inheritance +allows you to build a base "skeleton" template that contains all the common +elements of your site and defines **blocks** that child templates can override. + +Sounds complicated but is very basic. It's easiest to understand it by starting +with an example. + + +Base Template +------------- + +This template, which we'll call ``layout.html``, defines a simple HTML skeleton +document that you might use for a simple two-column page. It's the job of +"child" templates to fill the empty blocks with content: + +.. sourcecode:: html+jinja + + + + + {% block head %} + + {% block title %}{% endblock %} - My Webpage + {% endblock %} + + +

{% block content %}{% endblock %}
+ + + +In this example, the ``{% block %}`` tags define four blocks that child templates +can fill in. All the `block` tag does is tell the template engine that a +child template may override those portions of the template. + +Child Template +-------------- + +A child template might look like this: + +.. sourcecode:: html+jinja + + {% extends "layout.html" %} + {% block title %}Index{% endblock %} + {% block head %} + {{ super() }} + + {% endblock %} + {% block content %} +

Index

+

+ Welcome on my awesome homepage. + {% endblock %} + +The ``{% extends %}`` tag is the key here. It tells the template engine that +this template "extends" another template. When the template system evaluates +this template, first it locates the parent. The extends tag must be the +first tag in the template. To render the contents of a block defined in +the parent template, use ``{{ super() }}``. diff --git a/studio/static/doc/flask-docs/_sources/patterns/urlprocessors.txt b/studio/static/doc/flask-docs/_sources/patterns/urlprocessors.txt new file mode 100644 index 0000000..778a5a6 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/patterns/urlprocessors.txt @@ -0,0 +1,126 @@ +Using URL Processors +==================== + +.. versionadded:: 0.7 + +Flask 0.7 introduces the concept of URL processors. The idea is that you +might have a bunch of resources with common parts in the URL that you +don't always explicitly want to provide. For instance you might have a +bunch of URLs that have the language code in it but you don't want to have +to handle it in every single function yourself. + +URL processors are especially helpful when combined with blueprints. We +will handle both application specific URL processors here as well as +blueprint specifics. + +Internationalized Application URLs +---------------------------------- + +Consider an application like this:: + + from flask import Flask, g + + app = Flask(__name__) + + @app.route('//') + def index(lang_code): + g.lang_code = lang_code + ... + + @app.route('//about') + def about(lang_code): + g.lang_code = lang_code + ... + +This is an awful lot of repetition as you have to handle the language code +setting on the :data:`~flask.g` object yourself in every single function. +Sure, a decorator could be used to simplify this, but if you want to +generate URLs from one function to another you would have to still provide +the language code explicitly which can be annoying. + +For the latter, this is where :func:`~flask.Flask.url_defaults` functions +come in. They can automatically inject values into a call for +:func:`~flask.url_for` automatically. The code below checks if the +language code is not yet in the dictionary of URL values and if the +endpoint wants a value named ``'lang_code'``:: + + @app.url_defaults + def add_language_code(endpoint, values): + if 'lang_code' in values or not g.lang_code: + return + if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): + values['lang_code'] = g.lang_code + +The method :meth:`~werkzeug.routing.Map.is_endpoint_expecting` of the URL +map can be used to figure out if it would make sense to provide a language +code for the given endpoint. + +The reverse of that function are +:meth:`~flask.Flask.url_value_preprocessor`\s. They are executed right +after the request was matched and can execute code based on the URL +values. The idea is that they pull information out of the values +dictionary and put it somewhere else:: + + @app.url_value_preprocessor + def pull_lang_code(endpoint, values): + g.lang_code = values.pop('lang_code', None) + +That way you no longer have to do the `lang_code` assigment to +:data:`~flask.g` in every function. You can further improve that by +writing your own decorator that prefixes URLs with the language code, but +the more beautiful solution is using a blueprint. Once the +``'lang_code'`` is popped from the values dictionary and it will no longer +be forwarded to the view function reducing the code to this:: + + from flask import Flask, g + + app = Flask(__name__) + + @app.url_defaults + def add_language_code(endpoint, values): + if 'lang_code' in values or not g.lang_code: + return + if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): + values['lang_code'] = g.lang_code + + @app.url_value_preprocessor + def pull_lang_code(endpoint, values): + g.lang_code = values.pop('lang_code', None) + + @app.route('//') + def index(): + ... + + @app.route('//about') + def about(): + ... + +Internationalized Blueprint URLs +-------------------------------- + +Because blueprints can automatically prefix all URLs with a common string +it's easy to automatically do that for every function. Furthermore +blueprints can have per-blueprint URL processors which removes a whole lot +of logic from the :meth:`~flask.Flask.url_defaults` function because it no +longer has to check if the URL is really interested in a ``'lang_code'`` +parameter:: + + from flask import Blueprint, g + + bp = Blueprint('frontend', __name__, url_prefix='/') + + @bp.url_defaults + def add_language_code(endpoint, values): + values.setdefault('lang_code', g.lang_code) + + @bp.url_value_preprocessor + def pull_lang_code(endpoint, values): + g.lang_code = values.pop('lang_code') + + @bp.route('/') + def index(): + ... + + @bp.route('/about') + def about(): + ... diff --git a/studio/static/doc/flask-docs/_sources/patterns/viewdecorators.txt b/studio/static/doc/flask-docs/_sources/patterns/viewdecorators.txt new file mode 100644 index 0000000..a094857 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/patterns/viewdecorators.txt @@ -0,0 +1,168 @@ +View Decorators +=============== + +Python has a really interesting feature called function decorators. This +allow some really neat things for web applications. Because each view in +Flask is a function decorators can be used to inject additional +functionality to one or more functions. The :meth:`~flask.Flask.route` +decorator is the one you probably used already. But there are use cases +for implementing your own decorator. For instance, imagine you have a +view that should only be used by people that are logged in to. If a user +goes to the site and is not logged in, they should be redirected to the +login page. This is a good example of a use case where a decorator is an +excellent solution. + +Login Required Decorator +------------------------ + +So let's implement such a decorator. A decorator is a function that +returns a function. Pretty simple actually. The only thing you have to +keep in mind when implementing something like this is to update the +`__name__`, `__module__` and some other attributes of a function. This is +often forgotten, but you don't have to do that by hand, there is a +function for that that is used like a decorator (:func:`functools.wraps`). + +This example assumes that the login page is called ``'login'`` and that +the current user is stored as `g.user` and `None` if there is no-one +logged in:: + + from functools import wraps + from flask import g, request, redirect, url_for + + def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user is None: + return redirect(url_for('login', next=request.url)) + return f(*args, **kwargs) + return decorated_function + +So how would you use that decorator now? Apply it as innermost decorator +to a view function. When applying further decorators, always remember +that the :meth:`~flask.Flask.route` decorator is the outermost:: + + @app.route('/secret_page') + @login_required + def secret_page(): + pass + +Caching Decorator +----------------- + +Imagine you have a view function that does an expensive calculation and +because of that you would like to cache the generated results for a +certain amount of time. A decorator would be nice for that. We're +assuming you have set up a cache like mentioned in :ref:`caching-pattern`. + +Here an example cache function. It generates the cache key from a +specific prefix (actually a format string) and the current path of the +request. Notice that we are using a function that first creates the +decorator that then decorates the function. Sounds awful? Unfortunately +it is a little bit more complex, but the code should still be +straightforward to read. + +The decorated function will then work as follows + +1. get the unique cache key for the current request base on the current + path. +2. get the value for that key from the cache. If the cache returned + something we will return that value. +3. otherwise the original function is called and the return value is + stored in the cache for the timeout provided (by default 5 minutes). + +Here the code:: + + from functools import wraps + from flask import request + + def cached(timeout=5 * 60, key='view/%s'): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + cache_key = key % request.path + rv = cache.get(cache_key) + if rv is not None: + return rv + rv = f(*args, **kwargs) + cache.set(cache_key, rv, timeout=timeout) + return rv + return decorated_function + return decorator + +Notice that this assumes an instantiated `cache` object is available, see +:ref:`caching-pattern` for more information. + + +Templating Decorator +-------------------- + +A common pattern invented by the TurboGears guys a while back is a +templating decorator. The idea of that decorator is that you return a +dictionary with the values passed to the template from the view function +and the template is automatically rendered. With that, the following +three examples do exactly the same:: + + @app.route('/') + def index(): + return render_template('index.html', value=42) + + @app.route('/') + @templated('index.html') + def index(): + return dict(value=42) + + @app.route('/') + @templated() + def index(): + return dict(value=42) + +As you can see, if no template name is provided it will use the endpoint +of the URL map with dots converted to slashes + ``'.html'``. Otherwise +the provided template name is used. When the decorated function returns, +the dictionary returned is passed to the template rendering function. If +`None` is returned, an empty dictionary is assumed, if something else than +a dictionary is returned we return it from the function unchanged. That +way you can still use the redirect function or return simple strings. + +Here the code for that decorator:: + + from functools import wraps + from flask import request + + def templated(template=None): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + template_name = template + if template_name is None: + template_name = request.endpoint \ + .replace('.', '/') + '.html' + ctx = f(*args, **kwargs) + if ctx is None: + ctx = {} + elif not isinstance(ctx, dict): + return ctx + return render_template(template_name, **ctx) + return decorated_function + return decorator + + +Endpoint Decorator +------------------ + +When you want to use the werkzeug routing system for more flexibility you +need to map the endpoint as defined in the :class:`~werkzeug.routing.Rule` +to a view function. This is possible with this decorator. For example:: + + from flask import Flask + from werkzeug.routing import Rule + + app = Flask(__name__) + app.url_map.add(Rule('/', endpoint='index')) + + @app.endpoint('index') + def my_index(): + return "Hello world" + + + diff --git a/studio/static/doc/flask-docs/_sources/patterns/wtforms.txt b/studio/static/doc/flask-docs/_sources/patterns/wtforms.txt new file mode 100644 index 0000000..93824df --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/patterns/wtforms.txt @@ -0,0 +1,124 @@ +Form Validation with WTForms +============================ + +When you have to work with form data submitted by a browser view code +quickly becomes very hard to read. There are libraries out there designed +to make this process easier to manage. One of them is `WTForms`_ which we +will handle here. If you find yourself in the situation of having many +forms, you might want to give it a try. + +When you are working with WTForms you have to define your forms as classes +first. I recommend breaking up the application into multiple modules +(:ref:`larger-applications`) for that and adding a separate module for the +forms. + +.. admonition:: Getting most of WTForms with an Extension + + The `Flask-WTF`_ extension expands on this pattern and adds a few + handful little helpers that make working with forms and Flask more + fun. You can get it from `PyPI + `_. + +.. _Flask-WTF: http://packages.python.org/Flask-WTF/ + +The Forms +--------- + +This is an example form for a typical registration page:: + + from wtforms import Form, BooleanField, TextField, validators + + class RegistrationForm(Form): + username = TextField('Username', [validators.Length(min=4, max=25)]) + email = TextField('Email Address', [validators.Length(min=6, max=35)]) + password = PasswordField('New Password', [ + validators.Required(), + validators.EqualTo('confirm', message='Passwords must match') + ]) + confirm = PasswordField('Repeat Password') + accept_tos = BooleanField('I accept the TOS', [validators.Required()]) + +In the View +----------- + +In the view function, the usage of this form looks like this:: + + @app.route('/register', methods=['GET', 'POST']) + def register(): + form = RegistrationForm(request.form) + if request.method == 'POST' and form.validate(): + user = User(form.username.data, form.email.data, + form.password.data) + db_session.add(user) + flash('Thanks for registering') + return redirect(url_for('login')) + return render_template('register.html', form=form) + +Notice that we are implying that the view is using SQLAlchemy here +(:ref:`sqlalchemy-pattern`) but this is no requirement of course. Adapt +the code as necessary. + +Things to remember: + +1. create the form from the request :attr:`~flask.request.form` value if + the data is submitted via the HTTP `POST` method and + :attr:`~flask.request.args` if the data is submitted as `GET`. +2. to validate the data, call the :func:`~wtforms.form.Form.validate` + method which will return `True` if the data validates, `False` + otherwise. +3. to access individual values from the form, access `form..data`. + +Forms in Templates +------------------ + +Now to the template side. When you pass the form to the templates you can +easily render them there. Look at the following example template to see +how easy this is. WTForms does half the form generation for us already. +To make it even nicer, we can write a macro that renders a field with +label and a list of errors if there are any. + +Here's an example `_formhelpers.html` template with such a macro: + +.. sourcecode:: html+jinja + + {% macro render_field(field) %} +

{{ field.label }} +
{{ field(**kwargs)|safe }} + {% if field.errors %} +
    + {% for error in field.errors %}
  • {{ error }}{% endfor %} +
+ {% endif %} +
+ {% endmacro %} + +This macro accepts a couple of keyword arguments that are forwarded to +WTForm's field function that renders the field for us. The keyword +arguments will be inserted as HTML attributes. So for example you can +call ``render_field(form.username, class='username')`` to add a class to +the input element. Note that WTForms returns standard Python unicode +strings, so we have to tell Jinja2 that this data is already HTML escaped +with the `|safe` filter. + +Here the `register.html` template for the function we used above which +takes advantage of the `_formhelpers.html` template: + +.. sourcecode:: html+jinja + + {% from "_formhelpers.html" import render_field %} +
+
+ {{ render_field(form.username) }} + {{ render_field(form.email) }} + {{ render_field(form.password) }} + {{ render_field(form.confirm) }} + {{ render_field(form.accept_tos) }} +
+

+

+ +For more information about WTForms, head over to the `WTForms +website`_. + +.. _WTForms: http://wtforms.simplecodes.com/ +.. _WTForms website: http://wtforms.simplecodes.com/ diff --git a/studio/static/doc/flask-docs/_sources/quickstart.txt b/studio/static/doc/flask-docs/_sources/quickstart.txt new file mode 100644 index 0000000..34aa3be --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/quickstart.txt @@ -0,0 +1,824 @@ +.. _quickstart: + +Quickstart +========== + +Eager to get started? This page gives a good introduction in how to get +started with Flask. This assumes you already have Flask installed. If +you do not, head over to the :ref:`installation` section. + + +A Minimal Application +--------------------- + +A minimal Flask application looks something like this:: + + from flask import Flask + app = Flask(__name__) + + @app.route('/') + def hello_world(): + return 'Hello World!' + + if __name__ == '__main__': + app.run() + +Just save it as `hello.py` or something similar and run it with your +Python interpreter. Make sure to not call your application `flask.py` +because this would conflict with Flask itself. + +:: + + $ python hello.py + * Running on http://127.0.0.1:5000/ + +Head over to `http://127.0.0.1:5000/ `_, you should +see your hello world greeting. + +So what did that code do? + +1. First we imported the :class:`~flask.Flask` class. An instance of this + class will be our WSGI application. The first argument is the name of + the application's module. If you are using a single module (like here) + you should use `__name__` because depending on if it's started as + application or imported as module the name will be different + (``'__main__'`` versus the actual import name). For more information + on that, have a look at the :class:`~flask.Flask` documentation. +2. Next we create an instance of it. We pass it the name of the module / + package. This is needed so that Flask knows where it should look for + templates, static files and so on. +3. Then we use the :meth:`~flask.Flask.route` decorator to tell Flask + what URL should trigger our function. +4. The function then has a name which is also used to generate URLs to + that particular function, and returns the message we want to display in + the user's browser. +5. Finally we use the :meth:`~flask.Flask.run` function to run the + local server with our application. The ``if __name__ == '__main__':`` + makes sure the server only runs if the script is executed directly from + the Python interpreter and not used as imported module. + +To stop the server, hit control-C. + +.. _public-server: + +.. admonition:: Externally Visible Server + + If you run the server you will notice that the server is only available + from your own computer, not from any other in the network. This is the + default because in debugging mode a user of the application can execute + arbitrary Python code on your computer. If you have `debug` disabled + or trust the users on your network, you can make the server publicly + available. + + Just change the call of the :meth:`~flask.Flask.run` method to look + like this:: + + app.run(host='0.0.0.0') + + This tells your operating system to listen on a public IP. + + +Debug Mode +---------- + +The :meth:`~flask.Flask.run` method is nice to start a local +development server, but you would have to restart it manually after each +change you do to code. That is not very nice and Flask can do better. If +you enable the debug support the server will reload itself on code changes +and also provide you with a helpful debugger if things go wrong. + +There are two ways to enable debugging. Either set that flag on the +application object:: + + app.debug = True + app.run() + +Or pass it to run:: + + app.run(debug=True) + +Both will have exactly the same effect. + +.. admonition:: Attention + + Even though the interactive debugger does not work in forking environments + (which makes it nearly impossible to use on production servers), it still + allows the execution of arbitrary code. That makes it a major security + risk and therefore it **must never be used on production machines**. + +Screenshot of the debugger in action: + +.. image:: _static/debugger.png + :align: center + :class: screenshot + :alt: screenshot of debugger in action + +.. admonition:: Working With Other Debuggers + + Debuggers interfere with each other. If you are using another debugger + (e.g. PyDev or IntelliJ), you may need to set ``app.debug = False``. + + +Routing +------- + +Modern web applications have beautiful URLs. This helps people remember +the URLs which is especially handy for applications that are used from +mobile devices with slower network connections. If the user can directly +go to the desired page without having to hit the index page it is more +likely they will like the page and come back next time. + +As you have seen above, the :meth:`~flask.Flask.route` decorator is used +to bind a function to a URL. Here are some basic examples:: + + @app.route('/') + def index(): + return 'Index Page' + + @app.route('/hello') + def hello(): + return 'Hello World' + +But there is more to it! You can make certain parts of the URL dynamic +and attach multiple rules to a function. + +Variable Rules +`````````````` + +To add variable parts to a URL you can mark these special sections as +````. Such a part is then passed as keyword argument to +your function. Optionally a converter can be specified by specifying a +rule with ````. Here are some nice examples:: + + @app.route('/user/') + def show_user_profile(username): + # show the user profile for that user + pass + + @app.route('/post/') + def show_post(post_id): + # show the post with the given id, the id is an integer + pass + +The following converters exist: + +=========== =========================================== +`int` accepts integers +`float` like `int` but for floating point values +`path` like the default but also accepts slashes +=========== =========================================== + +.. admonition:: Unique URLs / Redirection Behaviour + + Flask's URL rules are based on Werkzeug's routing module. The idea + behind that module is to ensure nice looking and also unique URLs based + on behaviour Apache and earlier servers coined. + + Take these two rules:: + + @app.route('/projects/') + def projects(): + pass + + @app.route('/about') + def about(): + pass + + They look rather similar, the difference is the trailing slash in the + URL *definition*. In the first case, the canonical URL for the + `projects` endpoint has a trailing slash. It's similar to a folder in + that sense. Accessing it without a trailing slash will cause Flask to + redirect to the canonical URL with the trailing slash. + + However in the second case the URL is defined without a slash so it + behaves similar to a file and accessing the URL with a trailing slash + will be a 404 error. + + Why is this? This allows relative URLs to continue working if users + access the page when they forget a trailing slash. This behaviour is + also consistent with how Apache and other servers work. Also, the URLs + will stay unique which helps search engines not indexing the same page + twice. + + +.. _url-building: + +URL Building +```````````` + +If it can match URLs, can it also generate them? Of course it can. To +build a URL to a specific function you can use the :func:`~flask.url_for` +function. It accepts the name of the function as first argument and a +number of keyword arguments, each corresponding to the variable part of +the URL rule. Unknown variable parts are appended to the URL as query +parameter. Here are some examples: + +>>> from flask import Flask, url_for +>>> app = Flask(__name__) +>>> @app.route('/') +... def index(): pass +... +>>> @app.route('/login') +... def login(): pass +... +>>> @app.route('/user/') +... def profile(username): pass +... +>>> with app.test_request_context(): +... print url_for('index') +... print url_for('login') +... print url_for('login', next='/') +... print url_for('profile', username='John Doe') +... +/ +/login +/login?next=/ +/user/John%20Doe + +(This also uses the :meth:`~flask.Flask.test_request_context` method +explained below. It basically tells Flask to think we are handling a +request even though we are not, we are in an interactive Python shell. +Have a look at the explanation below. :ref:`context-locals`). + +Why would you want to build URLs instead of hardcoding them in your +templates? There are three good reasons for this: + +1. reversing is often more descriptive than hardcoding the URLs. Also and + more importantly you can change URLs in one go without having to change + the URLs all over the place. +2. URL building will handle escaping of special characters and Unicode + data transparently for you, you don't have to deal with that. +3. If your application is placed outside the URL root (so say in + ``/myapplication`` instead of ``/``), :func:`~flask.url_for` will + handle that properly for you. + + +HTTP Methods +```````````` + +HTTP (the protocol web applications are speaking) knows different methods +to access URLs. By default a route only answers to `GET` requests, but +that can be changed by providing the `methods` argument to the +:meth:`~flask.Flask.route` decorator. Here are some examples:: + + @app.route('/login', methods=['GET', 'POST']) + def login(): + if request.method == 'POST': + do_the_login() + else: + show_the_login_form() + +If `GET` is present, `HEAD` will be added automatically for you. You +don't have to deal with that. It will also make sure that `HEAD` requests +are handled like the `HTTP RFC`_ (the document describing the HTTP +protocol) demands, so you can completely ignore that part of the HTTP +specification. Likewise as of Flask 0.6, `OPTIONS` is implemented for you +as well automatically. + +You have no idea what an HTTP method is? Worry not, here is a quick +introduction to HTTP methods and why they matter: + +The HTTP method (also often called "the verb") tells the server what the +clients wants to *do* with the requested page. The following methods are +very common: + +`GET` + The browser tells the server to just *get* the information stored on + that page and send it. This is probably the most common method. + +`HEAD` + The browser tells the server to get the information, but it is only + interested in the *headers*, not the content of the page. An + application is supposed to handle that as if a `GET` request was + received but to not deliver the actual content. In Flask you don't + have to deal with that at all, the underlying Werkzeug library handles + that for you. + +`POST` + The browser tells the server that it wants to *post* some new + information to that URL and that the server must ensure the data is + stored and only stored once. This is how HTML forms are usually + transmitting data to the server. + +`PUT` + Similar to `POST` but the server might trigger the store procedure + multiple times by overwriting the old values more than once. Now you + might be asking why is this useful, but there are some good reasons + to do it this way. Consider that the connection gets lost during + transmission: in this situation a system between the browser and the + server might receive the request safely a second time without breaking + things. With `POST` that would not be possible because it must only + be triggered once. + +`DELETE` + Remove the information at the given location. + +`OPTIONS` + Provides a quick way for a client to figure out which methods are + supported by this URL. Starting with Flask 0.6, this is implemented + for you automatically. + +Now the interesting part is that in HTML4 and XHTML1, the only methods a +form can submit to the server are `GET` and `POST`. But with JavaScript +and future HTML standards you can use the other methods as well. Furthermore +HTTP has become quite popular lately and browsers are no longer the only +clients that are using HTTP. For instance, many revision control system +use it. + +.. _HTTP RFC: http://www.ietf.org/rfc/rfc2068.txt + +Static Files +------------ + +Dynamic web applications need static files as well. That's usually where +the CSS and JavaScript files are coming from. Ideally your web server is +configured to serve them for you, but during development Flask can do that +as well. Just create a folder called `static` in your package or next to +your module and it will be available at `/static` on the application. + +To generate URLs to that part of the URL, use the special ``'static'`` URL +name:: + + url_for('static', filename='style.css') + +The file has to be stored on the filesystem as ``static/style.css``. + +Rendering Templates +------------------- + +Generating HTML from within Python is not fun, and actually pretty +cumbersome because you have to do the HTML escaping on your own to keep +the application secure. Because of that Flask configures the `Jinja2 +`_ template engine for you automatically. + +To render a template you can use the :func:`~flask.render_template` +method. All you have to do is to provide the name of the template and the +variables you want to pass to the template engine as keyword arguments. +Here's a simple example of how to render a template:: + + from flask import render_template + + @app.route('/hello/') + @app.route('/hello/') + def hello(name=None): + return render_template('hello.html', name=name) + +Flask will look for templates in the `templates` folder. So if your +application is a module, that folder is next to that module, if it's a +package it's actually inside your package: + +**Case 1**: a module:: + + /application.py + /templates + /hello.html + +**Case 2**: a package:: + + /application + /__init__.py + /templates + /hello.html + +For templates you can use the full power of Jinja2 templates. Head over +to the the official `Jinja2 Template Documentation +`_ for more information. + +Here is an example template: + +.. sourcecode:: html+jinja + + + Hello from Flask + {% if name %} +

Hello {{ name }}!

+ {% else %} +

Hello World!

+ {% endif %} + +Inside templates you also have access to the :class:`~flask.request`, +:class:`~flask.session` and :class:`~flask.g` [#]_ objects +as well as the :func:`~flask.get_flashed_messages` function. + +Templates are especially useful if inheritance is used. If you want to +know how that works, head over to the :ref:`template-inheritance` pattern +documentation. Basically template inheritance makes it possible to keep +certain elements on each page (like header, navigation and footer). + +Automatic escaping is enabled, so if name contains HTML it will be escaped +automatically. If you can trust a variable and you know that it will be +safe HTML (because for example it came from a module that converts wiki +markup to HTML) you can mark it as safe by using the +:class:`~jinja2.Markup` class or by using the ``|safe`` filter in the +template. Head over to the Jinja 2 documentation for more examples. + +Here is a basic introduction to how the :class:`~jinja2.Markup` class works: + +>>> from flask import Markup +>>> Markup('Hello %s!') % 'hacker' +Markup(u'Hello <blink>hacker</blink>!') +>>> Markup.escape('hacker') +Markup(u'<blink>hacker</blink>') +>>> Markup('Marked up » HTML').striptags() +u'Marked up \xbb HTML' + +.. versionchanged:: 0.5 + + Autoescaping is no longer enabled for all templates. The following + extensions for templates trigger autoescaping: ``.html``, ``.htm``, + ``.xml``, ``.xhtml``. Templates loaded from a string will have + autoescaping disabled. + +.. [#] Unsure what that :class:`~flask.g` object is? It's something in which + you can store information for your own needs, check the documentation of + that object (:class:`~flask.g`) and the :ref:`sqlite3` for more + information. + + +Accessing Request Data +---------------------- + +For web applications it's crucial to react to the data a client sent to +the server. In Flask this information is provided by the global +:class:`~flask.request` object. If you have some experience with Python +you might be wondering how that object can be global and how Flask +manages to still be threadsafe. The answer are context locals: + + +.. _context-locals: + +Context Locals +`````````````` + +.. admonition:: Insider Information + + If you want to understand how that works and how you can implement + tests with context locals, read this section, otherwise just skip it. + +Certain objects in Flask are global objects, but not of the usual kind. +These objects are actually proxies to objects that are local to a specific +context. What a mouthful. But that is actually quite easy to understand. + +Imagine the context being the handling thread. A request comes in and the +webserver decides to spawn a new thread (or something else, the +underlying object is capable of dealing with other concurrency systems +than threads as well). When Flask starts its internal request handling it +figures out that the current thread is the active context and binds the +current application and the WSGI environments to that context (thread). +It does that in an intelligent way that one application can invoke another +application without breaking. + +So what does this mean to you? Basically you can completely ignore that +this is the case unless you are doing something like unittesting. You +will notice that code that depends on a request object will suddenly break +because there is no request object. The solution is creating a request +object yourself and binding it to the context. The easiest solution for +unittesting is by using the :meth:`~flask.Flask.test_request_context` +context manager. In combination with the `with` statement it will bind a +test request so that you can interact with it. Here is an example:: + + from flask import request + + with app.test_request_context('/hello', method='POST'): + # now you can do something with the request until the + # end of the with block, such as basic assertions: + assert request.path == '/hello' + assert request.method == 'POST' + +The other possibility is passing a whole WSGI environment to the +:meth:`~flask.Flask.request_context` method:: + + from flask import request + + with app.request_context(environ): + assert request.method == 'POST' + +The Request Object +`````````````````` + +The request object is documented in the API section and we will not cover +it here in detail (see :class:`~flask.request`). Here is a broad overview of +some of the most common operations. First of all you have to import it from +the `flask` module:: + + from flask import request + +The current request method is available by using the +:attr:`~flask.request.method` attribute. To access form data (data +transmitted in a `POST` or `PUT` request) you can use the +:attr:`~flask.request.form` attribute. Here is a full example of the two +attributes mentioned above:: + + @app.route('/login', methods=['POST', 'GET']) + def login(): + error = None + if request.method == 'POST': + if valid_login(request.form['username'], + request.form['password']): + return log_the_user_in(request.form['username']) + else: + error = 'Invalid username/password' + # this is executed if the request method was GET or the + # credentials were invalid + +What happens if the key does not exist in the `form` attribute? In that +case a special :exc:`KeyError` is raised. You can catch it like a +standard :exc:`KeyError` but if you don't do that, a HTTP 400 Bad Request +error page is shown instead. So for many situations you don't have to +deal with that problem. + +To access parameters submitted in the URL (``?key=value``) you can use the +:attr:`~flask.request.args` attribute:: + + searchword = request.args.get('q', '') + +We recommend accessing URL parameters with `get` or by catching the +`KeyError` because users might change the URL and presenting them a 400 +bad request page in that case is not user friendly. + +For a full list of methods and attributes of the request object, head over +to the :class:`~flask.request` documentation. + + +File Uploads +```````````` + +You can handle uploaded files with Flask easily. Just make sure not to +forget to set the ``enctype="multipart/form-data"`` attribute on your HTML +form, otherwise the browser will not transmit your files at all. + +Uploaded files are stored in memory or at a temporary location on the +filesystem. You can access those files by looking at the +:attr:`~flask.request.files` attribute on the request object. Each +uploaded file is stored in that dictionary. It behaves just like a +standard Python :class:`file` object, but it also has a +:meth:`~werkzeug.datastructures.FileStorage.save` method that allows you to store that +file on the filesystem of the server. Here is a simple example showing how +that works:: + + from flask import request + + @app.route('/upload', methods=['GET', 'POST']) + def upload_file(): + if request.method == 'POST': + f = request.files['the_file'] + f.save('/var/www/uploads/uploaded_file.txt') + ... + +If you want to know how the file was named on the client before it was +uploaded to your application, you can access the +:attr:`~werkzeug.datastructures.FileStorage.filename` attribute. However please keep in +mind that this value can be forged so never ever trust that value. If you +want to use the filename of the client to store the file on the server, +pass it through the :func:`~werkzeug.utils.secure_filename` function that +Werkzeug provides for you:: + + from flask import request + from werkzeug import secure_filename + + @app.route('/upload', methods=['GET', 'POST']) + def upload_file(): + if request.method == 'POST': + f = request.files['the_file'] + f.save('/var/www/uploads/' + secure_filename(f.filename)) + ... + +For some better examples, checkout the :ref:`uploading-files` pattern. + +Cookies +``````` + +To access cookies you can use the :attr:`~flask.Request.cookies` +attribute. To set cookies you can use the +:attr:`~flask.Response.set_cookie` method of response objects. The +:attr:`~flask.Request.cookies` attribute of request objects is a +dictionary with all the cookies the client transmits. If you want to use +sessions, do not use the cookies directly but instead use the +:ref:`sessions` in Flask that add some security on top of cookies for you. + +Reading cookies:: + + from flask import request + + @app.route('/') + def index(): + username = request.cookies.get('username') + # use cookies.get(key) instead of cookies[key] to not get a + # KeyError if the cookie is missing. + +Storing cookies:: + + from flask import make_response + + @app.route('/') + def index(): + resp = make_response(render_template(...)) + resp.set_cookie('username', 'the username') + return resp + +Note that cookies are set on response objects. Since you normally you +just return strings from the view functions Flask will convert them into +response objects for you. If you explicitly want to do that you can use +the :meth:`~flask.make_response` function and then modify it. + +Sometimes you might want to set a cookie at a point where the response +object does not exist yet. This is possible by utilizing the +:ref:`deferred-callbacks` pattern. + +For this also see :ref:`about-responses`. + +Redirects and Errors +-------------------- + +To redirect a user to somewhere else you can use the +:func:`~flask.redirect` function. To abort a request early with an error +code use the :func:`~flask.abort` function. Here an example how this works:: + + from flask import abort, redirect, url_for + + @app.route('/') + def index(): + return redirect(url_for('login')) + + @app.route('/login') + def login(): + abort(401) + this_is_never_executed() + +This is a rather pointless example because a user will be redirected from +the index to a page they cannot access (401 means access denied) but it +shows how that works. + +By default a black and white error page is shown for each error code. If +you want to customize the error page, you can use the +:meth:`~flask.Flask.errorhandler` decorator:: + + from flask import render_template + + @app.errorhandler(404) + def page_not_found(error): + return render_template('page_not_found.html'), 404 + +Note the ``404`` after the :func:`~flask.render_template` call. This +tells Flask that the status code of that page should be 404 which means +not found. By default 200 is assumed which translates to: all went well. + +.. _about-responses: + +About Responses +--------------- + +The return value from a view function is automatically converted into a +response object for you. If the return value is a string it's converted +into a response object with the string as response body, an ``200 OK`` +error code and a ``text/html`` mimetype. The logic that Flask applies to +converting return values into response objects is as follows: + +1. If a response object of the correct type is returned it's directly + returned from the view. +2. If it's a string, a response object is created with that data and the + default parameters. +3. If a tuple is returned the response object is created by passing the + tuple as arguments to the response object's constructor. +4. If neither of that works, Flask will assume the return value is a + valid WSGI application and converts that into a response object. + +If you want to get hold of the resulting response object inside the view +you can use the :func:`~flask.make_response` function. + +Imagine you have a view like this: + +.. sourcecode:: python + + @app.errorhandler(404) + def not_found(error): + return render_template('error.html'), 404 + +You just need to wrap the return expression with +:func:`~flask.make_response` and get the result object to modify it, then +return it: + +.. sourcecode:: python + + @app.errorhandler(404) + def not_found(error): + resp = make_response(render_template('error.html'), 404) + resp.headers['X-Something'] = 'A value' + return resp + +.. _sessions: + +Sessions +-------- + +Besides the request object there is also a second object called +:class:`~flask.session` that allows you to store information specific to a +user from one request to the next. This is implemented on top of cookies +for you and signs the cookies cryptographically. What this means is that +the user could look at the contents of your cookie but not modify it, +unless they know the secret key used for signing. + +In order to use sessions you have to set a secret key. Here is how +sessions work:: + + from flask import Flask, session, redirect, url_for, escape, request + + app = Flask(__name__) + + @app.route('/') + def index(): + if 'username' in session: + return 'Logged in as %s' % escape(session['username']) + return 'You are not logged in' + + @app.route('/login', methods=['GET', 'POST']) + def login(): + if request.method == 'POST': + session['username'] = request.form['username'] + return redirect(url_for('index')) + return ''' +
+

+

+

+ ''' + + @app.route('/logout') + def logout(): + # remove the username from the session if its there + session.pop('username', None) + return redirect(url_for('index')) + + # set the secret key. keep this really secret: + app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' + +The here mentioned :func:`~flask.escape` does escaping for you if you are +not using the template engine (like in this example). + +.. admonition:: How to generate good secret keys + + The problem with random is that it's hard to judge what random is. And + a secret key should be as random as possible. Your operating system + has ways to generate pretty random stuff based on a cryptographic + random generator which can be used to get such a key: + + >>> import os + >>> os.urandom(24) + '\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O`_ for more +information. + +Hooking in WSGI Middlewares +--------------------------- + +If you want to add a WSGI middleware to your application you can wrap the +internal WSGI application. For example if you want to use one of the +middlewares from the Werkzeug package to work around bugs in lighttpd, you +can do it like this:: + + from werkzeug.contrib.fixers import LighttpdCGIRootFix + app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app) diff --git a/studio/static/doc/flask-docs/_sources/reqcontext.txt b/studio/static/doc/flask-docs/_sources/reqcontext.txt new file mode 100644 index 0000000..0249b88 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/reqcontext.txt @@ -0,0 +1,239 @@ +.. _request-context: + +The Request Context +=================== + +This document describes the behavior in Flask 0.7 which is mostly in line +with the old behavior but has some small, subtle differences. + +One of the design ideas behind Flask is that there are two different +“states” in which code is executed. The application setup state in which +the application implicitly is on the module level. It starts when the +:class:`Flask` object is instantiated, and it implicitly ends when the +first request comes in. While the application is in this state a few +assumptions are true: + +- the programmer can modify the application object safely. +- no request handling happened so far +- you have to have a reference to the application object in order to + modify it, there is no magic proxy that can give you a reference to + the application object you're currently creating or modifying. + +On the contrast, during request handling, a couple of other rules exist: + +- while a request is active, the context local objects + (:data:`flask.request` and others) point to the current request. +- any code can get hold of these objects at any time. + +The magic that makes this works is internally referred in Flask as the +“request context”. + +Diving into Context Locals +-------------------------- + +Say you have a utility function that returns the URL the user should be +redirected to. Imagine it would always redirect to the URL's ``next`` +parameter or the HTTP referrer or the index page:: + + from flask import request, url_for + + def redirect_url(): + return request.args.get('next') or \ + request.referrer or \ + url_for('index') + +As you can see, it accesses the request object. If you try to run this +from a plain Python shell, this is the exception you will see: + +>>> redirect_url() +Traceback (most recent call last): + File "", line 1, in +AttributeError: 'NoneType' object has no attribute 'request' + +That makes a lot of sense because we currently do not have a request we +could access. So we have to make a request and bind it to the current +context. The :attr:`~flask.Flask.test_request_context` method can create +us a :class:`~flask.ctx.RequestContext`: + +>>> ctx = app.test_request_context('/?next=http://example.com/') + +This context can be used in two ways. Either with the `with` statement +or by calling the :meth:`~flask.ctx.RequestContext.push` and +:meth:`~flask.ctx.RequestContext.pop` methods: + +>>> ctx.push() + +From that point onwards you can work with the request object: + +>>> redirect_url() +u'http://example.com/' + +Until you call `pop`: + +>>> ctx.pop() + +Because the request context is internally maintained as a stack you can +push and pop multiple times. This is very handy to implement things like +internal redirects. + +For more information of how to utilize the request context from the +interactive Python shell, head over to the :ref:`shell` chapter. + +How the Context Works +--------------------- + +If you look into how the Flask WSGI application internally works, you will +find a piece of code that looks very much like this:: + + def wsgi_app(self, environ): + with self.request_context(environ): + try: + response = self.full_dispatch_request() + except Exception, e: + response = self.make_response(self.handle_exception(e)) + return response(environ, start_response) + +The method :meth:`~Flask.request_context` returns a new +:class:`~flask.ctx.RequestContext` object and uses it in combination with +the `with` statement to bind the context. Everything that is called from +the same thread from this point onwards until the end of the `with` +statement will have access to the request globals (:data:`flask.request` +and others). + +The request context internally works like a stack: The topmost level on +the stack is the current active request. +:meth:`~flask.ctx.RequestContext.push` adds the context to the stack on +the very top, :meth:`~flask.ctx.RequestContext.pop` removes it from the +stack again. On popping the application's +:func:`~flask.Flask.teardown_request` functions are also executed. + +.. _callbacks-and-errors: + +Callbacks and Errors +-------------------- + +What happens if an error occurs in Flask during request processing? This +particular behavior changed in 0.7 because we wanted to make it easier to +understand what is actually happening. The new behavior is quite simple: + +1. Before each request, :meth:`~flask.Flask.before_request` functions are + executed. If one of these functions return a response, the other + functions are no longer called. In any case however the return value + is treated as a replacement for the view's return value. + +2. If the :meth:`~flask.Flask.before_request` functions did not return a + response, the regular request handling kicks in and the view function + that was matched has the chance to return a response. + +3. The return value of the view is then converted into an actual response + object and handed over to the :meth:`~flask.Flask.after_request` + functions which have the chance to replace it or modify it in place. + +4. At the end of the request the :meth:`~flask.Flask.teardown_request` + functions are executed. This always happens, even in case of an + unhandled exception down the road or if a before-request handler was + not executed yet or at all (for example in test environments sometimes + you might want to not execute before-request callbacks). + +Now what happens on errors? In production mode if an exception is not +caught, the 500 internal server handler is called. In development mode +however the exception is not further processed and bubbles up to the WSGI +server. That way things like the interactive debugger can provide helpful +debug information. + +An important change in 0.7 is that the internal server error is now no +longer post processed by the after request callbacks and after request +callbacks are no longer guaranteed to be executed. This way the internal +dispatching code looks cleaner and is easier to customize and understand. + +The new teardown functions are supposed to be used as a replacement for +things that absolutely need to happen at the end of request. + +Teardown Callbacks +------------------ + +The teardown callbacks are special callbacks in that they are executed at +at different point. Strictly speaking they are independent of the actual +request handling as they are bound to the lifecycle of the +:class:`~flask.ctx.RequestContext` object. When the request context is +popped, the :meth:`~flask.Flask.teardown_request` functions are called. + +This is important to know if the life of the request context is prolonged +by using the test client in a with statement or when using the request +context from the command line:: + + with app.test_client() as client: + resp = client.get('/foo') + # the teardown functions are still not called at that point + # even though the response ended and you have the response + # object in your hand + + # only when the code reaches this point the teardown functions + # are called. Alternatively the same thing happens if another + # request was triggered from the test client + +It's easy to see the behavior from the command line: + +>>> app = Flask(__name__) +>>> @app.teardown_request +... def teardown_request(exception=None): +... print 'this runs after request' +... +>>> ctx = app.test_request_context() +>>> ctx.push() +>>> ctx.pop() +this runs after request +>>> + +Keep in mind that teardown callbacks are always executed, even if +before-request callbacks were not executed yet but an exception happened. +Certain parts of the test system might also temporarily create a request +context without calling the before-request handlers. Make sure to write +your teardown-request handlers in a way that they will never fail. + +.. _notes-on-proxies: + +Notes On Proxies +---------------- + +Some of the objects provided by Flask are proxies to other objects. The +reason behind this is that these proxies are shared between threads and +they have to dispatch to the actual object bound to a thread behind the +scenes as necessary. + +Most of the time you don't have to care about that, but there are some +exceptions where it is good to know that this object is an actual proxy: + +- The proxy objects do not fake their inherited types, so if you want to + perform actual instance checks, you have to do that on the instance + that is being proxied (see `_get_current_object` below). +- if the object reference is important (so for example for sending + :ref:`signals`) + +If you need to get access to the underlying object that is proxied, you +can use the :meth:`~werkzeug.local.LocalProxy._get_current_object` method:: + + app = current_app._get_current_object() + my_signal.send(app) + +Context Preservation on Error +----------------------------- + +If an error occurs or not, at the end of the request the request context +is popped and all data associated with it is destroyed. During +development however that can be problematic as you might want to have the +information around for a longer time in case an exception occurred. In +Flask 0.6 and earlier in debug mode, if an exception occurred, the +request context was not popped so that the interactive debugger can still +provide you with important information. + +Starting with Flask 0.7 you have finer control over that behavior by +setting the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. By +default it's linked to the setting of ``DEBUG``. If the application is in +debug mode the context is preserved, in production mode it's not. + +Do not force activate ``PRESERVE_CONTEXT_ON_EXCEPTION`` in production mode +as it will cause your application to leak memory on exceptions. However +it can be useful during development to get the same error preserving +behavior as in development mode when attempting to debug an error that +only occurs under production settings. diff --git a/studio/static/doc/flask-docs/_sources/security.txt b/studio/static/doc/flask-docs/_sources/security.txt new file mode 100644 index 0000000..909ef53 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/security.txt @@ -0,0 +1,175 @@ +Security Considerations +======================= + +Web applications usually face all kinds of security problems and it's very +hard to get everything right. Flask tries to solve a few of these things +for you, but there are a couple more you have to take care of yourself. + +.. _xss: + +Cross-Site Scripting (XSS) +-------------------------- + +Cross site scripting is the concept of injecting arbitrary HTML (and with +it JavaScript) into the context of a website. To remedy this, developers +have to properly escape text so that it cannot include arbitrary HTML +tags. For more information on that have a look at the Wikipedia article +on `Cross-Site Scripting +`_. + +Flask configures Jinja2 to automatically escape all values unless +explicitly told otherwise. This should rule out all XSS problems caused +in templates, but there are still other places where you have to be +careful: + +- generating HTML without the help of Jinja2 +- calling :class:`~flask.Markup` on data submitted by users +- sending out HTML from uploaded files, never do that, use the + `Content-Disposition: attachment` header to prevent that problem. +- sending out textfiles from uploaded files. Some browsers are using + content-type guessing based on the first few bytes so users could + trick a browser to execute HTML. + +Another thing that is very important are unquoted attributes. While +Jinja2 can protect you from XSS issues by escaping HTML, there is one +thing it cannot protect you from: XSS by attribute injection. To counter +this possible attack vector, be sure to always quote your attributes with +either double or single quotes when using Jinja expressions in them: + +.. sourcecode:: html+jinja + + the text + +Why is this necessary? Because if you would not be doing that, an +attacker could easily inject custom JavaScript handlers. For example an +attacker could inject this piece of HTML+JavaScript: + +.. sourcecode:: html + + onmouseover=alert(document.cookie) + +When the user would then move with the mouse over the link, the cookie +would be presented to the user in an alert window. But instead of showing +the cookie to the user, a good attacker might also execute any other +JavaScript code. In combination with CSS injections the attacker might +even make the element fill out the entire page so that the user would +just have to have the mouse anywhere on the page to trigger the attack. + +Cross-Site Request Forgery (CSRF) +--------------------------------- + +Another big problem is CSRF. This is a very complex topic and I won't +outline it here in detail just mention what it is and how to theoretically +prevent it. + +If your authentication information is stored in cookies, you have implicit +state management. The state of "being logged in" is controlled by a +cookie, and that cookie is sent with each request to a page. +Unfortunately that includes requests triggered by 3rd party sites. If you +don't keep that in mind, some people might be able to trick your +application's users with social engineering to do stupid things without +them knowing. + +Say you have a specific URL that, when you sent `POST` requests to will +delete a user's profile (say `http://example.com/user/delete`). If an +attacker now creates a page that sends a post request to that page with +some JavaScript they just has to trick some users to load that page and +their profiles will end up being deleted. + +Imagine you were to run Facebook with millions of concurrent users and +someone would send out links to images of little kittens. When users +would go to that page, their profiles would get deleted while they are +looking at images of fluffy cats. + +How can you prevent that? Basically for each request that modifies +content on the server you would have to either use a one-time token and +store that in the cookie **and** also transmit it with the form data. +After receiving the data on the server again, you would then have to +compare the two tokens and ensure they are equal. + +Why does Flask not do that for you? The ideal place for this to happen is +the form validation framework, which does not exist in Flask. + +.. _json-security: + +JSON Security +------------- + +.. admonition:: ECMAScript 5 Changes + + Starting with ECMAScript 5 the behavior of literals changed. Now they + are not constructed with the constructor of ``Array`` and others, but + with the builtin constructor of ``Array`` which closes this particular + attack vector. + +JSON itself is a high-level serialization format, so there is barely +anything that could cause security problems, right? You can't declare +recursive structures that could cause problems and the only thing that +could possibly break are very large responses that can cause some kind of +denial of service at the receiver's side. + +However there is a catch. Due to how browsers work the CSRF issue comes +up with JSON unfortunately. Fortunately there is also a weird part of the +JavaScript specification that can be used to solve that problem easily and +Flask is kinda doing that for you by preventing you from doing dangerous +stuff. Unfortunately that protection is only there for +:func:`~flask.jsonify` so you are still at risk when using other ways to +generate JSON. + +So what is the issue and how to avoid it? The problem are arrays at +top-level in JSON. Imagine you send the following data out in a JSON +request. Say that's exporting the names and email addresses of all your +friends for a part of the user interface that is written in JavaScript. +Not very uncommon: + +.. sourcecode:: javascript + + [ + {"username": "admin", + "email": "admin@localhost"} + ] + +And it is doing that of course only as long as you are logged in and only +for you. And it is doing that for all `GET` requests to a certain URL, +say the URL for that request is +``http://example.com/api/get_friends.json``. + +So now what happens if a clever hacker is embedding this to his website +and social engineers a victim to visiting his site: + +.. sourcecode:: html + + + + + +If you know a bit of JavaScript internals you might know that it's +possible to patch constructors and register callbacks for setters. An +attacker can use this (like above) to get all the data you exported in +your JSON file. The browser will totally ignore the ``application/json`` +mimetype if ``text/javascript`` is defined as content type in the script +tag and evaluate that as JavaScript. Because top-level array elements are +allowed (albeit useless) and we hooked in our own constructor, after that +page loaded the data from the JSON response is in the `captured` array. + +Because it is a syntax error in JavaScript to have an object literal +(``{...}``) toplevel an attacker could not just do a request to an +external URL with the script tag to load up the data. So what Flask does +is to only allow objects as toplevel elements when using +:func:`~flask.jsonify`. Make sure to do the same when using an ordinary +JSON generate function. diff --git a/studio/static/doc/flask-docs/_sources/shell.txt b/studio/static/doc/flask-docs/_sources/shell.txt new file mode 100644 index 0000000..61b9dc0 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/shell.txt @@ -0,0 +1,93 @@ +.. _shell: + +Working with the Shell +====================== + +.. versionadded:: 0.3 + +One of the reasons everybody loves Python is the interactive shell. It +basically allows you to execute Python commands in real time and +immediately get results back. Flask itself does not come with an +interactive shell, because it does not require any specific setup upfront, +just import your application and start playing around. + +There are however some handy helpers to make playing around in the shell a +more pleasant experience. The main issue with interactive console +sessions is that you're not triggering a request like a browser does which +means that :data:`~flask.g`, :data:`~flask.request` and others are not +available. But the code you want to test might depend on them, so what +can you do? + +This is where some helper functions come in handy. Keep in mind however +that these functions are not only there for interactive shell usage, but +also for unittesting and other situations that require a faked request +context. + +Generally it's recommended that you read the :ref:`request-context` +chapter of the documentation first. + +Creating a Request Context +-------------------------- + +The easiest way to create a proper request context from the shell is by +using the :attr:`~flask.Flask.test_request_context` method which creates +us a :class:`~flask.ctx.RequestContext`: + +>>> ctx = app.test_request_context() + +Normally you would use the `with` statement to make this request object +active, but in the shell it's easier to use the +:meth:`~flask.ctx.RequestContext.push` and +:meth:`~flask.ctx.RequestContext.pop` methods by hand: + +>>> ctx.push() + +From that point onwards you can work with the request object until you +call `pop`: + +>>> ctx.pop() + +Firing Before/After Request +--------------------------- + +By just creating a request context, you still don't have run the code that +is normally run before a request. This might result in your database +being unavailable if you are connecting to the database in a +before-request callback or the current user not being stored on the +:data:`~flask.g` object etc. + +This however can easily be done yourself. Just call +:meth:`~flask.Flask.preprocess_request`: + +>>> ctx = app.test_request_context() +>>> ctx.push() +>>> app.preprocess_request() + +Keep in mind that the :meth:`~flask.Flask.preprocess_request` function +might return a response object, in that case just ignore it. + +To shutdown a request, you need to trick a bit before the after request +functions (triggered by :meth:`~flask.Flask.process_response`) operate on +a response object: + +>>> app.process_response(app.response_class()) + +>>> ctx.pop() + +The functions registered as :meth:`~flask.Flask.teardown_request` are +automatically called when the context is popped. So this is the perfect +place to automatically tear down resources that were needed by the request +context (such as database connections). + + +Further Improving the Shell Experience +-------------------------------------- + +If you like the idea of experimenting in a shell, create yourself a module +with stuff you want to star import into your interactive session. There +you could also define some more helper methods for common things such as +initializing the database, dropping tables etc. + +Just put them into a module (like `shelltools` and import from there): + +>>> from shelltools import * diff --git a/studio/static/doc/flask-docs/_sources/signals.txt b/studio/static/doc/flask-docs/_sources/signals.txt new file mode 100644 index 0000000..0d1d9ee --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/signals.txt @@ -0,0 +1,255 @@ +.. _signals: + +Signals +======= + +.. versionadded:: 0.6 + +Starting with Flask 0.6, there is integrated support for signalling in +Flask. This support is provided by the excellent `blinker`_ library and +will gracefully fall back if it is not available. + +What are signals? Signals help you decouple applications by sending +notifications when actions occur elsewhere in the core framework or +another Flask extensions. In short, signals allow certain senders to +notify subscribers that something happened. + +Flask comes with a couple of signals and other extensions might provide +more. Also keep in mind that signals are intended to notify subscribers +and should not encourage subscribers to modify data. You will notice that +there are signals that appear to do the same thing like some of the +builtin decorators do (eg: :data:`~flask.request_started` is very similar +to :meth:`~flask.Flask.before_request`). There are however difference in +how they work. The core :meth:`~flask.Flask.before_request` handler for +example is executed in a specific order and is able to abort the request +early by returning a response. In contrast all signal handlers are +executed in undefined order and do not modify any data. + +The big advantage of signals over handlers is that you can safely +subscribe to them for the split of a second. These temporary +subscriptions are helpful for unittesting for example. Say you want to +know what templates were rendered as part of a request: signals allow you +to do exactly that. + +Subscribing to Signals +---------------------- + +To subscribe to a signal, you can use the +:meth:`~blinker.base.Signal.connect` method of a signal. The first +argument is the function that should be called when the signal is emitted, +the optional second argument specifies a sender. To unsubscribe from a +signal, you can use the :meth:`~blinker.base.Signal.disconnect` method. + +For all core Flask signals, the sender is the application that issued the +signal. When you subscribe to a signal, be sure to also provide a sender +unless you really want to listen for signals of all applications. This is +especially true if you are developing an extension. + +Here for example a helper context manager that can be used to figure out +in a unittest which templates were rendered and what variables were passed +to the template:: + + from flask import template_rendered + from contextlib import contextmanager + + @contextmanager + def captured_templates(app): + recorded = [] + def record(sender, template, context): + recorded.append((template, context)) + template_rendered.connect(record, app) + try: + yield recorded + finally: + template_rendered.disconnect(record, app) + +This can now easily be paired with a test client:: + + with captured_templates(app) as templates: + rv = app.test_client().get('/') + assert rv.status_code == 200 + assert len(templates) == 1 + template, context = templates[0] + assert template.name == 'index.html' + assert len(context['items']) == 10 + +All the template rendering in the code issued by the application `app` +in the body of the `with` block will now be recorded in the `templates` +variable. Whenever a template is rendered, the template object as well as +context are appended to it. + +Additionally there is a convenient helper method +(:meth:`~blinker.base.Signal.connected_to`). that allows you to +temporarily subscribe a function to a signal with is a context manager on +its own. Because the return value of the context manager cannot be +specified that way one has to pass the list in as argument:: + + from flask import template_rendered + + def captured_templates(app, recorded): + def record(sender, template, context): + recorded.append((template, context)) + return template_rendered.connected_to(record, app) + +The example above would then look like this:: + + templates = [] + with captured_templates(app, templates): + ... + template, context = templates[0] + +.. admonition:: Blinker API Changes + + The :meth:`~blinker.base.Signal.connected_to` method arrived in Blinker + with version 1.1. + +Creating Signals +---------------- + +If you want to use signals in your own application, you can use the +blinker library directly. The most common use case are named signals in a +custom :class:`~blinker.base.Namespace`.. This is what is recommended +most of the time:: + + from blinker import Namespace + my_signals = Namespace() + +Now you can create new signals like this:: + + model_saved = my_signals.signal('model-saved') + +The name for the signal here makes it unique and also simplifies +debugging. You can access the name of the signal with the +:attr:`~blinker.base.NamedSignal.name` attribute. + +.. admonition:: For Extension Developers + + If you are writing a Flask extension and you want to gracefully degrade for + missing blinker installations, you can do so by using the + :class:`flask.signals.Namespace` class. + +Sending Signals +--------------- + +If you want to emit a signal, you can do so by calling the +:meth:`~blinker.base.Signal.send` method. It accepts a sender as first +argument and optionally some keyword arguments that are forwarded to the +signal subscribers:: + + class Model(object): + ... + + def save(self): + model_saved.send(self) + +Try to always pick a good sender. If you have a class that is emitting a +signal, pass `self` as sender. If you emitting a signal from a random +function, you can pass ``current_app._get_current_object()`` as sender. + +.. admonition:: Passing Proxies as Senders + + Never pass :data:`~flask.current_app` as sender to a signal. Use + ``current_app._get_current_object()`` instead. The reason for this is + that :data:`~flask.current_app` is a proxy and not the real application + object. + +Decorator Based Signal Subscriptions +------------------------------------ + +With Blinker 1.1 you can also easily subscribe to signals by using the new +:meth:`~blinker.base.NamedSignal.connect_via` decorator:: + + from flask import template_rendered + + @template_rendered.connect_via(app) + def when_template_rendered(sender, template, context): + print 'Template %s is rendered with %s' % (template.name, context) + +Core Signals +------------ + +.. when modifying this list, also update the one in api.rst + +The following signals exist in Flask: + +.. data:: flask.template_rendered + :noindex: + + This signal is sent when a template was successfully rendered. The + signal is invoked with the instance of the template as `template` + and the context as dictionary (named `context`). + + Example subscriber:: + + def log_template_renders(sender, template, context): + sender.logger.debug('Rendering template "%s" with context %s', + template.name or 'string template', + context) + + from flask import template_rendered + template_rendered.connect(log_template_renders, app) + +.. data:: flask.request_started + :noindex: + + This signal is sent before any request processing started but when the + request context was set up. Because the request context is already + bound, the subscriber can access the request with the standard global + proxies such as :class:`~flask.request`. + + Example subscriber:: + + def log_request(sender): + sender.logger.debug('Request context is set up') + + from flask import request_started + request_started.connect(log_request, app) + +.. data:: flask.request_finished + :noindex: + + This signal is sent right before the response is sent to the client. + It is passed the response to be sent named `response`. + + Example subscriber:: + + def log_response(sender, response): + sender.logger.debug('Request context is about to close down. ' + 'Response: %s', response) + + from flask import request_finished + request_finished.connect(log_response, app) + +.. data:: flask.got_request_exception + :noindex: + + This signal is sent when an exception happens during request processing. + It is sent *before* the standard exception handling kicks in and even + in debug mode, where no exception handling happens. The exception + itself is passed to the subscriber as `exception`. + + Example subscriber:: + + def log_exception(sender, exception): + sender.logger.debug('Got exception during processing: %s', exception) + + from flask import got_request_exception + got_request_exception.connect(log_exception, app) + +.. data:: flask.request_tearing_down + :noindex: + + This signal is sent when the request is tearing down. This is always + called, even if an exception is caused. Currently functions listening + to this signal are called after the regular teardown handlers, but this + is not something you can rely on. + + Example subscriber:: + + def close_db_connection(sender): + session.close() + + from flask import request_tearing_down + request_tearing_down.connect(close_db_connection, app) + +.. _blinker: http://pypi.python.org/pypi/blinker diff --git a/studio/static/doc/flask-docs/_sources/styleguide.txt b/studio/static/doc/flask-docs/_sources/styleguide.txt new file mode 100644 index 0000000..d46ecd0 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/styleguide.txt @@ -0,0 +1,200 @@ +Pocoo Styleguide +================ + +The Pocoo styleguide is the styleguide for all Pocoo Projects, including +Flask. This styleguide is a requirement for Patches to Flask and a +recommendation for Flask extensions. + +In general the Pocoo Styleguide closely follows :pep:`8` with some small +differences and extensions. + +General Layout +-------------- + +Indentation: + 4 real spaces. No tabs, no exceptions. + +Maximum line length: + 79 characters with a soft limit for 84 if absolutely necessary. Try + to avoid too nested code by cleverly placing `break`, `continue` and + `return` statements. + +Continuing long statements: + To continue a statement you can use backslashes in which case you should + align the next line with the last dot or equal sign, or indent four + spaces:: + + this_is_a_very_long(function_call, 'with many parameters') \ + .that_returns_an_object_with_an_attribute + + MyModel.query.filter(MyModel.scalar > 120) \ + .order_by(MyModel.name.desc()) \ + .limit(10) + + If you break in a statement with parentheses or braces, align to the + braces:: + + this_is_a_very_long(function_call, 'with many parameters', + 23, 42, 'and even more') + + For lists or tuples with many items, break immediately after the + opening brace:: + + items = [ + 'this is the first', 'set of items', 'with more items', + 'to come in this line', 'like this' + ] + +Blank lines: + Top level functions and classes are separated by two lines, everything + else by one. Do not use too many blank lines to separate logical + segments in code. Example:: + + def hello(name): + print 'Hello %s!' % name + + + def goodbye(name): + print 'See you %s.' % name + + + class MyClass(object): + """This is a simple docstring""" + + def __init__(self, name): + self.name = name + + def get_annoying_name(self): + return self.name.upper() + '!!!!111' + +Expressions and Statements +-------------------------- + +General whitespace rules: + - No whitespace for unary operators that are not words + (e.g.: ``-``, ``~`` etc.) as well on the inner side of parentheses. + - Whitespace is placed between binary operators. + + Good:: + + exp = -1.05 + value = (item_value / item_count) * offset / exp + value = my_list[index] + value = my_dict['key'] + + Bad:: + + exp = - 1.05 + value = ( item_value / item_count ) * offset / exp + value = (item_value/item_count)*offset/exp + value=( item_value/item_count ) * offset/exp + value = my_list[ index ] + value = my_dict ['key'] + +Yoda statements are a no-go: + Never compare constant with variable, always variable with constant: + + Good:: + + if method == 'md5': + pass + + Bad:: + + if 'md5' == method: + pass + +Comparisons: + - against arbitrary types: ``==`` and ``!=`` + - against singletons with ``is`` and ``is not`` (eg: ``foo is not + None``) + - never compare something with `True` or `False` (for example never + do ``foo == False``, do ``not foo`` instead) + +Negated containment checks: + use ``foo not in bar`` instead of ``not foo in bar`` + +Instance checks: + ``isinstance(a, C)`` instead of ``type(A) is C``, but try to avoid + instance checks in general. Check for features. + + +Naming Conventions +------------------ + +- Class names: ``CamelCase``, with acronyms kept uppercase (``HTTPWriter`` + and not ``HttpWriter``) +- Variable names: ``lowercase_with_underscores`` +- Method and function names: ``lowercase_with_underscores`` +- Constants: ``UPPERCASE_WITH_UNDERSCORES`` +- precompiled regular expressions: ``name_re`` + +Protected members are prefixed with a single underscore. Double +underscores are reserved for mixin classes. + +On classes with keywords, trailing underscores are appended. Clashes with +builtins are allowed and **must not** be resolved by appending an +underline to the variable name. If the function needs to access a +shadowed builtin, rebind the builtin to a different name instead. + +Function and method arguments: + - class methods: ``cls`` as first parameter + - instance methods: ``self`` as first parameter + - lambdas for properties might have the first parameter replaced + with ``x`` like in ``display_name = property(lambda x: x.real_name + or x.username)`` + + +Docstrings +---------- + +Docstring conventions: + All docstrings are formatted with reStructuredText as understood by + Sphinx. Depending on the number of lines in the docstring, they are + laid out differently. If it's just one line, the closing triple + quote is on the same line as the opening, otherwise the text is on + the same line as the opening quote and the triple quote that closes + the string on its own line:: + + def foo(): + """This is a simple docstring""" + + + def bar(): + """This is a longer docstring with so much information in there + that it spans three lines. In this case the closing triple quote + is on its own line. + """ + +Module header: + The module header consists of an utf-8 encoding declaration (if non + ASCII letters are used, but it is recommended all the time) and a + standard docstring:: + + # -*- coding: utf-8 -*- + """ + package.module + ~~~~~~~~~~~~~~ + + A brief description goes here. + + :copyright: (c) YEAR by AUTHOR. + :license: LICENSE_NAME, see LICENSE_FILE for more details. + """ + + Please keep in mind that proper copyrights and license files are a + requirement for approved Flask extensions. + + +Comments +-------- + +Rules for comments are similar to docstrings. Both are formatted with +reStructuredText. If a comment is used to document an attribute, put a +colon after the opening pound sign (``#``):: + + class User(object): + #: the name of the user as unicode string + name = Column(String) + #: the sha1 hash of the password + inline salt + pw_hash = Column(String) diff --git a/studio/static/doc/flask-docs/_sources/templating.txt b/studio/static/doc/flask-docs/_sources/templating.txt new file mode 100644 index 0000000..bd940b0 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/templating.txt @@ -0,0 +1,188 @@ +Templates +========= + +Flask leverages Jinja2 as template engine. You are obviously free to use +a different template engine, but you still have to install Jinja2 to run +Flask itself. This requirement is necessary to enable rich extensions. +An extension can depend on Jinja2 being present. + +This section only gives a very quick introduction into how Jinja2 +is integrated into Flask. If you want information on the template +engine's syntax itself, head over to the official `Jinja2 Template +Documentation `_ for +more information. + +Jinja Setup +----------- + +Unless customized, Jinja2 is configured by Flask as follows: + +- autoescaping is enabled for all templates ending in ``.html``, + ``.htm``, ``.xml`` as well as ``.xhtml`` +- a template has the ability to opt in/out autoescaping with the + ``{% autoescape %}`` tag. +- Flask inserts a couple of global functions and helpers into the + Jinja2 context, additionally to the values that are present by + default. + +Standard Context +---------------- + +The following global variables are available within Jinja2 templates +by default: + +.. data:: config + :noindex: + + The current configuration object (:data:`flask.config`) + + .. versionadded:: 0.6 + +.. data:: request + :noindex: + + The current request object (:class:`flask.request`) + +.. data:: session + :noindex: + + The current session object (:class:`flask.session`) + +.. data:: g + :noindex: + + The request-bound object for global variables (:data:`flask.g`) + +.. function:: url_for + :noindex: + + The :func:`flask.url_for` function. + +.. function:: get_flashed_messages + :noindex: + + The :func:`flask.get_flashed_messages` function. + +.. admonition:: The Jinja Context Behaviour + + These variables are added to the context of variables, they are not + global variables. The difference is that by default these will not + show up in the context of imported templates. This is partially caused + by performance considerations, partially to keep things explicit. + + What does this mean for you? If you have a macro you want to import, + that needs to access the request object you have two possibilities: + + 1. you explicitly pass the request to the macro as parameter, or + the attribute of the request object you are interested in. + 2. you import the macro "with context". + + Importing with context looks like this: + + .. sourcecode:: jinja + + {% from '_helpers.html' import my_macro with context %} + +Standard Filters +---------------- + +These filters are available in Jinja2 additionally to the filters provided +by Jinja2 itself: + +.. function:: tojson + :noindex: + + This function converts the given object into JSON representation. This + is for example very helpful if you try to generate JavaScript on the + fly. + + Note that inside `script` tags no escaping must take place, so make + sure to disable escaping with ``|safe`` if you intend to use it inside + `script` tags: + + .. sourcecode:: html+jinja + + + + That the ``|tojson`` filter escapes forward slashes properly for you. + +Controlling Autoescaping +------------------------ + +Autoescaping is the concept of automatically escaping special characters +of you. Special characters in the sense of HTML (or XML, and thus XHTML) +are ``&``, ``>``, ``<``, ``"`` as well as ``'``. Because these characters +carry specific meanings in documents on their own you have to replace them +by so called "entities" if you want to use them for text. Not doing so +would not only cause user frustration by the inability to use these +characters in text, but can also lead to security problems. (see +:ref:`xss`) + +Sometimes however you will need to disable autoescaping in templates. +This can be the case if you want to explicitly inject HTML into pages, for +example if they come from a system that generate secure HTML like a +markdown to HTML converter. + +There are three ways to accomplish that: + +- In the Python code, wrap the HTML string in a :class:`~flask.Markup` + object before passing it to the template. This is in general the + recommended way. +- Inside the template, use the ``|safe`` filter to explicitly mark a + string as safe HTML (``{{ myvariable|safe }}``) +- Temporarily disable the autoescape system altogether. + +To disable the autoescape system in templates, you can use the ``{% +autoescape %}`` block: + +.. sourcecode:: html+jinja + + {% autoescape false %} +

autoescaping is disabled here +

{{ will_not_be_escaped }} + {% endautoescape %} + +Whenever you do this, please be very cautious about the variables you are +using in this block. + +Registering Filters +------------------- + +If you want to register your own filters in Jinja2 you have two ways to do +that. You can either put them by hand into the +:attr:`~flask.Flask.jinja_env` of the application or use the +:meth:`~flask.Flask.template_filter` decorator. + +The two following examples work the same and both reverse an object:: + + @app.template_filter('reverse') + def reverse_filter(s): + return s[::-1] + + def reverse_filter(s): + return s[::-1] + app.jinja_env.filters['reverse'] = reverse_filter + +In case of the decorator the argument is optional if you want to use the +function name as name of the filter. + +Context Processors +------------------ + +To inject new variables automatically into the context of a template +context processors exist in Flask. Context processors run before the +template is rendered and have the ability to inject new values into the +template context. A context processor is a function that returns a +dictionary. The keys and values of this dictionary are then merged with +the template context:: + + @app.context_processor + def inject_user(): + return dict(user=g.user) + +The context processor above makes a variable called `user` available in +the template with the value of `g.user`. This example is not very +interesting because `g` is available in templates anyways, but it gives an +idea how this works. diff --git a/studio/static/doc/flask-docs/_sources/testing.txt b/studio/static/doc/flask-docs/_sources/testing.txt new file mode 100644 index 0000000..1e00fe8 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/testing.txt @@ -0,0 +1,307 @@ +.. _testing: + +Testing Flask Applications +========================== + + **Something that is untested is broken.** + +The origin of this quote is unknown and while it is not entirely correct, it is also +not far from the truth. Untested applications make it hard to +improve existing code and developers of untested applications tend to +become pretty paranoid. If an application has automated tests, you can +safely make changes and instantly know if anything breaks. + +Flask provides a way to test your application by exposing the Werkzeug +test :class:`~werkzeug.test.Client` and handling the context locals for you. +You can then use that with your favourite testing solution. In this documentation +we will use the :mod:`unittest` package that comes pre-installed with Python. + +The Application +--------------- + +First, we need an application to test; we will use the application from +the :ref:`tutorial`. If you don't have that application yet, get the +sources from `the examples`_. + +.. _the examples: + http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/ + +The Testing Skeleton +-------------------- + +In order to test the application, we add a second module +(`flaskr_tests.py`) and create a unittest skeleton there:: + + import os + import flaskr + import unittest + import tempfile + + class FlaskrTestCase(unittest.TestCase): + + def setUp(self): + self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() + flaskr.app.config['TESTING'] = True + self.app = flaskr.app.test_client() + flaskr.init_db() + + def tearDown(self): + os.close(self.db_fd) + os.unlink(flaskr.app.config['DATABASE']) + + if __name__ == '__main__': + unittest.main() + +The code in the :meth:`~unittest.TestCase.setUp` method creates a new test +client and initializes a new database. This function is called before +each individual test function is run. To delete the database after the +test, we close the file and remove it from the filesystem in the +:meth:`~unittest.TestCase.tearDown` method. Additionally during setup the +``TESTING`` config flag is activated. What it does is disabling the error +catching during request handling so that you get better error reports when +performing test requests against the application. + +This test client will give us a simple interface to the application. We can +trigger test requests to the application, and the client will also keep track +of cookies for us. + +Because SQLite3 is filesystem-based we can easily use the tempfile module +to create a temporary database and initialize it. The +:func:`~tempfile.mkstemp` function does two things for us: it returns a +low-level file handle and a random file name, the latter we use as +database name. We just have to keep the `db_fd` around so that we can use +the :func:`os.close` function to close the file. + +If we now run the test suite, we should see the following output:: + + $ python flaskr_tests.py + + ---------------------------------------------------------------------- + Ran 0 tests in 0.000s + + OK + +Even though it did not run any actual tests, we already know that our flaskr +application is syntactically valid, otherwise the import would have died +with an exception. + +The First Test +-------------- + +Now it's time to start testing the functionality of the application. +Let's check that the application shows "No entries here so far" if we +access the root of the application (``/``). To do this, we add a new +test method to our class, like this:: + + class FlaskrTestCase(unittest.TestCase): + + def setUp(self): + self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() + self.app = flaskr.app.test_client() + flaskr.init_db() + + def tearDown(self): + os.close(self.db_fd) + os.unlink(flaskr.DATABASE) + + def test_empty_db(self): + rv = self.app.get('/') + assert 'No entries here so far' in rv.data + +Notice that our test functions begin with the word `test`; this allows +:mod:`unittest` to automatically identify the method as a test to run. + +By using `self.app.get` we can send an HTTP `GET` request to the application with +the given path. The return value will be a :class:`~flask.Flask.response_class` object. +We can now use the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute to inspect +the return value (as string) from the application. In this case, we ensure that +``'No entries here so far'`` is part of the output. + +Run it again and you should see one passing test:: + + $ python flaskr_tests.py + . + ---------------------------------------------------------------------- + Ran 1 test in 0.034s + + OK + +Logging In and Out +------------------ + +The majority of the functionality of our application is only available for +the administrative user, so we need a way to log our test client in and out +of the application. To do this, we fire some requests to the login and logout +pages with the required form data (username and password). And because the +login and logout pages redirect, we tell the client to `follow_redirects`. + +Add the following two methods to your `FlaskrTestCase` class:: + + def login(self, username, password): + return self.app.post('/login', data=dict( + username=username, + password=password + ), follow_redirects=True) + + def logout(self): + return self.app.get('/logout', follow_redirects=True) + +Now we can easily test that logging in and out works and that it fails with +invalid credentials. Add this new test to the class:: + + def test_login_logout(self): + rv = self.login('admin', 'default') + assert 'You were logged in' in rv.data + rv = self.logout() + assert 'You were logged out' in rv.data + rv = self.login('adminx', 'default') + assert 'Invalid username' in rv.data + rv = self.login('admin', 'defaultx') + assert 'Invalid password' in rv.data + +Test Adding Messages +-------------------- + +We should also test that adding messages works. Add a new test method +like this:: + + def test_messages(self): + self.login('admin', 'default') + rv = self.app.post('/add', data=dict( + title='', + text='HTML allowed here' + ), follow_redirects=True) + assert 'No entries here so far' not in rv.data + assert '<Hello>' in rv.data + assert 'HTML allowed here' in rv.data + +Here we check that HTML is allowed in the text but not in the title, +which is the intended behavior. + +Running that should now give us three passing tests:: + + $ python flaskr_tests.py + ... + ---------------------------------------------------------------------- + Ran 3 tests in 0.332s + + OK + +For more complex tests with headers and status codes, check out the +`MiniTwit Example`_ from the sources which contains a larger test +suite. + + +.. _MiniTwit Example: + http://github.com/mitsuhiko/flask/tree/master/examples/minitwit/ + + +Other Testing Tricks +-------------------- + +Besides using the test client as shown above, there is also the +:meth:`~flask.Flask.test_request_context` method that can be used +in combination with the `with` statement to activate a request context +temporarily. With this you can access the :class:`~flask.request`, +:class:`~flask.g` and :class:`~flask.session` objects like in view +functions. Here is a full example that demonstrates this approach:: + + app = flask.Flask(__name__) + + with app.test_request_context('/?name=Peter'): + assert flask.request.path == '/' + assert flask.request.args['name'] == 'Peter' + +All the other objects that are context bound can be used in the same +way. + +If you want to test your application with different configurations and +there does not seem to be a good way to do that, consider switching to +application factories (see :ref:`app-factories`). + +Note however that if you are using a test request context, the +:meth:`~flask.Flask.before_request` functions are not automatically called +same for :meth:`~flask.Flask.after_request` functions. However +:meth:`~flask.Flask.teardown_request` functions are indeed executed when +the test request context leaves the `with` block. If you do want the +:meth:`~flask.Flask.before_request` functions to be called as well, you +need to call :meth:`~flask.Flask.preprocess_request` yourself:: + + app = flask.Flask(__name__) + + with app.test_request_context('/?name=Peter'): + app.preprocess_request() + ... + +This can be necessary to open database connections or something similar +depending on how your application was designed. + +If you want to call the :meth:`~flask.Flask.after_request` functions you +need to call into :meth:`~flask.Flask.process_response` which however +requires that you pass it a response object:: + + app = flask.Flask(__name__) + + with app.test_request_context('/?name=Peter'): + resp = Response('...') + resp = app.process_response(resp) + ... + +This in general is less useful because at that point you can directly +start using the test client. + + +Keeping the Context Around +-------------------------- + +.. versionadded:: 0.4 + +Sometimes it is helpful to trigger a regular request but still keep the +context around for a little longer so that additional introspection can +happen. With Flask 0.4 this is possible by using the +:meth:`~flask.Flask.test_client` with a `with` block:: + + app = flask.Flask(__name__) + + with app.test_client() as c: + rv = c.get('/?tequila=42') + assert request.args['tequila'] == '42' + +If you were to use just the :meth:`~flask.Flask.test_client` without +the `with` block, the `assert` would fail with an error because `request` +is no longer available (because you are trying to use it outside of the actual request). +However, keep in mind that any :meth:`~flask.Flask.after_request` functions +are already called at this point so your database connection and +everything involved is probably already closed down. + + +Accessing and Modifying Sessions +-------------------------------- + +.. versionadded:: 0.8 + +Sometimes it can be very helpful to access or modify the sessions from the +test client. Generally there are two ways for this. If you just want to +ensure that a session has certain keys set to certain values you can just +keep the context around and access :data:`flask.session`:: + + with app.test_client() as c: + rv = c.get('/') + assert flask.session['foo'] == 42 + +This however does not make it possible to also modify the session or to +access the session before a request was fired. Starting with Flask 0.8 we +provide a so called “session transaction” which simulates the appropriate +calls to open a session in the context of the test client and to modify +it. At the end of the transaction the session is stored. This works +independently of the session backend used:: + + with app.test_client() as c: + with c.session_transaction() as sess: + sess['a_key'] = 'a value' + + # once this is reached the session was stored + +Note that in this case you have to use the ``sess`` object instead of the +:data:`flask.session` proxy. The object however itself will provide the +same interface. diff --git a/studio/static/doc/flask-docs/_sources/tutorial/css.txt b/studio/static/doc/flask-docs/_sources/tutorial/css.txt new file mode 100644 index 0000000..03f62ed --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/tutorial/css.txt @@ -0,0 +1,31 @@ +.. _tutorial-css: + +Step 7: Adding Style +==================== + +Now that everything else works, it's time to add some style to the +application. Just create a stylesheet called `style.css` in the `static` +folder we created before: + +.. sourcecode:: css + + body { font-family: sans-serif; background: #eee; } + a, h1, h2 { color: #377BA8; } + h1, h2 { font-family: 'Georgia', serif; margin: 0; } + h1 { border-bottom: 2px solid #eee; } + h2 { font-size: 1.2em; } + + .page { margin: 2em auto; width: 35em; border: 5px solid #ccc; + padding: 0.8em; background: white; } + .entries { list-style: none; margin: 0; padding: 0; } + .entries li { margin: 0.8em 1.2em; } + .entries li h2 { margin-left: -1em; } + .add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; } + .add-entry dl { font-weight: bold; } + .metanav { text-align: right; font-size: 0.8em; padding: 0.3em; + margin-bottom: 1em; background: #fafafa; } + .flash { background: #CEE5F5; padding: 0.5em; + border: 1px solid #AACBE2; } + .error { background: #F0D6D6; padding: 0.5em; } + +Continue with :ref:`tutorial-testing`. diff --git a/studio/static/doc/flask-docs/_sources/tutorial/dbcon.txt b/studio/static/doc/flask-docs/_sources/tutorial/dbcon.txt new file mode 100644 index 0000000..99391a2 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/tutorial/dbcon.txt @@ -0,0 +1,57 @@ +.. _tutorial-dbcon: + +Step 4: Request Database Connections +------------------------------------ + +Now we know how we can open database connections and use them for scripts, +but how can we elegantly do that for requests? We will need the database +connection in all our functions so it makes sense to initialize them +before each request and shut them down afterwards. + +Flask allows us to do that with the :meth:`~flask.Flask.before_request`, +:meth:`~flask.Flask.after_request` and :meth:`~flask.Flask.teardown_request` +decorators:: + + @app.before_request + def before_request(): + g.db = connect_db() + + @app.teardown_request + def teardown_request(exception): + g.db.close() + +Functions marked with :meth:`~flask.Flask.before_request` are called before +a request and passed no arguments. Functions marked with +:meth:`~flask.Flask.after_request` are called after a request and +passed the response that will be sent to the client. They have to return +that response object or a different one. They are however not guaranteed +to be executed if an exception is raised, this is where functions marked with +:meth:`~flask.Flask.teardown_request` come in. They get called after the +response has been constructed. They are not allowed to modify the request, and +their return values are ignored. If an exception occurred while the request was +being processed, it is passed to each function; otherwise, `None` is passed in. + +We store our current database connection on the special :data:`~flask.g` +object that Flask provides for us. This object stores information for one +request only and is available from within each function. Never store such +things on other objects because this would not work with threaded +environments. That special :data:`~flask.g` object does some magic behind +the scenes to ensure it does the right thing. + +Continue to :ref:`tutorial-views`. + +.. hint:: Where do I put this code? + + If you've been following along in this tutorial, you might be wondering + where to put the code from this step and the next. A logical place is to + group these module-level functions together, and put your new + ``before_request`` and ``teardown_request`` functions below your existing + ``init_db`` function (following the tutorial line-by-line). + + If you need a moment to find your bearings, take a look at how the `example + source`_ is organized. In Flask, you can put all of your application code + into a single Python module. You don't have to, and if your app :ref:`grows + larger `, it's a good idea not to. + +.. _example source: + http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/ diff --git a/studio/static/doc/flask-docs/_sources/tutorial/dbinit.txt b/studio/static/doc/flask-docs/_sources/tutorial/dbinit.txt new file mode 100644 index 0000000..b546a1a --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/tutorial/dbinit.txt @@ -0,0 +1,67 @@ +.. _tutorial-dbinit: + +Step 3: Creating The Database +============================= + +Flaskr is a database powered application as outlined earlier, and more +precisely, an application powered by a relational database system. Such +systems need a schema that tells them how to store that information. So +before starting the server for the first time it's important to create +that schema. + +Such a schema can be created by piping the `schema.sql` file into the +`sqlite3` command as follows:: + + sqlite3 /tmp/flaskr.db < schema.sql + +The downside of this is that it requires the sqlite3 command to be +installed which is not necessarily the case on every system. Also one has +to provide the path to the database there which leaves some place for +errors. It's a good idea to add a function that initializes the database +for you to the application. + +If you want to do that, you first have to import the +:func:`contextlib.closing` function from the contextlib package. If you +want to use Python 2.5 it's also necessary to enable the `with` statement +first (`__future__` imports must be the very first import):: + + from __future__ import with_statement + from contextlib import closing + +Next we can create a function called `init_db` that initializes the +database. For this we can use the `connect_db` function we defined +earlier. Just add that function below the `connect_db` function:: + + def init_db(): + with closing(connect_db()) as db: + with app.open_resource('schema.sql') as f: + db.cursor().executescript(f.read()) + db.commit() + +The :func:`~contextlib.closing` helper function allows us to keep a +connection open for the duration of the `with` block. The +:func:`~flask.Flask.open_resource` method of the application object +supports that functionality out of the box, so it can be used in the +`with` block directly. This function opens a file from the resource +location (your `flaskr` folder) and allows you to read from it. We are +using this here to execute a script on the database connection. + +When we connect to a database we get a connection object (here called +`db`) that can give us a cursor. On that cursor there is a method to +execute a complete script. Finally we only have to commit the changes. +SQLite 3 and other transactional databases will not commit unless you +explicitly tell it to. + +Now it is possible to create a database by starting up a Python shell and +importing and calling that function:: + +>>> from flaskr import init_db +>>> init_db() + +.. admonition:: Troubleshooting + + If you get an exception later that a table cannot be found check that + you did call the `init_db` function and that your table names are + correct (singular vs. plural for example). + +Continue with :ref:`tutorial-dbcon` diff --git a/studio/static/doc/flask-docs/_sources/tutorial/folders.txt b/studio/static/doc/flask-docs/_sources/tutorial/folders.txt new file mode 100644 index 0000000..6108093 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/tutorial/folders.txt @@ -0,0 +1,23 @@ +.. _tutorial-folders: + +Step 0: Creating The Folders +============================ + +Before we get started, let's create the folders needed for this +application:: + + /flaskr + /static + /templates + +The `flaskr` folder is not a python package, but just something where we +drop our files. Directly into this folder we will then put our database +schema as well as main module in the following steps. The files inside +the `static` folder are available to users of the application via `HTTP`. +This is the place where css and javascript files go. Inside the +`templates` folder Flask will look for `Jinja2`_ templates. The +templates you create later in the tutorial will go in this directory. + +Continue with :ref:`tutorial-schema`. + +.. _Jinja2: http://jinja.pocoo.org/2/ diff --git a/studio/static/doc/flask-docs/_sources/tutorial/index.txt b/studio/static/doc/flask-docs/_sources/tutorial/index.txt new file mode 100644 index 0000000..3f2d659 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/tutorial/index.txt @@ -0,0 +1,32 @@ +.. _tutorial: + +Tutorial +======== + +You want to develop an application with Python and Flask? Here you have +the chance to learn that by example. In this tutorial we will create a +simple microblog application. It only supports one user that can create +text-only entries and there are no feeds or comments, but it still +features everything you need to get started. We will use Flask and SQLite +as database which comes out of the box with Python, so there is nothing +else you need. + +If you want the full sourcecode in advance or for comparison, check out +the `example source`_. + +.. _example source: + http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/ + +.. toctree:: + :maxdepth: 2 + + introduction + folders + schema + setup + dbinit + dbcon + views + templates + css + testing diff --git a/studio/static/doc/flask-docs/_sources/tutorial/introduction.txt b/studio/static/doc/flask-docs/_sources/tutorial/introduction.txt new file mode 100644 index 0000000..c72bbd7 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/tutorial/introduction.txt @@ -0,0 +1,33 @@ +.. _tutorial-introduction: + +Introducing Flaskr +================== + +We will call our blogging application flaskr here, feel free to chose a +less web-2.0-ish name ;) Basically we want it to do the following things: + +1. let the user sign in and out with credentials specified in the + configuration. Only one user is supported. +2. when the user is logged in they can add new entries to the page + consisting of a text-only title and some HTML for the text. This HTML + is not sanitized because we trust the user here. +3. the page shows all entries so far in reverse order (newest on top) and + the user can add new ones from there if logged in. + +We will be using SQLite3 directly for that application because it's good +enough for an application of that size. For larger applications however +it makes a lot of sense to use `SQLAlchemy`_ that handles database +connections in a more intelligent way, allows you to target different +relational databases at once and more. You might also want to consider +one of the popular NoSQL databases if your data is more suited for those. + +Here a screenshot from the final application: + +.. image:: ../_static/flaskr.png + :align: center + :class: screenshot + :alt: screenshot of the final application + +Continue with :ref:`tutorial-folders`. + +.. _SQLAlchemy: http://www.sqlalchemy.org/ diff --git a/studio/static/doc/flask-docs/_sources/tutorial/schema.txt b/studio/static/doc/flask-docs/_sources/tutorial/schema.txt new file mode 100644 index 0000000..c078667 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/tutorial/schema.txt @@ -0,0 +1,25 @@ +.. _tutorial-schema: + +Step 1: Database Schema +======================= + +First we want to create the database schema. For this application only a +single table is needed and we only want to support SQLite so that is quite +easy. Just put the following contents into a file named `schema.sql` in +the just created `flaskr` folder: + +.. sourcecode:: sql + + drop table if exists entries; + create table entries ( + id integer primary key autoincrement, + title string not null, + text string not null + ); + +This schema consists of a single table called `entries` and each row in +this table has an `id`, a `title` and a `text`. The `id` is an +automatically incrementing integer and a primary key, the other two are +strings that must not be null. + +Continue with :ref:`tutorial-setup`. diff --git a/studio/static/doc/flask-docs/_sources/tutorial/setup.txt b/studio/static/doc/flask-docs/_sources/tutorial/setup.txt new file mode 100644 index 0000000..e9e4d67 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/tutorial/setup.txt @@ -0,0 +1,90 @@ +.. _tutorial-setup: + +Step 2: Application Setup Code +============================== + +Now that we have the schema in place we can create the application module. +Let's call it `flaskr.py` inside the `flaskr` folder. For starters we +will add the imports we will need as well as the config section. For +small applications it's a possibility to drop the configuration directly +into the module which we will be doing here. However a cleaner solution +would be to create a separate `.ini` or `.py` file and load that or import +the values from there. + +:: + + # all the imports + import sqlite3 + from flask import Flask, request, session, g, redirect, url_for, \ + abort, render_template, flash + + # configuration + DATABASE = '/tmp/flaskr.db' + DEBUG = True + SECRET_KEY = 'development key' + USERNAME = 'admin' + PASSWORD = 'default' + +Next we can create our actual application and initialize it with the +config from the same file:: + + # create our little application :) + app = Flask(__name__) + app.config.from_object(__name__) + +:meth:`~flask.Config.from_object` will look at the given object (if it's a +string it will import it) and then look for all uppercase variables +defined there. In our case, the configuration we just wrote a few lines +of code above. You can also move that into a separate file. + +It is also a good idea to be able to load a configuration from a +configurable file. This is what :meth:`~flask.Config.from_envvar` can +do:: + + app.config.from_envvar('FLASKR_SETTINGS', silent=True) + +That way someone can set an environment variable called +:envvar:`FLASKR_SETTINGS` to specify a config file to be loaded which will +then override the default values. The silent switch just tells Flask to +not complain if no such environment key is set. + +The `secret_key` is needed to keep the client-side sessions secure. +Choose that key wisely and as hard to guess and complex as possible. The +debug flag enables or disables the interactive debugger. Never leave +debug mode activated in a production system because it will allow users to +execute code on the server! + +We also add a method to easily connect to the database specified. That +can be used to open a connection on request and also from the interactive +Python shell or a script. This will come in handy later. + +:: + + def connect_db(): + return sqlite3.connect(app.config['DATABASE']) + +Finally we just add a line to the bottom of the file that fires up the +server if we want to run that file as a standalone application:: + + if __name__ == '__main__': + app.run() + +With that out of the way you should be able to start up the application +without problems. Do this with the following command:: + + python flaskr.py + +You will see a message telling you that server has started along with +the address at which you can access it. + +When you head over to the server in your browser you will get an 404 +page not found error because we don't have any views yet. But we will +focus on that a little later. First we should get the database working. + +.. admonition:: Externally Visible Server + + Want your server to be publicly available? Check out the + :ref:`externally visible server ` section for more + information. + +Continue with :ref:`tutorial-dbinit`. diff --git a/studio/static/doc/flask-docs/_sources/tutorial/templates.txt b/studio/static/doc/flask-docs/_sources/tutorial/templates.txt new file mode 100644 index 0000000..5ec5584 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/tutorial/templates.txt @@ -0,0 +1,111 @@ +.. _tutorial-templates: + +Step 6: The Templates +===================== + +Now we should start working on the templates. If we request the URLs now +we would only get an exception that Flask cannot find the templates. The +templates are using `Jinja2`_ syntax and have autoescaping enabled by +default. This means that unless you mark a value in the code with +:class:`~flask.Markup` or with the ``|safe`` filter in the template, +Jinja2 will ensure that special characters such as ``<`` or ``>`` are +escaped with their XML equivalents. + +We are also using template inheritance which makes it possible to reuse +the layout of the website in all pages. + +Put the following templates into the `templates` folder: + +.. _Jinja2: http://jinja.pocoo.org/2/documentation/templates + +layout.html +----------- + +This template contains the HTML skeleton, the header and a link to log in +(or log out if the user was already logged in). It also displays the +flashed messages if there are any. The ``{% block body %}`` block can be +replaced by a block of the same name (``body``) in a child template. + +The :class:`~flask.session` dict is available in the template as well and +you can use that to check if the user is logged in or not. Note that in +Jinja you can access missing attributes and items of objects / dicts which +makes the following code work, even if there is no ``'logged_in'`` key in +the session: + +.. sourcecode:: html+jinja + + + Flaskr + +

+

Flaskr

+
+ {% if not session.logged_in %} + log in + {% else %} + log out + {% endif %} +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block body %}{% endblock %} +
+ +show_entries.html +----------------- + +This template extends the `layout.html` template from above to display the +messages. Note that the `for` loop iterates over the messages we passed +in with the :func:`~flask.render_template` function. We also tell the +form to submit to your `add_entry` function and use `POST` as `HTTP` +method: + +.. sourcecode:: html+jinja + + {% extends "layout.html" %} + {% block body %} + {% if session.logged_in %} +
+
+
Title: +
+
Text: +
+
+
+
+ {% endif %} +
    + {% for entry in entries %} +
  • {{ entry.title }}

    {{ entry.text|safe }} + {% else %} +
  • Unbelievable. No entries here so far + {% endfor %} +
+ {% endblock %} + +login.html +---------- + +Finally the login template which basically just displays a form to allow +the user to login: + +.. sourcecode:: html+jinja + + {% extends "layout.html" %} + {% block body %} +

Login

+ {% if error %}

Error: {{ error }}{% endif %} +

+
+
Username: +
+
Password: +
+
+
+
+ {% endblock %} + +Continue with :ref:`tutorial-css`. diff --git a/studio/static/doc/flask-docs/_sources/tutorial/testing.txt b/studio/static/doc/flask-docs/_sources/tutorial/testing.txt new file mode 100644 index 0000000..34edd79 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/tutorial/testing.txt @@ -0,0 +1,10 @@ +.. _tutorial-testing: + +Bonus: Testing the Application +============================== + +Now that you have finished the application and everything works as +expected, it's probably not a bad idea to add automated tests to simplify +modifications in the future. The application above is used as a basic +example of how to perform unittesting in the :ref:`testing` section of the +documentation. Go there to see how easy it is to test Flask applications. diff --git a/studio/static/doc/flask-docs/_sources/tutorial/views.txt b/studio/static/doc/flask-docs/_sources/tutorial/views.txt new file mode 100644 index 0000000..93bec3b --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/tutorial/views.txt @@ -0,0 +1,98 @@ +.. _tutorial-views: + +Step 5: The View Functions +========================== + +Now that the database connections are working we can start writing the +view functions. We will need four of them: + +Show Entries +------------ + +This view shows all the entries stored in the database. It listens on the +root of the application and will select title and text from the database. +The one with the highest id (the newest entry) will be on top. The rows +returned from the cursor are tuples with the columns ordered like specified +in the select statement. This is good enough for small applications like +here, but you might want to convert them into a dict. If you are +interested in how to do that, check out the :ref:`easy-querying` example. + +The view function will pass the entries as dicts to the +`show_entries.html` template and return the rendered one:: + + @app.route('/') + def show_entries(): + cur = g.db.execute('select title, text from entries order by id desc') + entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()] + return render_template('show_entries.html', entries=entries) + +Add New Entry +------------- + +This view lets the user add new entries if they are logged in. This only +responds to `POST` requests, the actual form is shown on the +`show_entries` page. If everything worked out well we will +:func:`~flask.flash` an information message to the next request and +redirect back to the `show_entries` page:: + + @app.route('/add', methods=['POST']) + def add_entry(): + if not session.get('logged_in'): + abort(401) + g.db.execute('insert into entries (title, text) values (?, ?)', + [request.form['title'], request.form['text']]) + g.db.commit() + flash('New entry was successfully posted') + return redirect(url_for('show_entries')) + +Note that we check that the user is logged in here (the `logged_in` key is +present in the session and `True`). + +.. admonition:: Security Note + + Be sure to use question marks when building SQL statements, as done in the + example above. Otherwise, your app will be vulnerable to SQL injection when + you use string formatting to build SQL statements. + See :ref:`sqlite3` for more. + +Login and Logout +---------------- + +These functions are used to sign the user in and out. Login checks the +username and password against the ones from the configuration and sets the +`logged_in` key in the session. If the user logged in successfully, that +key is set to `True`, and the user is redirected back to the `show_entries` +page. In addition, a message is flashed that informs the user that he or +she was logged in successfully. If an error occurred, the template is +notified about that, and the user is asked again:: + + @app.route('/login', methods=['GET', 'POST']) + def login(): + error = None + if request.method == 'POST': + if request.form['username'] != app.config['USERNAME']: + error = 'Invalid username' + elif request.form['password'] != app.config['PASSWORD']: + error = 'Invalid password' + else: + session['logged_in'] = True + flash('You were logged in') + return redirect(url_for('show_entries')) + return render_template('login.html', error=error) + +The logout function, on the other hand, removes that key from the session +again. We use a neat trick here: if you use the :meth:`~dict.pop` method +of the dict and pass a second parameter to it (the default), the method +will delete the key from the dictionary if present or do nothing when that +key is not in there. This is helpful because now we don't have to check +if the user was logged in. + +:: + + @app.route('/logout') + def logout(): + session.pop('logged_in', None) + flash('You were logged out') + return redirect(url_for('show_entries')) + +Continue with :ref:`tutorial-templates`. diff --git a/studio/static/doc/flask-docs/_sources/unicode.txt b/studio/static/doc/flask-docs/_sources/unicode.txt new file mode 100644 index 0000000..413ea84 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/unicode.txt @@ -0,0 +1,107 @@ +Unicode in Flask +================ + +Flask like Jinja2 and Werkzeug is totally Unicode based when it comes to +text. Not only these libraries, also the majority of web related Python +libraries that deal with text. If you don't know Unicode so far, you +should probably read `The Absolute Minimum Every Software Developer +Absolutely, Positively Must Know About Unicode and Character Sets +`_. This part of the +documentation just tries to cover the very basics so that you have a +pleasant experience with Unicode related things. + +Automatic Conversion +-------------------- + +Flask has a few assumptions about your application (which you can change +of course) that give you basic and painless Unicode support: + +- the encoding for text on your website is UTF-8 +- internally you will always use Unicode exclusively for text except + for literal strings with only ASCII character points. +- encoding and decoding happens whenever you are talking over a protocol + that requires bytes to be transmitted. + +So what does this mean to you? + +HTTP is based on bytes. Not only the protocol, also the system used to +address documents on servers (so called URIs or URLs). However HTML which +is usually transmitted on top of HTTP supports a large variety of +character sets and which ones are used, are transmitted in an HTTP header. +To not make this too complex Flask just assumes that if you are sending +Unicode out you want it to be UTF-8 encoded. Flask will do the encoding +and setting of the appropriate headers for you. + +The same is true if you are talking to databases with the help of +SQLAlchemy or a similar ORM system. Some databases have a protocol that +already transmits Unicode and if they do not, SQLAlchemy or your other ORM +should take care of that. + +The Golden Rule +--------------- + +So the rule of thumb: if you are not dealing with binary data, work with +Unicode. What does working with Unicode in Python 2.x mean? + +- as long as you are using ASCII charpoints only (basically numbers, + some special characters of latin letters without umlauts or anything + fancy) you can use regular string literals (``'Hello World'``). +- if you need anything else than ASCII in a string you have to mark + this string as Unicode string by prefixing it with a lowercase `u`. + (like ``u'Hänsel und Gretel'``) +- if you are using non-Unicode characters in your Python files you have + to tell Python which encoding your file uses. Again, I recommend + UTF-8 for this purpose. To tell the interpreter your encoding you can + put the ``# -*- coding: utf-8 -*-`` into the first or second line of + your Python source file. +- Jinja is configured to decode the template files from UTF-8. So make + sure to tell your editor to save the file as UTF-8 there as well. + +Encoding and Decoding Yourself +------------------------------ + +If you are talking with a filesystem or something that is not really based +on Unicode you will have to ensure that you decode properly when working +with Unicode interface. So for example if you want to load a file on the +filesystem and embed it into a Jinja2 template you will have to decode it +from the encoding of that file. Here the old problem that text files do +not specify their encoding comes into play. So do yourself a favour and +limit yourself to UTF-8 for text files as well. + +Anyways. To load such a file with Unicode you can use the built-in +:meth:`str.decode` method:: + + def read_file(filename, charset='utf-8'): + with open(filename, 'r') as f: + return f.read().decode(charset) + +To go from Unicode into a specific charset such as UTF-8 you can use the +:meth:`unicode.encode` method:: + + def write_file(filename, contents, charset='utf-8'): + with open(filename, 'w') as f: + f.write(contents.encode(charset)) + +Configuring Editors +------------------- + +Most editors save as UTF-8 by default nowadays but in case your editor is +not configured to do this you have to change it. Here some common ways to +set your editor to store as UTF-8: + +- Vim: put ``set enc=utf-8`` to your ``.vimrc`` file. + +- Emacs: either use an encoding cookie or put this into your ``.emacs`` + file:: + + (prefer-coding-system 'utf-8) + (setq default-buffer-file-coding-system 'utf-8) + +- Notepad++: + + 1. Go to *Settings -> Preferences ...* + 2. Select the "New Document/Default Directory" tab + 3. Select "UTF-8 without BOM" as encoding + + It is also recommended to use the Unix newline format, you can select + it in the same panel but this is not a requirement. diff --git a/studio/static/doc/flask-docs/_sources/upgrading.txt b/studio/static/doc/flask-docs/_sources/upgrading.txt new file mode 100644 index 0000000..0ba46c1 --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/upgrading.txt @@ -0,0 +1,324 @@ +Upgrading to Newer Releases +=========================== + +Flask itself is changing like any software is changing over time. Most of +the changes are the nice kind, the kind where you don't have to change +anything in your code to profit from a new release. + +However every once in a while there are changes that do require some +changes in your code or there are changes that make it possible for you to +improve your own code quality by taking advantage of new features in +Flask. + +This section of the documentation enumerates all the changes in Flask from +release to release and how you can change your code to have a painless +updating experience. + +If you want to use the `easy_install` command to upgrade your Flask +installation, make sure to pass it the ``-U`` parameter:: + + $ easy_install -U Flask + +Version 0.8 +----------- + +Flask introduced a new session interface system. We also noticed that +there was a naming collision between `flask.session` the module that +implements sessions and :data:`flask.session` which is the global session +object. With that introduction we moved the implementation details for +the session system into a new module called :mod:`flask.sessions`. If you +used the previously undocumented session support we urge you to upgrade. + +If invalid JSON data was submitted Flask will now raise a +:exc:`~werkzeug.exceptions.BadRequest` exception instead of letting the +default :exc:`ValueError` bubble up. This has the advantage that you no +longer have to handle that error to avoid an internal server error showing +up for the user. If you were catching this down explicitly in the past +as `ValueError` you will need to change this. + +Due to a bug in the test client Flask 0.7 did not trigger teardown +handlers when the test client was used in a with statement. This was +since fixed but might require some changes in your testsuites if you +relied on this behavior. + +Version 0.7 +----------- + +In Flask 0.7 we cleaned up the code base internally a lot and did some +backwards incompatible changes that make it easier to implement larger +applications with Flask. Because we want to make upgrading as easy as +possible we tried to counter the problems arising from these changes by +providing a script that can ease the transition. + +The script scans your whole application and generates an unified diff with +changes it assumes are safe to apply. However as this is an automated +tool it won't be able to find all use cases and it might miss some. We +internally spread a lot of deprecation warnings all over the place to make +it easy to find pieces of code that it was unable to upgrade. + +We strongly recommend that you hand review the generated patchfile and +only apply the chunks that look good. + +If you are using git as version control system for your project we +recommend applying the patch with ``path -p1 < patchfile.diff`` and then +using the interactive commit feature to only apply the chunks that look +good. + +To apply the upgrade script do the following: + +1. Download the script: `flask-07-upgrade.py + `_ +2. Run it in the directory of your application:: + + python flask-07-upgrade.py > patchfile.diff + +3. Review the generated patchfile. +4. Apply the patch:: + + patch -p1 < patchfile.diff + +5. If you were using per-module template folders you need to move some + templates around. Previously if you had a folder named ``templates`` + next to a blueprint named ``admin`` the implicit template path + automatically was ``admin/index.html`` for a template file called + ``templates/index.html``. This no longer is the case. Now you need + to name the template ``templates/admin/index.html``. The tool will + not detect this so you will have to do that on your own. + +Please note that deprecation warnings are disabled by default starting +with Python 2.7. In order to see the deprecation warnings that might be +emitted you have to enabled them with the :mod:`warnings` module. + +If you are working with windows and you lack the `patch` command line +utility you can get it as part of various Unix runtime environments for +windows including cygwin, msysgit or ming32. Also source control systems +like svn, hg or git have builtin support for applying unified diffs as +generated by the tool. Check the manual of your version control system +for more information. + +Bug in Request Locals +````````````````````` + +Due to a bug in earlier implementations the request local proxies now +raise a :exc:`RuntimeError` instead of an :exc:`AttributeError` when they +are unbound. If you caught these exceptions with :exc:`AttributeError` +before, you should catch them with :exc:`RuntimeError` now. + +Additionally the :func:`~flask.send_file` function is now issuing +deprecation warnings if you depend on functionality that will be removed +in Flask 1.0. Previously it was possible to use etags and mimetypes +when file objects were passed. This was unreliable and caused issues +for a few setups. If you get a deprecation warning, make sure to +update your application to work with either filenames there or disable +etag attaching and attach them yourself. + +Old code:: + + return send_file(my_file_object) + return send_file(my_file_object) + +New code:: + + return send_file(my_file_object, add_etags=False) + +.. _upgrading-to-new-teardown-handling: + +Upgrading to new Teardown Handling +`````````````````````````````````` + +We streamlined the behavior of the callbacks for request handling. For +things that modify the response the :meth:`~flask.Flask.after_request` +decorators continue to work as expected, but for things that absolutely +must happen at the end of request we introduced the new +:meth:`~flask.Flask.teardown_request` decorator. Unfortunately that +change also made after-request work differently under error conditions. +It's not consistently skipped if exceptions happen whereas previously it +might have been called twice to ensure it is executed at the end of the +request. + +If you have database connection code that looks like this:: + + @app.after_request + def after_request(response): + g.db.close() + return response + +You are now encouraged to use this instead:: + + @app.teardown_request + def after_request(exception): + if hasattr(g, 'db'): + g.db.close() + +On the upside this change greatly improves the internal code flow and +makes it easier to customize the dispatching and error handling. This +makes it now a lot easier to write unit tests as you can prevent closing +down of database connections for a while. You can take advantage of the +fact that the teardown callbacks are called when the response context is +removed from the stack so a test can query the database after request +handling:: + + with app.test_client() as client: + resp = client.get('/') + # g.db is still bound if there is such a thing + + # and here it's gone + +Manual Error Handler Attaching +`````````````````````````````` + +While it is still possible to attach error handlers to +:attr:`Flask.error_handlers` it's discouraged to do so and in fact +deprecated. In generaly we no longer recommend custom error handler +attaching via assignments to the underlying dictionary due to the more +complex internal handling to support arbitrary exception classes and +blueprints. See :meth:`Flask.errorhandler` for more information. + +The proper upgrade is to change this:: + + app.error_handlers[403] = handle_error + +Into this:: + + app.register_error_handler(403, handle_error) + +Alternatively you should just attach the function with a decorator:: + + @app.errorhandler(403) + def handle_error(e): + ... + +(Note that :meth:`register_error_handler` is new in Flask 0.7) + +Blueprint Support +````````````````` + +Blueprints replace the previous concept of “Modules” in Flask. They +provide better semantics for various features and work better with large +applications. The update script provided should be able to upgrade your +applications automatically, but there might be some cases where it fails +to upgrade. What changed? + +- Blueprints need explicit names. Modules had an automatic name + guesssing scheme where the shortname for the module was taken from the + last part of the import module. The upgrade script tries to guess + that name but it might fail as this information could change at + runtime. +- Blueprints have an inverse behavior for :meth:`url_for`. Previously + ``.foo`` told :meth:`url_for` that it should look for the endpoint + `foo` on the application. Now it means “relative to current module”. + The script will inverse all calls to :meth:`url_for` automatically for + you. It will do this in a very eager way so you might end up with + some unnecessary leading dots in your code if you're not using + modules. +- Blueprints do not automatically provide static folders. They will + also no longer automatically export templates from a folder called + `templates` next to their location however but it can be enabled from + the constructor. Same with static files: if you want to continue + serving static files you need to tell the constructor explicitly the + path to the static folder (which can be relative to the blueprint's + module path). +- Rendering templates was simplified. Now the blueprints can provide + template folders which are added to a general template searchpath. + This means that you need to add another subfolder with the blueprint's + name into that folder if you want ``blueprintname/template.html`` as + the template name. + +If you continue to use the `Module` object which is deprecated, Flask will +restore the previous behavior as good as possible. However we strongly +recommend upgrading to the new blueprints as they provide a lot of useful +improvement such as the ability to attach a blueprint multiple times, +blueprint specific error handlers and a lot more. + + +Version 0.6 +----------- + +Flask 0.6 comes with a backwards incompatible change which affects the +order of after-request handlers. Previously they were called in the order +of the registration, now they are called in reverse order. This change +was made so that Flask behaves more like people expected it to work and +how other systems handle request pre- and postprocessing. If you +depend on the order of execution of post-request functions, be sure to +change the order. + +Another change that breaks backwards compatibility is that context +processors will no longer override values passed directly to the template +rendering function. If for example `request` is as variable passed +directly to the template, the default context processor will not override +it with the current request object. This makes it easier to extend +context processors later to inject additional variables without breaking +existing template not expecting them. + +Version 0.5 +----------- + +Flask 0.5 is the first release that comes as a Python package instead of a +single module. There were a couple of internal refactoring so if you +depend on undocumented internal details you probably have to adapt the +imports. + +The following changes may be relevant to your application: + +- autoescaping no longer happens for all templates. Instead it is + configured to only happen on files ending with ``.html``, ``.htm``, + ``.xml`` and ``.xhtml``. If you have templates with different + extensions you should override the + :meth:`~flask.Flask.select_jinja_autoescape` method. +- Flask no longer supports zipped applications in this release. This + functionality might come back in future releases if there is demand + for this feature. Removing support for this makes the Flask internal + code easier to understand and fixes a couple of small issues that make + debugging harder than necessary. +- The `create_jinja_loader` function is gone. If you want to customize + the Jinja loader now, use the + :meth:`~flask.Flask.create_jinja_environment` method instead. + +Version 0.4 +----------- + +For application developers there are no changes that require changes in +your code. In case you are developing on a Flask extension however, and +that extension has a unittest-mode you might want to link the activation +of that mode to the new ``TESTING`` flag. + +Version 0.3 +----------- + +Flask 0.3 introduces configuration support and logging as well as +categories for flashing messages. All these are features that are 100% +backwards compatible but you might want to take advantage of them. + +Configuration Support +````````````````````` + +The configuration support makes it easier to write any kind of application +that requires some sort of configuration. (Which most likely is the case +for any application out there). + +If you previously had code like this:: + + app.debug = DEBUG + app.secret_key = SECRET_KEY + +You no longer have to do that, instead you can just load a configuration +into the config object. How this works is outlined in :ref:`config`. + +Logging Integration +``````````````````` + +Flask now configures a logger for you with some basic and useful defaults. +If you run your application in production and want to profit from +automatic error logging, you might be interested in attaching a proper log +handler. Also you can start logging warnings and errors into the logger +when appropriately. For more information on that, read +:ref:`application-errors`. + +Categories for Flash Messages +````````````````````````````` + +Flash messages can now have categories attached. This makes it possible +to render errors, warnings or regular messages differently for example. +This is an opt-in feature because it requires some rethinking in the code. + +Read all about that in the :ref:`message-flashing-pattern` pattern. diff --git a/studio/static/doc/flask-docs/_sources/views.txt b/studio/static/doc/flask-docs/_sources/views.txt new file mode 100644 index 0000000..441620a --- /dev/null +++ b/studio/static/doc/flask-docs/_sources/views.txt @@ -0,0 +1,227 @@ +.. _views: + +Pluggable Views +=============== + +.. versionadded:: 0.7 + +Flask 0.7 introduces pluggable views inspired by the generic views from +Django which are based on classes instead of functions. The main +intention is that you can replace parts of the implementations and this +way have customizable pluggable views. + +Basic Principle +--------------- + +Consider you have a function that loads a list of objects from the +database and renders into a template:: + + @app.route('/users/') + def show_users(page): + users = User.query.all() + return render_template('users.html', users=users) + +This is simple and flexible, but if you want to provide this view in a +generic fashion that can be adapted to other models and templates as well +you might want more flexibility. This is where pluggable class based +views come into place. As the first step to convert this into a class +based view you would do this:: + + + from flask.views import View + + class ShowUsers(View): + + def dispatch_request(self): + users = User.query.all() + return render_template('users.html', objects=users) + + app.add_url_rule('/users/', ShowUsers.as_view('show_users')) + +As you can see what you have to do is to create a subclass of +:class:`flask.views.View` and implement +:meth:`~flask.views.View.dispatch_request`. Then we have to convert that +class into an actual view function by using the +:meth:`~flask.views.View.as_view` class method. The string you pass to +that function is the name of the endpoint that view will then have. But +this by itself is not helpful, so let's refactor the code a bit:: + + + from flask.views import View + + class ListView(View): + + def get_template_name(self): + raise NotImplementedError() + + def render_template(self, context): + return render_template(self.get_template_name(), **context) + + def dispatch_request(self): + context = {'objects': self.get_objects()} + return self.render_template(context) + + class UserView(ListView): + + def get_template_name(self): + return 'users.html' + + def get_objects(self): + return User.query.all() + +This of course is not that helpful for such a small example, but it's good +enough to explain the basic principle. When you have a class based view +the question comes up what `self` points to. The way this works is that +whenever the request is dispatched a new instance of the class is created +and the :meth:`~flask.views.View.dispatch_request` method is called with +the parameters from the URL rule. The class itself is instanciated with +the parameters passed to the :meth:`~flask.views.View.as_view` function. +For instance you can write a class like this:: + + class RenderTemplateView(View): + def __init__(self, template_name): + self.template_name = template_name + def dispatch_request(self): + return render_template(self.template_name) + +And then you can register it like this:: + + app.add_url_rule('/about', view_func=RenderTemplateView.as_view( + 'about_page', template_name='about.html')) + +Method Hints +------------ + +Pluggable views are attached to the application like a regular function by +either using :func:`~flask.Flask.route` or better +:meth:`~flask.Flask.add_url_rule`. That however also means that you would +have to provide the names of the HTTP methods the view supports when you +attach this. In order to move that information to the class you can +provide a :attr:`~flask.views.View.methods` attribute that has this +information:: + + class MyView(View): + methods = ['GET', 'POST'] + + def dispatch_request(self): + if request.method == 'POST': + ... + ... + + app.add_url_rule('/myview', view_func=MyView.as_view('myview')) + +Method Based Dispatching +------------------------ + +For RESTful APIs it's especially helpful to execute a different function +for each HTTP method. With the :class:`flask.views.MethodView` you can +easily do that. Each HTTP method maps to a function with the same name +(just in lowercase):: + + from flask.views import MethodView + + class UserAPI(MethodView): + + def get(self): + users = User.query.all() + ... + + def post(self): + user = User.from_form_data(request.form) + ... + + app.add_url_rule('/users/', view_func=UserAPI.as_view('users')) + +That way you also don't have to provide the +:attr:`~flask.views.View.methods` attribute. It's automatically set based +on the methods defined in the class. + +Decorating Views +---------------- + +Since the view class itself is not the view function that is added to the +routing system it does not make much sense to decorate the class itself. +Instead you either have to decorate the return value of +:meth:`~flask.views.View.as_view` by hand:: + + view = rate_limited(UserAPI.as_view('users')) + app.add_url_rule('/users/', view_func=view) + +Starting with Flask 0.8 there is also an alternative way where you can +specify a list of decorators to apply in the class declaration:: + + class UserAPI(MethodView): + decorators = [rate_limited] + +Due to the implicit self from the caller's perspective you cannot use +regular view decorators on the individual methods of the view however, +keep this in mind. + +Method Views for APIs +--------------------- + +Web APIs are often working very closely with HTTP verbs so it makes a lot +of sense to implement such an API based on the +:class:`~flask.views.MethodView`. That said, you will notice that the API +will require different URL rules that go to the same method view most of +the time. For instance consider that you are exposing a user object on +the web: + +=============== =============== ====================================== +URL Method Description +--------------- --------------- -------------------------------------- +``/users/`` ``GET`` Gives a list of all users +``/users/`` ``POST`` Creates a new user +``/users/`` ``GET`` Shows a single user +``/users/`` ``PUT`` Updates a single user +``/users/`` ``DELETE`` Deletes a single user +=============== =============== ====================================== + +So how would you go about doing that with the +:class:`~flask.views.MethodView`? The trick is to take advantage of the +fact that you can provide multiple rules to the same view. + +Let's assume for the moment the view would look like this:: + + class UserAPI(MethodView): + + def get(self, user_id): + if user_id is None: + # return a list of users + pass + else: + # expose a single user + pass + + def post(self): + # create a new user + pass + + def delete(self, user_id): + # delete a single user + pass + + def put(self, user_id): + # update a single user + pass + +So how do we hook this up with the routing system? By adding two rules +and explicitly mentioning the methods for each:: + + user_view = UserAPI.as_view('user_api') + app.add_url_rule('/users/', defaults={'user_id': None}, + view_func=user_view, methods=['GET', 'POST']) + app.add_url_rule('/users/', view_func=user_view, + methods=['GET', 'PUT', 'DELETE']) + +If you have a lot of APIs that look similar you can refactor that +registration code:: + + def register_api(view, endpoint, url, pk='id', pk_type='int'): + view_func = view.as_view(endpoint) + app.add_url_rule(url, defaults={pk: None}, + view_func=view_func, methods=['GET', 'POST']) + app.add_url_rule('%s<%s:%s>' % (url, pk), view_func=view_func, + methods=['GET', 'PUT', 'DELETE']) + + register_api(UserAPI, 'user_api', '/users/', pk='user_id') diff --git a/studio/static/doc/flask-docs/_static/ajax-loader.gif b/studio/static/doc/flask-docs/_static/ajax-loader.gif new file mode 100644 index 0000000..61faf8c --- /dev/null +++ b/studio/static/doc/flask-docs/_static/ajax-loader.gif Binary files differ diff --git a/studio/static/doc/flask-docs/_static/basic.css b/studio/static/doc/flask-docs/_static/basic.css new file mode 100644 index 0000000..f0379f3 --- /dev/null +++ b/studio/static/doc/flask-docs/_static/basic.css @@ -0,0 +1,540 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar input[type="text"] { + width: 170px; +} + +div.sphinxsidebar input[type="submit"] { + width: 30px; +} + +img { + border: 0; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.refcount { + color: #060; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/studio/static/doc/flask-docs/_static/comment-bright.png b/studio/static/doc/flask-docs/_static/comment-bright.png new file mode 100644 index 0000000..551517b --- /dev/null +++ b/studio/static/doc/flask-docs/_static/comment-bright.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/comment-close.png b/studio/static/doc/flask-docs/_static/comment-close.png new file mode 100644 index 0000000..09b54be --- /dev/null +++ b/studio/static/doc/flask-docs/_static/comment-close.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/comment.png b/studio/static/doc/flask-docs/_static/comment.png new file mode 100644 index 0000000..92feb52 --- /dev/null +++ b/studio/static/doc/flask-docs/_static/comment.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/debugger.png b/studio/static/doc/flask-docs/_static/debugger.png new file mode 100644 index 0000000..4f47229 --- /dev/null +++ b/studio/static/doc/flask-docs/_static/debugger.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/doctools.js b/studio/static/doc/flask-docs/_static/doctools.js new file mode 100644 index 0000000..8b9bd2c --- /dev/null +++ b/studio/static/doc/flask-docs/_static/doctools.js @@ -0,0 +1,247 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilties for all documentation. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +} + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * small function to check if an array contains + * a given item. + */ +jQuery.contains = function(arr, item) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] == item) + return true; + } + return false; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('.sidebar .this-page-menu')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('.sidebar .this-page-menu li.highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/studio/static/doc/flask-docs/_static/down-pressed.png b/studio/static/doc/flask-docs/_static/down-pressed.png new file mode 100644 index 0000000..6f7ad78 --- /dev/null +++ b/studio/static/doc/flask-docs/_static/down-pressed.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/down.png b/studio/static/doc/flask-docs/_static/down.png new file mode 100644 index 0000000..3003a88 --- /dev/null +++ b/studio/static/doc/flask-docs/_static/down.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/file.png b/studio/static/doc/flask-docs/_static/file.png new file mode 100644 index 0000000..d18082e --- /dev/null +++ b/studio/static/doc/flask-docs/_static/file.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/flask.png b/studio/static/doc/flask-docs/_static/flask.png new file mode 100644 index 0000000..5c603cc --- /dev/null +++ b/studio/static/doc/flask-docs/_static/flask.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/flaskr.png b/studio/static/doc/flask-docs/_static/flaskr.png new file mode 100644 index 0000000..07d027d --- /dev/null +++ b/studio/static/doc/flask-docs/_static/flaskr.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/flasky.css b/studio/static/doc/flask-docs/_static/flasky.css new file mode 100644 index 0000000..48bebc8 --- /dev/null +++ b/studio/static/doc/flask-docs/_static/flasky.css @@ -0,0 +1,387 @@ +/* + * flasky.css_t + * ~~~~~~~~~~~~ + * + * :copyright: Copyright 2010 by Armin Ronacher. + * :license: Flask Design License, see LICENSE for details. + */ + + + + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: 'Georgia', serif; + font-size: 17px; + background-color: #c0c0c0; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + width: 940px; + margin: 30px auto 0 auto; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 220px; +} + +div.sphinxsidebar { + width: 220px; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.body { + background-color: #ffffff; + color: #3E4349; + padding: 0 30px 0 30px; +} + +img.floatingflask { + padding: 0 0 10px 10px; + float: right; +} + +div.footer { + width: 940px; + margin: 20px auto 30px auto; + font-size: 14px; + color: #888; + text-align: right; +} + +div.footer a { + color: #888; +} + +div.related { + display: none; +} + +div.sphinxsidebar a { + color: #444; + text-decoration: none; + border-bottom: 1px dotted #999; +} + +div.sphinxsidebar a:hover { + border-bottom: 1px solid #999; +} + +div.sphinxsidebar { + font-size: 14px; + line-height: 1.5; +} + +div.sphinxsidebarwrapper { + padding: 18px 10px; +} + +div.sphinxsidebarwrapper p.logo { + padding: 0 0 20px 0; + margin: 0; + text-align: center; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: 'Garamond', 'Georgia', serif; + color: #444; + font-size: 24px; + font-weight: normal; + margin: 0 0 5px 0; + padding: 0; +} + +div.sphinxsidebar h4 { + font-size: 20px; +} + +div.sphinxsidebar h3 a { + color: #444; +} + +div.sphinxsidebar p.logo a, +div.sphinxsidebar h3 a, +div.sphinxsidebar p.logo a:hover, +div.sphinxsidebar h3 a:hover { + border: none; +} + +div.sphinxsidebar p { + color: #555; + margin: 10px 0; +} + +div.sphinxsidebar ul { + margin: 10px 0; + padding: 0; + color: #000; +} + +div.sphinxsidebar input { + border: 1px solid #ccc; + font-family: 'Georgia', serif; + font-size: 1em; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #004B6B; + text-decoration: underline; +} + +a:hover { + color: #6D4100; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Garamond', 'Georgia', serif; + font-weight: normal; + margin: 30px 0px 10px 0px; + padding: 0; +} + +div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } +div.body h2 { font-size: 180%; } +div.body h3 { font-size: 150%; } +div.body h4 { font-size: 130%; } +div.body h5 { font-size: 100%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #ddd; + padding: 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + color: #444; + background: #eaeaea; +} + +div.body p, div.body dd, div.body li { + line-height: 1.4em; +} + +div.admonition { + background: #fafafa; + margin: 20px -30px; + padding: 10px 30px; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; +} + +div.admonition tt.xref, div.admonition a tt { + border-bottom: 1px solid #fafafa; +} + +dd div.admonition { + margin-left: -60px; + padding-left: 60px; +} + +div.admonition p.admonition-title { + font-family: 'Garamond', 'Georgia', serif; + font-weight: normal; + font-size: 24px; + margin: 0 0 10px 0; + padding: 0; + line-height: 1; +} + +div.admonition p.last { + margin-bottom: 0; +} + +div.highlight { + background-color: white; +} + +dt:target, .highlight { + background: #FAF3E8; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre, tt { + font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.9em; +} + +img.screenshot { +} + +tt.descname, tt.descclassname { + font-size: 0.95em; +} + +tt.descname { + padding-right: 0.08em; +} + +img.screenshot { + -moz-box-shadow: 2px 2px 4px #eee; + -webkit-box-shadow: 2px 2px 4px #eee; + box-shadow: 2px 2px 4px #eee; +} + +table.docutils { + border: 1px solid #888; + -moz-box-shadow: 2px 2px 4px #eee; + -webkit-box-shadow: 2px 2px 4px #eee; + box-shadow: 2px 2px 4px #eee; +} + +table.docutils td, table.docutils th { + border: 1px solid #888; + padding: 0.25em 0.7em; +} + +table.field-list, table.footnote { + border: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +table.footnote { + margin: 15px 0; + width: 100%; + border: 1px solid #eee; + background: #fdfdfd; + font-size: 0.9em; +} + +table.footnote + table.footnote { + margin-top: -15px; + border-top: none; +} + +table.field-list th { + padding: 0 0.8em 0 0; +} + +table.field-list td { + padding: 0; +} + +table.footnote td.label { + width: 0px; + padding: 0.3em 0 0.3em 0.5em; +} + +table.footnote td { + padding: 0.3em 0.5em; +} + +dl { + margin: 0; + padding: 0; +} + +dl dd { + margin-left: 30px; +} + +blockquote { + margin: 0 0 0 30px; + padding: 0; +} + +ul, ol { + margin: 10px 0 10px 30px; + padding: 0; +} + +pre { + background: #eee; + padding: 7px 30px; + margin: 15px -30px; + line-height: 1.3em; +} + +dl pre, blockquote pre, li pre { + margin-left: -60px; + padding-left: 60px; +} + +dl dl pre { + margin-left: -90px; + padding-left: 90px; +} + +tt { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ +} + +tt.xref, a tt { + background-color: #FBFBFB; + border-bottom: 1px solid white; +} + +a.reference { + text-decoration: none; + border-bottom: 1px dotted #004B6B; +} + +a.reference:hover { + border-bottom: 1px solid #6D4100; +} + +a.footnote-reference { + text-decoration: none; + font-size: 0.7em; + vertical-align: top; + border-bottom: 1px dotted #004B6B; +} + +a.footnote-reference:hover { + border-bottom: 1px solid #6D4100; +} + +a:hover tt { + background: #EEE; +} diff --git a/studio/static/doc/flask-docs/_static/jquery.js b/studio/static/doc/flask-docs/_static/jquery.js new file mode 100644 index 0000000..7c24308 --- /dev/null +++ b/studio/static/doc/flask-docs/_static/jquery.js @@ -0,0 +1,154 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, +Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& +(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, +a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== +"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, +function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a"; +var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, +parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= +false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= +s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, +applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; +else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, +a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== +w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, +cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, +function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); +k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), +C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= +e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& +f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; +if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", +e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, +"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, +d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); +t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| +g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== +"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, +serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), +function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, +global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& +e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? +"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== +false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= +false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", +c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| +d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); +g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== +1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== +"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; +if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== +"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| +c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; +this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= +this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, +e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
"; +a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); +c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, +d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- +f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": +"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in +e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); diff --git a/studio/static/doc/flask-docs/_static/logo-full.png b/studio/static/doc/flask-docs/_static/logo-full.png new file mode 100644 index 0000000..5deaf1b --- /dev/null +++ b/studio/static/doc/flask-docs/_static/logo-full.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/minus.png b/studio/static/doc/flask-docs/_static/minus.png new file mode 100644 index 0000000..da1c562 --- /dev/null +++ b/studio/static/doc/flask-docs/_static/minus.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/no.png b/studio/static/doc/flask-docs/_static/no.png new file mode 100644 index 0000000..4ac1083 --- /dev/null +++ b/studio/static/doc/flask-docs/_static/no.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/plus.png b/studio/static/doc/flask-docs/_static/plus.png new file mode 100644 index 0000000..b3cb374 --- /dev/null +++ b/studio/static/doc/flask-docs/_static/plus.png Binary files differ diff --git a/studio/static/doc/flask-docs/_static/pygments.css b/studio/static/doc/flask-docs/_static/pygments.css new file mode 100644 index 0000000..ee96fae --- /dev/null +++ b/studio/static/doc/flask-docs/_static/pygments.css @@ -0,0 +1,70 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #8f5902; font-style: italic } /* Comment */ +.highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ +.highlight .g { color: #000000 } /* Generic */ +.highlight .k { color: #004461; font-weight: bold } /* Keyword */ +.highlight .l { color: #000000 } /* Literal */ +.highlight .n { color: #000000 } /* Name */ +.highlight .o { color: #582800 } /* Operator */ +.highlight .x { color: #000000 } /* Other */ +.highlight .p { color: #000000; font-weight: bold } /* Punctuation */ +.highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #8f5902 } /* Comment.Preproc */ +.highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #a40000 } /* Generic.Deleted */ +.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #ef2929 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #808080 } /* Generic.Output */ +.highlight .gp { color: #745334 } /* Generic.Prompt */ +.highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ +.highlight .kc { color: #004461; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #004461; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #004461; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #004461; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #004461; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #004461; font-weight: bold } /* Keyword.Type */ +.highlight .ld { color: #000000 } /* Literal.Date */ +.highlight .m { color: #990000 } /* Literal.Number */ +.highlight .s { color: #4e9a06 } /* Literal.String */ +.highlight .na { color: #c4a000 } /* Name.Attribute */ +.highlight .nb { color: #004461 } /* Name.Builtin */ +.highlight .nc { color: #000000 } /* Name.Class */ +.highlight .no { color: #000000 } /* Name.Constant */ +.highlight .nd { color: #808080 } /* Name.Decorator */ +.highlight .ni { color: #ce5c00 } /* Name.Entity */ +.highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #000000 } /* Name.Function */ +.highlight .nl { color: #f57900 } /* Name.Label */ +.highlight .nn { color: #000000 } /* Name.Namespace */ +.highlight .nx { color: #000000 } /* Name.Other */ +.highlight .py { color: #000000 } /* Name.Property */ +.highlight .nt { color: #004461; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #000000 } /* Name.Variable */ +.highlight .ow { color: #004461; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */ +.highlight .mf { color: #990000 } /* Literal.Number.Float */ +.highlight .mh { color: #990000 } /* Literal.Number.Hex */ +.highlight .mi { color: #990000 } /* Literal.Number.Integer */ +.highlight .mo { color: #990000 } /* Literal.Number.Oct */ +.highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */ +.highlight .sc { color: #4e9a06 } /* Literal.String.Char */ +.highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4e9a06 } /* Literal.String.Double */ +.highlight .se { color: #4e9a06 } /* Literal.String.Escape */ +.highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */ +.highlight .si { color: #4e9a06 } /* Literal.String.Interpol */ +.highlight .sx { color: #4e9a06 } /* Literal.String.Other */ +.highlight .sr { color: #4e9a06 } /* Literal.String.Regex */ +.highlight .s1 { color: #4e9a06 } /* Literal.String.Single */ +.highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */ +.highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #000000 } /* Name.Variable.Class */ +.highlight .vg { color: #000000 } /* Name.Variable.Global */ +.highlight .vi { color: #000000 } /* Name.Variable.Instance */ +.highlight .il { color: #990000 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/studio/static/doc/flask-docs/_static/searchtools.js b/studio/static/doc/flask-docs/_static/searchtools.js new file mode 100644 index 0000000..6ed5ac8 --- /dev/null +++ b/studio/static/doc/flask-docs/_static/searchtools.js @@ -0,0 +1,556 @@ +/* + * searchtools.js_t + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilties for the full-text search. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words, hlwords is the list of normal, unstemmed + * words. the first one is used to find the occurance, the + * latter for highlighting it. + */ + +jQuery.makeSearchSummary = function(text, keywords, hlwords) { + var textLower = text.toLowerCase(); + var start = 0; + $.each(keywords, function() { + var i = textLower.indexOf(this.toLowerCase()); + if (i > -1) + start = i; + }); + start = Math.max(start - 120, 0); + var excerpt = ((start > 0) ? '...' : '') + + $.trim(text.substr(start, 240)) + + ((start + 240 - text.length) ? '...' : ''); + var rv = $('
').text(excerpt); + $.each(hlwords, function() { + rv = rv.highlightText(this, 'highlighted'); + }); + return rv; +} + + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + + +/** + * Search Module + */ +var Search = { + + _index : null, + _queued_query : null, + _pulse_status : -1, + + init : function() { + var params = $.getQueryParameters(); + if (params.q) { + var query = params.q[0]; + $('input[name="q"]')[0].value = query; + this.performSearch(query); + } + }, + + loadIndex : function(url) { + $.ajax({type: "GET", url: url, data: null, success: null, + dataType: "script", cache: true}); + }, + + setIndex : function(index) { + var q; + this._index = index; + if ((q = this._queued_query) !== null) { + this._queued_query = null; + Search.query(q); + } + }, + + hasIndex : function() { + return this._index !== null; + }, + + deferQuery : function(query) { + this._queued_query = query; + }, + + stopPulse : function() { + this._pulse_status = 0; + }, + + startPulse : function() { + if (this._pulse_status >= 0) + return; + function pulse() { + Search._pulse_status = (Search._pulse_status + 1) % 4; + var dotString = ''; + for (var i = 0; i < Search._pulse_status; i++) + dotString += '.'; + Search.dots.text(dotString); + if (Search._pulse_status > -1) + window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something + */ + performSearch : function(query) { + // create the required interface elements + this.out = $('#search-results'); + this.title = $('

' + _('Searching') + '

').appendTo(this.out); + this.dots = $('').appendTo(this.title); + this.status = $('

').appendTo(this.out); + this.output = $('