├── .gitignore ├── .gitmodules ├── AUTHORS ├── CHANGES ├── LICENSE ├── README ├── docs ├── .gitignore ├── LICENSE ├── Makefile ├── _static │ ├── debugger.png │ ├── flask.png │ ├── flaskr.png │ ├── logo-full.png │ ├── no.png │ ├── touch-icon.png │ └── yes.png ├── _templates │ ├── sidebarintro.html │ └── sidebarlogo.html ├── _themes │ └── flasky │ │ ├── static │ │ └── flasky.css_t │ │ └── theme.conf ├── api.rst ├── becomingbig.rst ├── blueprints.rst ├── changelog.rst ├── conf.py ├── config.rst ├── contents.rst.inc ├── deploying │ ├── cgi.rst │ ├── fastcgi.rst │ ├── index.rst │ ├── mod_wsgi.rst │ ├── others.rst │ └── uwsgi.rst ├── design.rst ├── errorhandling.rst ├── extensiondev.rst ├── flaskext.py ├── flaskstyle.sty ├── foreword.rst ├── htmlfaq.rst ├── index.rst ├── installation.rst ├── latexindex.rst ├── license.rst ├── logo.pdf ├── make.bat ├── patterns │ ├── appdispatch.rst │ ├── appfactories.rst │ ├── caching.rst │ ├── distribute.rst │ ├── errorpages.rst │ ├── fabric.rst │ ├── favicon.rst │ ├── fileuploads.rst │ ├── flashing.rst │ ├── index.rst │ ├── jquery.rst │ ├── lazyloading.rst │ ├── mongokit.rst │ ├── packages.rst │ ├── sqlalchemy.rst │ ├── sqlite3.rst │ ├── templateinheritance.rst │ ├── urlprocessors.rst │ ├── viewdecorators.rst │ └── wtforms.rst ├── quickstart.rst ├── reqcontext.rst ├── security.rst ├── shell.rst ├── signals.rst ├── styleguide.rst ├── templating.rst ├── testing.rst ├── tutorial │ ├── css.rst │ ├── dbcon.rst │ ├── dbinit.rst │ ├── folders.rst │ ├── index.rst │ ├── introduction.rst │ ├── schema.rst │ ├── setup.rst │ ├── templates.rst │ ├── testing.rst │ └── views.rst ├── unicode.rst ├── upgrading.rst └── views.rst ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.pyo 4 | *.swp 5 | env 6 | dist 7 | *.egg-info 8 | _mailinglist 9 | .bak 10 | Makefile 11 | artwork 12 | _themes 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/_themes"] 2 | path = docs/_themes 3 | url = git://github.com/mitsuhiko/flask-sphinx-themes.git 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Flask is written and maintained by Armin Ronacher and 2 | various contributors: 3 | 4 | Development Lead 5 | ```````````````` 6 | 7 | - Armin Ronacher 8 | 9 | Patches and Suggestions 10 | ``````````````````````` 11 | 12 | - Adam Zapletal 13 | - Chris Edgemon 14 | - Chris Grindstaff 15 | - Christopher Grebs 16 | - Florent Xicluna 17 | - Georg Brandl 18 | - Justin Quick 19 | - Kenneth Reitz 20 | - Marian Sigler 21 | - Matt Campell 22 | - Matthew Frazier 23 | - Ron DuPlain 24 | - Sebastien Estienne 25 | - Simon Sapin 26 | - Stephane Wirtel 27 | - Thomas Schranz 28 | - Zhao Xiaohong 29 | 30 | 中文翻译者 31 | ```````````````` 32 | - Young King 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 by Armin Ronacher and contributors. See AUTHORS 2 | for more details. 3 | 4 | Some rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are 8 | met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following 15 | disclaimer in the documentation and/or other materials provided 16 | with the distribution. 17 | 18 | * The names of the contributors may not be used to endorse or 19 | promote products derived from this software without specific 20 | prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | // Flask // 2 | 3 | web development, one drop at a time 4 | Web 开发,一点一滴 5 | 6 | 7 | ~ 什么是Flask? 8 | 9 | Flask是Python环境下的一个基于Werkzeug和Jinja2的微型网络应用框架。 10 | Flask凝结着我们最强烈的意向,它适合小规模的网络应用程序。 11 | 12 | ~ Flask准备好了吗? 13 | 14 | Flask还没有到1.0版,但是已经被塑造的很好,而且被广泛应用了。 15 | 未来,API也许会有一些小的变动,但是我们不会去阻止这些变动。 16 | 17 | ~ 我需要准备些什么? 18 | 19 | Jinja 2.4 和 Werkzeug 0.6.1. 20 | 如果你希望通过 `easy_install Flask` 来安装Flask, 21 | 那么你需要先安装 `pip` 或者 `easy_install` 。 22 | 我们建议使用一个虚拟环境(virtualenv)。 23 | 更多细节请查阅文档中有关Flask安装以及使用说明的部分。 24 | 25 | ~ 文档在哪里? 26 | 27 | 浏览 http://flask.pocoo.org/docs/ 28 | 你将会看到一个还未编译成册的最新版本的在线文档。 29 | 当然,你也可以自己利用文档的Sphinx源代码(ReST)生产其他格式的文档。 30 | 31 | ~ 如何测试Flask程序? 32 | 33 | 问的非常好。 34 | 测试程序在 flask/testsuite 包中。 35 | 可以用 `run-tests.py` 文件运行测试: 36 | 37 | $ python run-tests.py 38 | 39 | 如果还不能获得足够的输出信息, 40 | 那么你可以使用 `--verbose` 参数: 41 | 42 | $ python run-tests.py --verbose 43 | 44 | 如果你只想运行一个测试用例, 45 | 那么你可以在命令行中这样输入: 46 | 47 | $ python run-tests.py test_to_run 48 | 49 | ~ 我可以从哪里获得帮助? 50 | 51 | 可以通过 #pocoo IRC 频道(irc.freenode.net) 52 | 或者在邮件列表中提问, 53 | 邮件列表地址: http://flask.pocoo.org/mailinglist/ -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 by Armin Ronacher and contributors. See AUTHORS 2 | for more details. 3 | 4 | Some rights reserved. 5 | 6 | Redistribution and use in source (reStructuredText) and 'compiled' forms (HTML, 7 | PDF, PostScript, RTF and so forth) with or without modification, are permitted 8 | provided that the following conditions are met: 9 | 10 | * Redistributions of source code (reStructuredText) must retain the above 11 | copyright notice, this list of conditions and the following disclaimer as the 12 | first lines of this file unmodified. 13 | 14 | * Redistributions in compiled form (converted to HTML, PDF, PostScript, RTF 15 | and so forth) must reproduce the above copyright notice, this list of 16 | conditions and the following disclaimer in the documentation and/or other 17 | materials provided with the distribution. 18 | 19 | THIS DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 20 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp epub latex changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " changes to make an overview of all changed/added/deprecated items" 31 | @echo " linkcheck to check all external links for integrity" 32 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 33 | 34 | clean: 35 | -rm -rf $(BUILDDIR)/* 36 | 37 | html: 38 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 39 | @echo 40 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 41 | 42 | dirhtml: 43 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 44 | @echo 45 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 46 | 47 | singlehtml: 48 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 49 | @echo 50 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 51 | 52 | pickle: 53 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 54 | @echo 55 | @echo "Build finished; now you can process the pickle files." 56 | 57 | json: 58 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 59 | @echo 60 | @echo "Build finished; now you can process the JSON files." 61 | 62 | htmlhelp: 63 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 64 | @echo 65 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 66 | ".hhp project file in $(BUILDDIR)/htmlhelp." 67 | 68 | qthelp: 69 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 70 | @echo 71 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 72 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 73 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask.qhcp" 74 | @echo "To view the help file:" 75 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask.qhc" 76 | 77 | devhelp: 78 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) _build/devhelp 79 | @echo 80 | @echo "Build finished." 81 | @echo "To view the help file:" 82 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask" 83 | @echo "# ln -s _build/devhelp $$HOME/.local/share/devhelp/Flask" 84 | @echo "# devhelp" 85 | 86 | epub: 87 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 88 | @echo 89 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 90 | 91 | latex: 92 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 93 | @echo 94 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 95 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 96 | "run these through (pdf)latex." 97 | 98 | latexpdf: latex 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex 100 | @echo "Running LaTeX files through pdflatex..." 101 | make -C _build/latex all-pdf 102 | @echo "pdflatex finished; the PDF files are in _build/latex." 103 | 104 | changes: 105 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 106 | @echo 107 | @echo "The overview file is in $(BUILDDIR)/changes." 108 | 109 | linkcheck: 110 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 111 | @echo 112 | @echo "Link check complete; look for any errors in the above output " \ 113 | "or in $(BUILDDIR)/linkcheck/output.txt." 114 | 115 | doctest: 116 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 117 | @echo "Testing of doctests in the sources finished, look at the " \ 118 | "results in $(BUILDDIR)/doctest/output.txt." 119 | -------------------------------------------------------------------------------- /docs/_static/debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayhome/flask-cn/560e4ce88aa15d410a619ab4fa629ff508b3c4e9/docs/_static/debugger.png -------------------------------------------------------------------------------- /docs/_static/flask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayhome/flask-cn/560e4ce88aa15d410a619ab4fa629ff508b3c4e9/docs/_static/flask.png -------------------------------------------------------------------------------- /docs/_static/flaskr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayhome/flask-cn/560e4ce88aa15d410a619ab4fa629ff508b3c4e9/docs/_static/flaskr.png -------------------------------------------------------------------------------- /docs/_static/logo-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayhome/flask-cn/560e4ce88aa15d410a619ab4fa629ff508b3c4e9/docs/_static/logo-full.png -------------------------------------------------------------------------------- /docs/_static/no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayhome/flask-cn/560e4ce88aa15d410a619ab4fa629ff508b3c4e9/docs/_static/no.png -------------------------------------------------------------------------------- /docs/_static/touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayhome/flask-cn/560e4ce88aa15d410a619ab4fa629ff508b3c4e9/docs/_static/touch-icon.png -------------------------------------------------------------------------------- /docs/_static/yes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayhome/flask-cn/560e4ce88aa15d410a619ab4fa629ff508b3c4e9/docs/_static/yes.png -------------------------------------------------------------------------------- /docs/_templates/sidebarintro.html: -------------------------------------------------------------------------------- 1 |

About Flask

2 |

3 | Flask is a micro webdevelopment framework for Python. You are currently 4 | looking at the documentation of the development version. Things are 5 | not stable yet, but if you have some feedback, 6 | let me know. 7 |

8 |

Other Formats

9 |

10 | You can download the documentation in other formats as well: 11 |

12 | 16 |

Useful Links

17 | 22 | -------------------------------------------------------------------------------- /docs/_templates/sidebarlogo.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /docs/_themes/flasky/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- flasky theme based on nature theme. 6 | * 7 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'Georgia', serif; 18 | font-size: 17px; 19 | background-color: #ddd; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | background: #fafafa; 27 | } 28 | 29 | div.documentwrapper { 30 | float: left; 31 | width: 100%; 32 | } 33 | 34 | div.bodywrapper { 35 | margin: 0 0 0 230px; 36 | } 37 | 38 | hr { 39 | border: 1px solid #B1B4B6; 40 | } 41 | 42 | div.body { 43 | background-color: #ffffff; 44 | color: #3E4349; 45 | padding: 0 30px 30px 30px; 46 | min-height: 34em; 47 | } 48 | 49 | img.floatingflask { 50 | padding: 0 0 10px 10px; 51 | float: right; 52 | } 53 | 54 | div.footer { 55 | position: absolute; 56 | right: 0; 57 | margin-top: -70px; 58 | text-align: right; 59 | color: #888; 60 | padding: 10px; 61 | font-size: 14px; 62 | } 63 | 64 | div.footer a { 65 | color: #888; 66 | text-decoration: underline; 67 | } 68 | 69 | div.related { 70 | line-height: 32px; 71 | color: #888; 72 | } 73 | 74 | div.related ul { 75 | padding: 0 0 0 10px; 76 | } 77 | 78 | div.related a { 79 | color: #444; 80 | } 81 | 82 | div.sphinxsidebar { 83 | font-size: 14px; 84 | line-height: 1.5; 85 | } 86 | 87 | div.sphinxsidebarwrapper { 88 | padding: 0 20px; 89 | } 90 | 91 | div.sphinxsidebarwrapper p.logo { 92 | padding: 20px 0 10px 0; 93 | margin: 0; 94 | text-align: center; 95 | } 96 | 97 | div.sphinxsidebar h3, 98 | div.sphinxsidebar h4 { 99 | font-family: 'Garamond', 'Georgia', serif; 100 | color: #222; 101 | font-size: 24px; 102 | font-weight: normal; 103 | margin: 20px 0 5px 0; 104 | padding: 0; 105 | } 106 | 107 | div.sphinxsidebar h4 { 108 | font-size: 20px; 109 | } 110 | 111 | div.sphinxsidebar h3 a { 112 | color: #444; 113 | } 114 | 115 | div.sphinxsidebar p { 116 | color: #555; 117 | margin: 10px 0; 118 | } 119 | 120 | div.sphinxsidebar ul { 121 | margin: 10px 0; 122 | padding: 0; 123 | color: #000; 124 | } 125 | 126 | div.sphinxsidebar a { 127 | color: #444; 128 | text-decoration: none; 129 | } 130 | 131 | div.sphinxsidebar a:hover { 132 | text-decoration: underline; 133 | } 134 | 135 | div.sphinxsidebar input { 136 | border: 1px solid #ccc; 137 | font-family: 'Georgia', serif; 138 | font-size: 1em; 139 | } 140 | 141 | /* -- body styles ----------------------------------------------------------- */ 142 | 143 | a { 144 | color: #004B6B; 145 | text-decoration: underline; 146 | } 147 | 148 | a:hover { 149 | color: #6D4100; 150 | text-decoration: underline; 151 | } 152 | 153 | div.body { 154 | padding-bottom: 40px; /* saved for footer */ 155 | } 156 | 157 | div.body h1, 158 | div.body h2, 159 | div.body h3, 160 | div.body h4, 161 | div.body h5, 162 | div.body h6 { 163 | font-family: 'Garamond', 'Georgia', serif; 164 | font-weight: normal; 165 | margin: 30px 0px 10px 0px; 166 | padding: 0; 167 | } 168 | 169 | div.body h1 { margin-top: 0; padding-top: 20px; font-size: 240%; } 170 | div.body h2 { font-size: 180%; } 171 | div.body h3 { font-size: 150%; } 172 | div.body h4 { font-size: 130%; } 173 | div.body h5 { font-size: 100%; } 174 | div.body h6 { font-size: 100%; } 175 | 176 | a.headerlink { 177 | color: white; 178 | padding: 0 4px; 179 | text-decoration: none; 180 | } 181 | 182 | a.headerlink:hover { 183 | color: #444; 184 | background: #eaeaea; 185 | } 186 | 187 | div.body p, div.body dd, div.body li { 188 | line-height: 1.4em; 189 | } 190 | 191 | div.admonition { 192 | background: #fafafa; 193 | margin: 20px -30px; 194 | padding: 10px 30px; 195 | border-top: 1px solid #ccc; 196 | border-bottom: 1px solid #ccc; 197 | } 198 | 199 | div.admonition p.admonition-title { 200 | font-family: 'Garamond', 'Georgia', serif; 201 | font-weight: normal; 202 | font-size: 24px; 203 | margin: 0 0 10px 0; 204 | padding: 0; 205 | line-height: 1; 206 | } 207 | 208 | div.admonition p.last { 209 | margin-bottom: 0; 210 | } 211 | 212 | div.highlight{ 213 | background-color: white; 214 | } 215 | 216 | dt:target, .highlight { 217 | background: #FAF3E8; 218 | } 219 | 220 | div.note { 221 | background-color: #eee; 222 | border: 1px solid #ccc; 223 | } 224 | 225 | div.seealso { 226 | background-color: #ffc; 227 | border: 1px solid #ff6; 228 | } 229 | 230 | div.topic { 231 | background-color: #eee; 232 | } 233 | 234 | div.warning { 235 | background-color: #ffe4e4; 236 | border: 1px solid #f66; 237 | } 238 | 239 | p.admonition-title { 240 | display: inline; 241 | } 242 | 243 | p.admonition-title:after { 244 | content: ":"; 245 | } 246 | 247 | pre, tt { 248 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 249 | font-size: 0.9em; 250 | } 251 | 252 | img.screenshot { 253 | } 254 | 255 | tt.descname, tt.descclassname { 256 | font-size: 0.95em; 257 | } 258 | 259 | tt.descname { 260 | padding-right: 0.08em; 261 | } 262 | 263 | img.screenshot { 264 | -moz-box-shadow: 2px 2px 4px #eee; 265 | -webkit-box-shadow: 2px 2px 4px #eee; 266 | box-shadow: 2px 2px 4px #eee; 267 | } 268 | 269 | table.docutils { 270 | border: 1px solid #888; 271 | -moz-box-shadow: 2px 2px 4px #eee; 272 | -webkit-box-shadow: 2px 2px 4px #eee; 273 | box-shadow: 2px 2px 4px #eee; 274 | } 275 | 276 | table.docutils td, table.docutils th { 277 | border: 1px solid #888; 278 | padding: 0.25em 0.7em; 279 | } 280 | 281 | table.field-list, table.footnote { 282 | border: none; 283 | -moz-box-shadow: none; 284 | -webkit-box-shadow: none; 285 | box-shadow: none; 286 | } 287 | 288 | table.footnote { 289 | margin: 15px 0; 290 | width: 100%; 291 | border: 1px solid #eee; 292 | } 293 | 294 | table.field-list th { 295 | padding: 0 0.8em 0 0; 296 | } 297 | 298 | table.field-list td { 299 | padding: 0; 300 | } 301 | 302 | table.footnote td { 303 | padding: 0.5em; 304 | } 305 | 306 | dl { 307 | margin: 0; 308 | padding: 0; 309 | } 310 | 311 | dl dd { 312 | margin-left: 30px; 313 | } 314 | 315 | pre { 316 | background: #eee; 317 | padding: 7px 30px; 318 | margin: 15px -30px; 319 | line-height: 1.3em; 320 | } 321 | 322 | dl pre { 323 | margin-left: -60px; 324 | padding-left: 60px; 325 | } 326 | 327 | dl dl pre { 328 | margin-left: -90px; 329 | padding-left: 90px; 330 | } 331 | 332 | tt { 333 | background-color: #ecf0f3; 334 | color: #222; 335 | /* padding: 1px 2px; */ 336 | } 337 | 338 | tt.xref, a tt { 339 | background-color: #FBFBFB; 340 | } 341 | 342 | a:hover tt { 343 | background: #EEE; 344 | } 345 | -------------------------------------------------------------------------------- /docs/_themes/flasky/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | -------------------------------------------------------------------------------- /docs/becomingbig.rst: -------------------------------------------------------------------------------- 1 | .. _becomingbig: 2 | 3 | 搞大了?! 4 | ============ 5 | :译者: fermin.yang#gmail.com 6 | 7 | 你的应用程序越搞越大越来越复杂了?如果你某天猛然意识到基于这种方式的Flask编程对于 8 | 你的应用程序已经不够给力怎么办?别慌,Flask还是搞得定! 9 | 10 | Flask基于Werkzeug和Jinja2技术,这两套类库已经广泛应用于许多正在运行的大型网站系统, 11 | 而Flask则将二者有机的融合在一起。作为一个微型的框架,Flask除了整合已有的类库外其他 12 | 没有瞎掺和 - 代码不是特别多啦。 就是说项目即使搞大了也可以非常方便地将自己的代码抽出 13 | 然后封装到某一个新应用程序的模块里且加以扩展。 14 | 15 | Flask在设计初时就考虑到了扩展和修改的可能性,可以用下列方法来搞定这个问题: 16 | 17 | - Flask 扩展. 你可以针对大量可重复利用的代码功能进行扩展,之后在整个Flask环境内 18 | 就会产生很多附带信号和回调功能的钩子可供使用。 19 | 20 | - 子类化. 大多数的功能都能通过创建 :class:`~flask.Flask` 的子类和重载 21 | 针对此目的方法来进行个性改装。 22 | 23 | - 开分舵. 如果实在没办法搞定你还可以在Flask的代码库的指定的位置选择一些源码(文件) 24 | 复制/粘贴到你的应用程序(目录)中然后进行修改。Flask在设计时已经考虑到你可能会这么 25 | 干所以一定会让你干的很爽。你要做的仅仅是选定几个包然后复制到应用程序代码文件(文件夹) 26 | 里,并且对其重命名(例如`framework`)。然后你可以在那里对代码做进一步的修改。 27 | 28 | 干嘛要开分舵? 29 | --------------------- 30 | 31 | Flask的主要代码是由Werkzeug和Jinja2组成的。这两个类库搞定了绝大部分的工作。Flask只是 32 | 负责粘贴并且将二者紧密联系在一起。自古对于许多项目来说存在这么一个观点,那就是底层框架“卖艺 33 | 不卖身”,往往感觉像是个鸡肋(基于假定初始开发人员碰到这个问题)。这样看来允许开“分舵”形式的产 34 | 生就很自然而然了,因为如果不这么干,框架就会变得非常复杂很难入手,会造成框架的学习曲线十分陡峭, 35 | 许多使用者信心大减等不和谐的问题。 36 | 37 | 这个问题不是Flask独有的。许多人通过对它们的框架打补丁或更新版本来弥补不足。这个概念在Flask 38 | 的授权协议里也有体现。你在决定并更改已经属于你应用程序一部分的“分舵”框架时,无需向我们提交任何 39 | “保护费”(信息)。 40 | 41 | 开“分舵”当然也有他的缺点,那就是Flask“总舵”的更新可能会变更导入命名,这样会使得大多数的Flask扩展 42 | 不能使用。此外,与“总舵”的新版本整合可能是一个非常复杂的过程,这个要根据更新的数量进行估算。总之, 43 | “开分舵”应该是最后一招,不得已而为之的。 44 | 45 | 像大师一样游刃有余 46 | ------------------ 47 | 48 | 对于许多Web应用程序来说,代码的复杂度和处理响应多到爆的用户或数据请求相比,简直是小巫见大巫。 49 | Flask自身仅根据你的应用程序代码,你使用的数据存储方式,Python的执行效率和你挂载的Web服务器 50 | 的不同而受到限制。 51 | 52 | 好的延展性举例说明就是如果你将你的服务器的数量翻倍,你马上获得了双倍的运行表现。相对的,差的 53 | 延展性就是即使你买了新的服务器也不能给你带来什么帮助或者你的应用程序根本不支持多服务器负载。 54 | 55 | 在Flask只有一个限制因素与延展有关,那就是上下文本地代理(context local proxies)。他们基于 56 | Flask里那些被定义为线程,进程或者greenlet(python的一个扩展模块)的上下文。如果你的服务器进 57 | 行用某种不是基于线程或者greenlets的并发处理时,Flask不会支持这些全局代理。然而大多数服务器只 58 | 能通过使用线程,greenlet或者独立进程来实现并发,且底层的Werkzeug类库对这些方法都提供了很好 59 | 的支持。 60 | 61 | 通过网络社区进行交流 62 | --------------------------- 63 | 64 | Flask的开发人员一直认为大家开发的爽就是自己爽,所以一旦你碰到任何因为Flask导致的问题,不要憋着, 65 | 通过邮件列表发邮件或者上IRC频道炮轰我们吧。这也是促使编写Flask和Flask扩展的程序员提高,把应用程 66 | 序搞得更大的最佳途径。 67 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGES 2 | -------------------------------------------------------------------------------- /docs/contents.rst.inc: -------------------------------------------------------------------------------- 1 | 用户指南 2 | ------------ 3 | 4 | 这部分文档主要介绍了Flask的背景,然后对于Flask的web开发做了一个一步一 5 | 步的要点指示. 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | foreword 11 | installation 12 | quickstart 13 | tutorial/index 14 | templating 15 | testing 16 | errorhandling 17 | config 18 | signals 19 | views 20 | reqcontext 21 | blueprints 22 | shell 23 | patterns/index 24 | deploying/index 25 | becomingbig 26 | 27 | API 参考 28 | ------------- 29 | 30 | If you are looking for information on a specific function, class or 31 | method, this part of the documentation is for you. 32 | 33 | .. toctree:: 34 | :maxdepth: 2 35 | 36 | api 37 | 38 | 其它事项 39 | ---------------- 40 | 41 | Design notes, legal information and changelog are here for the interested. 42 | 43 | .. toctree:: 44 | :maxdepth: 2 45 | 46 | design 47 | htmlfaq 48 | security 49 | unicode 50 | extensiondev 51 | styleguide 52 | upgrading 53 | changelog 54 | license 55 | -------------------------------------------------------------------------------- /docs/deploying/cgi.rst: -------------------------------------------------------------------------------- 1 | CGI 2 | === 3 | 4 | 即使所有其他的部署方法都不行,CGI一定行! 5 | 所有主流的服务器都支持CGI,只不过这个不是首选的解决方案。 6 | 7 | 这也是一种将你的Flask应用架设在Google的 `App Engine`_ 上的一种方法,或者其他任何和CGI类似的环境下。 8 | 9 | .. admonition:: 注意 10 | 11 | 请事先确认你应用程序内的所有 ``app.run()`` 调用包含在 ``if __name__ == '__main__':`` 区块内或在一个单独的文件里。 要确认这个的原因是因为这句语句执行后总是会启动一个新的本地WSGI服务器,但在部署到CGI / app的环境中,我们不需要它。 12 | 13 | 创建一个 `.cgi` 文件 14 | ---------------------- 15 | 16 | 首先你要创建一个CGI应用文件。我们先给它起个名字,就叫它 `yourapplication.cgi`:: 17 | 18 | #!/usr/bin/python 19 | from wsgiref.handlers import CGIHandler 20 | from yourapplication import app 21 | 22 | CGIHandler().run(app) 23 | 24 | 服务器设置 25 | ------------ 26 | 27 | 配置服务器通常有两种方法。直接将 `.cgi` 文件拷贝到一个 `cgi-bin` (并且使用 `mod_rewrite` 或者其他类似的东西来重写URL)或者将服务器直接指向文件。 28 | 29 | 例如在Apache环境下,你可以在配置文件内加入以下内容: 30 | 31 | .. sourcecode:: apache 32 | 33 | ScriptAlias /app /path/to/the/application.cgi 34 | 35 | 如你需要更多的信息请参考你使用的web服务器的文档。 36 | 37 | .. _App Engine: http://code.google.com/appengine/ 38 | -------------------------------------------------------------------------------- /docs/deploying/fastcgi.rst: -------------------------------------------------------------------------------- 1 | .. _deploying-fastcgi: 2 | 3 | FastCGI 4 | ======= 5 | 6 | FastCGI是一架设在诸如 `nginx`_, `lighttpd`_, 还有 `cherokee`_ 等服务器上的部署方式,请查阅 :ref:`deploying-uwsgi` 和 :ref:`deploying-other-servers` 获取关于上述方式的详细内容。在将你的WSGI应用架设到上述服务器上之前,你还需要一个FastCGI服务器。目前最流行的是 `flup`_ ,我们将在本向导里使用它。请确认将其安装在环境内。 7 | 8 | .. admonition:: 注意 9 | 10 | 请事先确认你应用程序内的所有 ``app.run()`` 调用包含在 ``if __name__ == '__main__':`` 区块内或在一个单独的文件里。 要确认这个的原因是因为这句语句执行后总是会启动一个新的本地WSGI服务器,但在部署到FastCGI的环境中,我们不需要它。 11 | 12 | 创建一个 `.fcgi` 文件 13 | ----------------------- 14 | 15 | 首先你需要创建一个FastCGI服务器文件。我们给它起了名字,就叫它 `yourapplication.fcgi`:: 16 | 17 | #!/usr/bin/python 18 | from flup.server.fcgi import WSGIServer 19 | from yourapplication import app 20 | 21 | if __name__ == '__main__': 22 | WSGIServer(app).run() 23 | 24 | 这已经足够让Apache正常工作了,不过nginx和老版本的lighttpd需要一个socket(套接字)来使与FastCGI服务器之间的通信更加明确。为了实现这一点你需要将socket的路径传到 :class:`~flup.server.fcgi.WSGIServer` 类里:: 25 | 26 | WSGIServer(application, bindAddress='/path/to/fcgi.sock').run() 27 | 28 | 这里的路径必须与你在服务器配置文件内定义的完全一致。 29 | 30 | 将 `yourapplication.fcgi` 文件保存在你能找得到的地方。 31 | 比如在 `/var/www/yourapplication` 或类似的地方就比较靠谱。 32 | 33 | 确认这个文件上有服务器可以的执行权限: 34 | 35 | .. sourcecode:: text 36 | 37 | # chmod +x /var/www/yourapplication/yourapplication.fcgi 38 | 39 | 配置 lighttpd 40 | -------------------- 41 | 42 | lighttpd的FastCGI基本配置如下:: 43 | 44 | fastcgi.server = ("/yourapplication.fcgi" => 45 | (( 46 | "socket" => "/tmp/yourapplication-fcgi.sock", 47 | "bin-path" => "/var/www/yourapplication/yourapplication.fcgi", 48 | "check-local" => "disable", 49 | "max-procs" -> 1 50 | )) 51 | ) 52 | 53 | alias.url = ( 54 | "/static/" => "/path/to/your/static" 55 | ) 56 | 57 | url.rewrite-once = ( 58 | "^(/static.*)$" => "$1", 59 | "^(/.*)$" => "/yourapplication.fcgi$1" 60 | 61 | 记住要启用FastCGI, alias和rewrite模块。上述配置将要发布的应用绑定到了 `/yourapplication` 路径下。如果你希望将应用配置在根目录执行,那你就不得不先研究 :class:`~werkzeug.contrib.fixers.LighttpdCGIRootFix` 这个中间件来搞定lighttpd中的bug。 62 | 63 | 确认仅在你确实需要将你的应用配置到根目录时应用这个中间件。另外,如果需要了解更多信息也可以去看Lighty关于 `FastCGI and 64 | Python `_ 的文档(注意Lighty文档里提到的将socket传给run()的部分已经不需要了)。 65 | 66 | 67 | 配置 nginx 68 | ----------------- 69 | 70 | 在nginx上安装FastCGI稍微有些不同,因为在默认情况下nginx没有提供FastCGI参数。 71 | 72 | 基本的在nginx下的flask FastCGI配置如下:: 73 | 74 | location = /yourapplication { rewrite ^ /yourapplication/ last; } 75 | location /yourapplication { try_files $uri @yourapplication; } 76 | location @yourapplication { 77 | include fastcgi_params; 78 | fastcgi_split_path_info ^(/yourapplication)(.*)$; 79 | fastcgi_param PATH_INFO $fastcgi_path_info; 80 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 81 | fastcgi_pass unix:/tmp/yourapplication-fcgi.sock; 82 | } 83 | 84 | 上述配置将要发布的应用绑定到了 `/yourapplication` 路径下。如果你希望将应用配置在根目录执行,这非常方便,因为你不需要考虑怎么算出 `PATH_INFO` 和 `SCRIPT_NAME` 的问题:: 85 | 86 | location / { try_files $uri @yourapplication; } 87 | location @yourapplication { 88 | include fastcgi_params; 89 | fastcgi_param PATH_INFO $fastcgi_script_name; 90 | fastcgi_param SCRIPT_NAME ""; 91 | fastcgi_pass unix:/tmp/yourapplication-fcgi.sock; 92 | } 93 | 94 | 执行 FastCGI 进程 95 | ------------------------- 96 | 97 | 既然 Nginx 和其他服务器不自动加载FastCGI的应用,你只好自己来了。 `管理员可以控制FastCGI进程 `_ 你也可以研究其他FastCGI进程的管理方法或者写一个脚本在系统启动时来运行你的 `.fcgi` 文件,举个例子,使用一个 SysV ``init.d`` 脚本。如果你是需要临时应付一下,你可以在GNU下直接执行 ``.fcgi`` 脚本然后不要去关它就好了。如需更多信息请看 ``man screen`` ,注意这是一个全手动的,重启后就只能再来一次:: 98 | 99 | $ screen 100 | $ /var/www/yourapplication/yourapplication.fcgi 101 | 102 | 调试 103 | --------- 104 | 105 | FastCGI的部署方式在大多数web服务器下都很难调试。很多情况下服务器的日志只能告诉你诸如 "premature end of headers" (网页个屁)的信息。为了要找出问题所在,你唯一能研究尝试的地方就是换一个确定能用的用户然后再次手动执行你的应用。 106 | 107 | 这个例子假设你的应用叫做 `application.fcgi` ,你的web服务器用户是 `www-data`:: 108 | 109 | $ su www-data 110 | $ cd /var/www/yourapplication 111 | $ python application.fcgi 112 | Traceback (most recent call last): 113 | File "yourapplication.fcgi", line 4, in 114 | ImportError: No module named yourapplication 115 | 116 | 在这个例子里这个错误大多是因为 "yourapplication" 应用不在python能找到的路径里。这个问题通常出在: 117 | 118 | - 使用了相对路径,且和当前工作目录没有关系。 119 | - 部分代码需要调用环境变量,但是没有在web服务器内配置。 120 | - 使用了其他python的解释器。 121 | 122 | .. _nginx: http://nginx.org/ 123 | .. _lighttpd: http://www.lighttpd.net/ 124 | .. _cherokee: http://www.cherokee-project.com/ 125 | .. _flup: http://trac.saddi.com/flup 126 | -------------------------------------------------------------------------------- /docs/deploying/index.rst: -------------------------------------------------------------------------------- 1 | .. _deployment: 2 | 3 | 部署方式 4 | ================== 5 | :译者: fermin.yang#gmail.com 6 | 7 | 你有很多种途径可以运行Flask应用程序,你可以根据实际情况进行选择。你当然可以直接使用内嵌的服务器,但是使用正规的部署方式来呈现产品更加靠谱。(就是说别在发布的服务器上偷懒用内嵌的啦。)你有很多种的选择,这里都将一一列出。 8 | 9 | 如果你要用其他的WSGI服务器请自行查阅关于如何使用WSGI app的服务器文档。你只要记住你的 :class:`Flask` 应用程序对象事实上就是一个WSGI应用程序就可以了。 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | mod_wsgi 15 | cgi 16 | fastcgi 17 | uwsgi 18 | others 19 | -------------------------------------------------------------------------------- /docs/deploying/mod_wsgi.rst: -------------------------------------------------------------------------------- 1 | .. _mod_wsgi-deployment: 2 | 3 | mod_wsgi (Apache) 4 | ================= 5 | 6 | 如果你使用 `Apache`_ 作为Web服务器, 你可以考虑使用 `mod_wsgi`_. 7 | 8 | .. admonition:: 注意 9 | 10 | 请事先确认你应用程序内的所有 ``app.run()`` 调用包含在 ``if __name__ == '__main__':`` 区块内或在一个单独的文件里。 要确认这个的原因是因为这句语句执行后总是会启动一个新的本地WSGI服务器,但在部署到mod_wsgi的环境中,我们不需要它。 11 | 12 | .. _Apache: http://httpd.apache.org/ 13 | 14 | 安装 `mod_wsgi` 15 | --------------------- 16 | 17 | 如果你还没有安装 `mod_wsgi` ,你需要自己去下载安装或者自行编译。你可以点击 `安装指南`_ 来学习如何在UNIX系统下安装mod_wsgi。 18 | 19 | 如果你使用Ubuntu或Debian,你可以使用apt-get来启用它,如下所示: 20 | 21 | .. sourcecode:: text 22 | 23 | # apt-get install libapache2-mod-wsgi 24 | 25 | 在FreeBSD上安装`mod_wsgi`可以通过编译 `www/mod_wsgi` 端口或者使用pkg_add: 26 | 27 | .. sourcecode:: text 28 | 29 | # pkg_add -r mod_wsgi 30 | 31 | 如果你使用pkgsrc,你可以通过编译 `www/ap2-wsgi` 包来安装 `mod_wsgi` 。 32 | 33 | 如果在第一次apache重启后出现segfaulting child processes(子进程段错误),你可以直接的无视。再重启一下即可。 34 | 35 | 创建一个 `.wsgi` 文件 36 | ----------------------- 37 | 38 | 要运行你的应用程序,你需要一个 `yourapplication.wsgi` 文件。这个文件包含 `mod_wsgi` 在启动时需要执行的获取应用程序对象的代码。该对象在那个文件里调用了 `application` 然后就会把它当作应用程序来处理了。 39 | 40 | 对于大多数应用程序而言,用下面这样的一个文件就足够了:: 41 | 42 | from yourapplication import app as application 43 | 44 | 如果你没有创建应用程序对象的工厂方法,只有一个单独的实例,你可以直接将其作为 `application` 直接import(导入)。 45 | 46 | 找一个地方把这个文件放好,以便于你可以再次找到它 (举例:`/var/www/yourapplication`) 并且确认 `yourapplication` 以及所有你已经使用到的类库在python可以读取的路径内。如果你不希望整个系统使用同一个运行环境,你可以考虑创建一个 `虚拟 python`_ 环境。 47 | 48 | 配置 Apache 49 | ------------------ 50 | 51 | 你要做的最后一件事就是为你的应用程序建立一个Apache配置文件。基于安全方面的原因,在这个实例里我们会让 `mod_wsgi` 在不同的用户环境下执行应用程序: 52 | 53 | .. sourcecode:: apache 54 | 55 | 56 | ServerName example.com 57 | 58 | WSGIDaemonProcess yourapplication user=user1 group=group1 threads=5 59 | WSGIScriptAlias / /var/www/yourapplication/yourapplication.wsgi 60 | 61 | 62 | WSGIProcessGroup yourapplication 63 | WSGIApplicationGroup %{GLOBAL} 64 | Order deny,allow 65 | Allow from all 66 | 67 | 68 | 69 | 如需要更多信息请参考 `mod_wsgi 维基`_. 70 | 71 | .. _mod_wsgi: http://code.google.com/p/modwsgi/ 72 | .. _安装指南: http://code.google.com/p/modwsgi/wiki/QuickInstallationGuide 73 | .. _虚拟 python: http://pypi.python.org/pypi/virtualenv 74 | .. _mod_wsgi 维基: http://code.google.com/p/modwsgi/wiki/ 75 | 76 | 常见问题 77 | --------------- 78 | 79 | 如果你的应用程序没有运行,请跟着以下步骤来排错: 80 | 81 | **问题:** 应用程序没有运行,错误日志显示SystemExit ignored 82 | 在你的应用程序文件里有某处调用了 ``app.run()`` ,但是没有包含在 ``if __name__ == '__main__':`` 条件内。你可以将这个文件里的调用方法移到一个单独的 `run.py` 文件或者给它加上一个if语块。 83 | 84 | **问题:** 应用程序抛出permission(许可)错误 85 | 这个可能是由于运行你的应用程序的用户不正确。请确认应用程序所在目录的用户访问权限使应用程序获得所需的执行条件,且再次确认实际用户与配置用户是否一致。(确认 `WSGIDaemonProcess` 命令的 ``用户`` 和 ``组`` 参数) 86 | 87 | **问题:** 应用程序挂了并且输出了错误信息 88 | 请记住 mod_wsgi 不允许做任何的 :data:`sys.stdout` 和 :data:`sys.stderr` 操作。你可以配置文件里设置 `WSGIRestrictStdout` 的值为 ``off`` 来禁用该保护: 89 | 90 | .. sourcecode:: apache 91 | 92 | WSGIRestrictStdout Off 93 | 94 | 或者你也可以在.wsgi文件里用一个不一样的流替换标准输出:: 95 | 96 | import sys 97 | sys.stdout = sys.stderr 98 | 99 | **问题:** 访问资源时出现IO错误 100 | 你的应用程序可能是一个单独的.py文件通过快捷方式(符号链接)指向site-packages目录。要知道这样是不行的,要修复这个错误你必须将此目录放到这个文件实际所在的python路径,或者将你的应用程序转成一个包。 101 | 102 | 这么做的理由是对于没有安装过的包来说,filename模块用户定位资源,而对于快捷方式(符号链接)来说就等于调用了错误的filename模块。 103 | 104 | 自动重载的支持 105 | ------------------------------- 106 | 107 | 为了部署更加方便,你可以启用自动重载功能。就是说在 `.wsgi` 文件内有任意变更, `mod_wsgi` 就会为我们重载所有的后台进程。 108 | 109 | 关于这个的实现,只需要在你的 `Directory` 配置块内加入以下指令即可: 110 | 111 | .. sourcecode:: apache 112 | 113 | WSGIScriptReloading On 114 | 115 | 在虚拟环境下工作 116 | --------------------------------- 117 | 118 | 虚拟环境有很明显的优点。比如你无需在整个系统环境下配置所有的依赖且只能用同一个版本,虚拟环境可以在任何地方随心所欲的作环境的版本控制。不过如果你需要在mod_wsgi配合下使用虚拟环境,你必须对 `.wsgi` 作些许的修改。 119 | 120 | 在你的 `.wsgi` 文件的顶部插入如下行:: 121 | 122 | activate_this = '/path/to/env/bin/activate_this.py' 123 | execfile(activate_this, dict(__file__=activate_this)) 124 | 125 | 这两句话根据设置的虚拟环境建立了要载入的路径。要谨记这里必须要用绝对地址。 126 | -------------------------------------------------------------------------------- /docs/deploying/others.rst: -------------------------------------------------------------------------------- 1 | .. _deploying-other-servers: 2 | 3 | 其他服务器 4 | ============= 5 | 6 | 现在还有许多十分流行的由Python编写的服务器可以执行WSGI应用。这些服务器在执行时会独立运行;你可以在你的web服务器上设置反向代理来使用它们。 7 | 8 | Tornado 9 | -------- 10 | 11 | `Tornado`_ 是一个可扩展的,non-blocking(非阻塞)的开源服务器和工具,且提供了 `FriendFeed`_ 。因为它是non-blocking的且使用了epoll,它可以同时处理几千个连接,这意味着它是一个理想的实时web服务器。将它与Flask集成更是小菜一碟:: 12 | 13 | from tornado.wsgi import WSGIContainer 14 | from tornado.httpserver import HTTPServer 15 | from tornado.ioloop import IOLoop 16 | from yourapplication import app 17 | 18 | http_server = HTTPServer(WSGIContainer(app)) 19 | http_server.listen(5000) 20 | IOLoop.instance().start() 21 | 22 | 23 | .. _Tornado: http://www.tornadoweb.org/ 24 | .. _FriendFeed: http://friendfeed.com/ 25 | 26 | Gevent 27 | ------- 28 | 29 | `Gevent`_ 是一个基于协程的Python网络类库,它使用了 `greenlet`_ 来提供在 `libevent`_ 事件循环顶端的高级别异步API:: 30 | 31 | from gevent.wsgi import WSGIServer 32 | from yourapplication import app 33 | 34 | http_server = WSGIServer(('', 5000), app) 35 | http_server.serve_forever() 36 | 37 | .. _Gevent: http://www.gevent.org/ 38 | .. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html 39 | .. _libevent: http://monkey.org/~provos/libevent/ 40 | 41 | Gunicorn 42 | -------- 43 | 44 | `Gunicorn`_ 'Green Unicorn' 是一个UNIX下的WSGI HTTP服务器。它是一个早期从Ruby的Unicorn项目分流出来的项目。它同时支持 `eventlet`_ 和 `greenlet`_ 。在该服务器上运行Flask应用也是非常简单的:: 45 | 46 | gunicorn myproject:app 47 | 48 | `Gunicorn`_ 提供了许多命令行的操作方式 -- 请查看 ``gunicorn -h`` 。举个例子,使用4个工作进程来执行Flask应用 (``-w4``) 绑定在localhost的4000端口上 (``-b 127.0.0.1:4000``):: 49 | 50 | gunicorn -w 4 -b 127.0.0.1:4000 myproject:app 51 | 52 | .. _Gunicorn: http://gunicorn.org/ 53 | .. _eventlet: http://eventlet.net/ 54 | .. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html 55 | 56 | 反向代理设置 57 | ------------ 58 | 59 | 如果你将你的应用部署在上述其中一个服务器上且放置在某个已设置反向代理的HTTP服务器后面,你需要重写一些头来让你的应用正常工作。通常在WSGI环境下会导致问题产生的值是 `REMOTE_ADDR` 和 `HTTP_HOST` 。Werkzeug已经搭载了一些常见设置的解决方法,但是你可能希望为某些特殊的情况自己写WSGI中间件。 60 | 61 | 这种情况下一般要设定的值是 `X-Forwarded-Host` 转发到哪里和 `X-Forwarded-For` 从哪里转发的:: 62 | 63 | from werkzeug.contrib.fixers import ProxyFix 64 | app.wsgi_app = ProxyFix(app.wsgi_app) 65 | 66 | 请记住在无反向代理的设置下使用这样一个中间件会造成一定的安全隐患,因为会信任那些可能由恶意用户伪造传入的头。 67 | 68 | 如果你需要重写头,你可能需要使用如下方法:: 69 | 70 | class CustomProxyFix(object): 71 | 72 | def __init__(self, app): 73 | self.app = app 74 | 75 | def __call__(self, environ, start_response): 76 | host = environ.get('HTTP_X_FHOST', '') 77 | if host: 78 | environ['HTTP_HOST'] = host 79 | return self.app(environ, start_response) 80 | 81 | app.wsgi_app = CustomProxyFix(app.wsgi_app) 82 | -------------------------------------------------------------------------------- /docs/deploying/uwsgi.rst: -------------------------------------------------------------------------------- 1 | .. _deploying-uwsgi: 2 | 3 | uWSGI 4 | ===== 5 | 6 | uWSGI是一架设在诸如 `nginx`_, `lighttpd`_, 还有 `cherokee`_ 等服务器上的部署方式,请查阅 :ref:`deploying-fastcgi` 和 :ref:`deploying-other-servers` 获取关于上述方式的详细内容。在将你的WSGI应用架设到上述服务器上之前,你还需要一个uWSGI服务器。uWSGI是一个应用服务器,同时也是一个协议服务器;它能够搭载uWSGI, FastCGI, 和 HTTP协议。 7 | 8 | 目前最流行的uWSGI服务器是 `uwsgi`_ ,我们将在本向导里使用它。请确认将其安装在环境内。 9 | 10 | .. admonition:: 注意 11 | 12 | 请事先确认你应用程序内的所有 ``app.run()`` 调用包含在 ``if __name__ == '__main__':`` 区块内或在一个单独的文件里。 要确认这个的原因是因为这句语句执行后总是会启动一个新的本地WSGI服务器,但在部署到uWSGI的环境中,我们不需要它。 13 | 14 | 用uwsgi执行你的应用 15 | ---------------------------- 16 | 17 | `uwsgi` 被设计于在python模块里找到WSGI调用时进行相关操作。 18 | 19 | 假设在myapp.py里已有一个flask的应用,只要执行如下的命令: 20 | 21 | .. sourcecode:: text 22 | 23 | $ uwsgi -s /tmp/uwsgi.sock --module myapp --callable app 24 | 25 | 或者,你也可以这样: 26 | 27 | .. sourcecode:: text 28 | 29 | $ uwsgi -s /tmp/uwsgi.sock myapp:app 30 | 31 | 配置 nginx 32 | ----------------- 33 | 34 | 基本的基于nginx的flask uWSGI配置如下:: 35 | 36 | location = /yourapplication { rewrite ^ /yourapplication/; } 37 | location /yourapplication { try_files $uri @yourapplication; } 38 | location @yourapplication { 39 | include uwsgi_params; 40 | uwsgi_param SCRIPT_NAME /yourapplication; 41 | uwsgi_modifier1 30; 42 | uwsgi_pass unix:/tmp/uwsgi.sock; 43 | } 44 | 45 | 上述配置将要发布的应用绑定到了 `/yourapplication` 路径下。如果你希望将应用配置在根目录执行,这非常方便,因为你只要不要去指定 `SCRIPT_NAME` 和设置uwsgi modifier就可以了:: 46 | 47 | location / { try_files $uri @yourapplication; } 48 | location @yourapplication { 49 | include uwsgi_params; 50 | uwsgi_pass unix:/tmp/uwsgi.sock; 51 | } 52 | 53 | .. _nginx: http://nginx.org/ 54 | .. _lighttpd: http://www.lighttpd.net/ 55 | .. _cherokee: http://www.cherokee-project.com/ 56 | .. _uwsgi: http://projects.unbit.it/uwsgi/ 57 | -------------------------------------------------------------------------------- /docs/design.rst: -------------------------------------------------------------------------------- 1 | .. _design: 2 | 3 | Design Decisions in Flask 4 | ========================= 5 | 6 | If you are curious why Flask does certain things the way it does and not 7 | differently, this section is for you. This should give you an idea about 8 | some of the design decisions that may appear arbitrary and surprising at 9 | first, especially in direct comparison with other frameworks. 10 | 11 | 12 | The Explicit Application Object 13 | ------------------------------- 14 | 15 | A Python web application based on WSGI has to have one central callable 16 | object that implements the actual application. In Flask this is an 17 | instance of the :class:`~flask.Flask` class. Each Flask application has 18 | to create an instance of this class itself and pass it the name of the 19 | module, but why can't Flask do that itself? 20 | 21 | Without such an explicit application object the following code:: 22 | 23 | from flask import Flask 24 | app = Flask(__name__) 25 | 26 | @app.route('/') 27 | def index(): 28 | return 'Hello World!' 29 | 30 | Would look like this instead:: 31 | 32 | from hypothetical_flask import route 33 | 34 | @route('/') 35 | def index(): 36 | return 'Hello World!' 37 | 38 | There are three major reasons for this. The most important one is that 39 | implicit application objects require that there may only be one instance at 40 | the time. There are ways to fake multiple applications with a single 41 | application object, like maintaining a stack of applications, but this 42 | causes some problems I won't outline here in detail. Now the question is: 43 | when does a microframework need more than one application at the same 44 | time? A good example for this is unittesting. When you want to test 45 | something it can be very helpful to create a minimal application to test 46 | specific behavior. When the application object is deleted everything it 47 | allocated will be freed again. 48 | 49 | Another thing that becomes possible when you have an explicit object lying 50 | around in your code is that you can subclass the base class 51 | (:class:`~flask.Flask`) to alter specific behaviour. This would not be 52 | possible without hacks if the object were created ahead of time for you 53 | based on a class that is not exposed to you. 54 | 55 | But there is another very important reason why Flask depends on an 56 | explicit instantiation of that class: the package name. Whenever you 57 | create a Flask instance you usually pass it `__name__` as package name. 58 | Flask depends on that information to properly load resources relative 59 | to your module. With Python's outstanding support for reflection it can 60 | then access the package to figure out where the templates and static files 61 | are stored (see :meth:`~flask.Flask.open_resource`). Now obviously there 62 | are frameworks around that do not need any configuration and will still be 63 | able to load templates relative to your application module. But they have 64 | to use the current working directory for that, which is a very unreliable 65 | way to determine where the application is. The current working directory 66 | is process-wide and if you are running multiple applications in one 67 | process (which could happen in a webserver without you knowing) the paths 68 | will be off. Worse: many webservers do not set the working directory to 69 | the directory of your application but to the document root which does not 70 | have to be the same folder. 71 | 72 | The third reason is "explicit is better than implicit". That object is 73 | your WSGI application, you don't have to remember anything else. If you 74 | want to apply a WSGI middleware, just wrap it and you're done (though 75 | there are better ways to do that so that you do not lose the reference 76 | to the application object :meth:`~flask.Flask.wsgi_app`). 77 | 78 | Furthermore this design makes it possible to use a factory function to 79 | create the application which is very helpful for unittesting and similar 80 | things (:ref:`app-factories`). 81 | 82 | One Template Engine 83 | ------------------- 84 | 85 | Flask decides on one template engine: Jinja2. Why doesn't Flask have a 86 | pluggable template engine interface? You can obviously use a different 87 | template engine, but Flask will still configure Jinja2 for you. While 88 | that limitation that Jinja2 is *always* configured will probably go away, 89 | the decision to bundle one template engine and use that will not. 90 | 91 | Template engines are like programming languages and each of those engines 92 | has a certain understanding about how things work. On the surface they 93 | all work the same: you tell the engine to evaluate a template with a set 94 | of variables and take the return value as string. 95 | 96 | But that's about where similarities end. Jinja2 for example has an 97 | extensive filter system, a certain way to do template inheritance, support 98 | for reusable blocks (macros) that can be used from inside templates and 99 | also from Python code, uses Unicode for all operations, supports 100 | iterative template rendering, configurable syntax and more. On the other 101 | hand an engine like Genshi is based on XML stream evaluation, template 102 | inheritance by taking the availability of XPath into account and more. 103 | Mako on the other hand treats templates similar to Python modules. 104 | 105 | When it comes to connecting a template engine with an application or 106 | framework there is more than just rendering templates. For instance, 107 | Flask uses Jinja2's extensive autoescaping support. Also it provides 108 | ways to access macros from Jinja2 templates. 109 | 110 | A template abstraction layer that would not take the unique features of 111 | the template engines away is a science on its own and a too large 112 | undertaking for a microframework like Flask. 113 | 114 | Furthermore extensions can then easily depend on one template language 115 | being present. You can easily use your own templating language, but an 116 | extension could still depend on Jinja itself. 117 | 118 | 119 | Micro with Dependencies 120 | ----------------------- 121 | 122 | Why does Flask call itself a microframework and yet it depends on two 123 | libraries (namely Werkzeug and Jinja2). Why shouldn't it? If we look 124 | over to the Ruby side of web development there we have a protocol very 125 | similar to WSGI. Just that it's called Rack there, but besides that it 126 | looks very much like a WSGI rendition for Ruby. But nearly all 127 | applications in Ruby land do not work with Rack directly, but on top of a 128 | library with the same name. This Rack library has two equivalents in 129 | Python: WebOb (formerly Paste) and Werkzeug. Paste is still around but 130 | from my understanding it's sort of deprecated in favour of WebOb. The 131 | development of WebOb and Werkzeug started side by side with similar ideas 132 | in mind: be a good implementation of WSGI for other applications to take 133 | advantage. 134 | 135 | Flask is a framework that takes advantage of the work already done by 136 | Werkzeug to properly interface WSGI (which can be a complex task at 137 | times). Thanks to recent developments in the Python package 138 | infrastructure, packages with dependencies are no longer an issue and 139 | there are very few reasons against having libraries that depend on others. 140 | 141 | 142 | Thread Locals 143 | ------------- 144 | 145 | Flask uses thread local objects (context local objects in fact, they 146 | support greenlet contexts as well) for request, session and an extra 147 | object you can put your own things on (:data:`~flask.g`). Why is that and 148 | isn't that a bad idea? 149 | 150 | Yes it is usually not such a bright idea to use thread locals. They cause 151 | troubles for servers that are not based on the concept of threads and make 152 | large applications harder to maintain. However Flask is just not designed 153 | for large applications or asynchronous servers. Flask wants to make it 154 | quick and easy to write a traditional web application. 155 | 156 | Also see the :ref:`becomingbig` section of the documentation for some 157 | inspiration for larger applications based on Flask. 158 | 159 | 160 | What Flask is, What Flask is Not 161 | -------------------------------- 162 | 163 | Flask will never have a database layer. It will not have a form library 164 | or anything else in that direction. Flask itself just bridges to Werkzeug 165 | to implement a proper WSGI application and to Jinja2 to handle templating. 166 | It also binds to a few common standard library packages such as logging. 167 | Everything else is up for extensions. 168 | 169 | Why is this the case? Because people have different preferences and 170 | requirements and Flask could not meet those if it would force any of this 171 | into the core. The majority of web applications will need a template 172 | engine in some sort. However not every application needs a SQL database. 173 | 174 | The idea of Flask is to build a good foundation for all applications. 175 | Everything else is up to you or extensions. 176 | -------------------------------------------------------------------------------- /docs/errorhandling.rst: -------------------------------------------------------------------------------- 1 | .. _application-errors: 2 | 3 | 处理应用异常 4 | ============== 5 | :译者: plucury#gmail.com 6 | 7 | .. versionadded:: 0.3 8 | 9 | 应用程序处理失败,服务器处理失败。在你的产品中这些异常迟早会暴露出来,即使你的代码是完全正确的, 10 | 你仍然会一次次的面对这些异常。原因?因为所有的一切都有可能失败。在以下的几种情况中,完美的代码 11 | 却导致了服务器的错误: 12 | 13 | - 当应用系统正在读取传入的数据时,客户端过早的结束了请求。 14 | - 数据库超过负荷,无法处理查询请求。 15 | - 文件系统没有空间了。 16 | - 硬盘挂了。 17 | - 终端服务器超过负荷。 18 | - 你所使用的代码库中存在编程错误。 19 | - 服务器与其他系统的网络连接中断了。 20 | 21 | 而这只是你所要面对的问题中一些最简单的例子。那我们将如何来解决这些问题呢?在默认的情况下,你的 22 | 应用程序在生产模式下运行,Flask将显示一个十分简单的页面并记录这些异常通过使用 :attr:`~flask.Flask.logger` . 23 | 24 | 但是你可以做得更多,并且我们将会讨论几种更好的方案来处理这些异常。 25 | 26 | 报错邮件 27 | --------- 28 | 如果应用程序以生产模式运行(通常在服务器上你会这么做),在默认情况下你不会看见任何的日志信息。 29 | 这是为什么呢?因为Flask是一个零配置框架,而如果没有配置的话,框架又应该把日志文件放到哪里去 30 | 呢?依靠假设并不是一个很好的方法,因为总是会存在各种不同的可能,也许那个我们假设放置日志的地方 31 | 用户并没有权限访问。另外,对于大多数小型的应用程序来说也不会有人去关注他的日志。 32 | 33 | 实际上,我可以向你保证即使你为你的程序配置了放置错误信息的日志文件,你也永远不会去查看他,除非 34 | 当你的用户向你报告了一个事件而你需要去排查错误的时候。你所需要的只是,当异常第二次发生时接收到 35 | 一封报警邮件,然后你在针对其中的情况进行处理。 36 | 37 | Flask使用了python内置的日志系统,并且他会在你需要是向你发生关于异常的邮件。这里是一个关于如何 38 | 配置Flask的日志以向你发送异常邮件的例子:: 39 | 40 | 41 | ADMINS = ['yourname@example.com'] 42 | if not app.debug: 43 | import logging 44 | from logging.handlers import SMTPHandler 45 | mail_handler = SMTPHandler('127.0.0.1', 46 | 'server-error@example.com', 47 | ADMINS, 'YourApplication Failed') 48 | mail_handler.setLevel(logging.ERROR) 49 | app.logger.addHandler(mail_handler) 50 | 51 | 这是如何操作的呢?我们创建了一个新的类 :class:`~logging.handlers.SMTPHandler` ,他 52 | 将通过 ``127.0.0.1`` 的邮件服务器向所有的 `ADMINS` 用户发送标题为“YourApplication Failed” 53 | 邮件,并且将发件地址配置为 *server-error@example.com* 。此外,我们还提供了对 54 | 需要证书的邮件服务器的支持,关于这部分的文档,请查看 :class:`~logging.handlers.SMTPHandler` 。 55 | 56 | 邮件处理器只会发送异常和错误的信息,因为我们并不希望通过邮件获取警告信息或其他一些处理过程中 57 | 产生的没有用的日志。 58 | 59 | 当你在产品中使用它们的时候,请务必查看 :ref:`logformat` 以使得报错邮件中包含更多的信息。这 60 | 些信息将为你解决很多的烦恼。 61 | 62 | 日志文件 63 | --------- 64 | 即使你已经有了报错邮件,你可能仍然希望能够查看到警告信息。为了排查问题,尽可能的保存更多的 65 | 信息不失为一个好主意。请注意,Flask的系统核心本身并不会去记录任何警告信息,因此编写记录那 66 | 些看起来不对劲的地方的代码将是你的责任。 67 | 68 | 这里提供了几个处理类,但对于基本的记录错误日志而言他们并不是总是那么的有用。而其中最值得我们 69 | 注意的是以下几项: 70 | 71 | - :class:`~logging.handlers.FileHandler` - 将日志信息写入文件系统中 72 | 73 | - :class:`~logging.handlers.RotatingFileHandler` - 将日志信息写入文件系统中,并且 74 | 当日志达到一定数量时会滚动记录最新的信息。 75 | 76 | - :class:`~logging.handlers.NTEventLogHandler` - 将日志发送到windows系统的日 77 | 志事件中。如果你的系统部署在windows环境中,那么这正是你想要的。 78 | 79 | - :class:`~logging.handlers.SysLogHandler` - 将日志发送到UNIX的系统日志中。 80 | 81 | 一旦你选择了你的日志处理类,你就可以向上文中配置SMTP处理类一样的来配置它们,唯一需要注意的 82 | 是使用更低级别的设置(我这里使用的是 `WARNING` ):: 83 | 84 | if not app.debug: 85 | import logging 86 | from logging.handlers import TheHandlerYouWant 87 | file_handler = TheHandlerYouWant(...) 88 | file_handler.setLevel(logging.WARNING) 89 | app.logger.addHandler(file_handler) 90 | 91 | .. _logformat: 92 | 93 | 日志格式 94 | -------- 95 | 在默认情况下,处理器只会将日志信息写入文件或是用邮件发送给你。而日志应该记录更多的信息,你必须 96 | 配置你的日志,使它能够让你更方便的知道发生了什么样的错误,以及更重要的是告诉你哪里发生了错误。 97 | 98 | 格式处理器(formatter)可以让你获取格式化的字符串。你需要知道是日志的连接是自动进行的,你不需要 99 | 将它包含在格式处理器的格式化字符串中。 100 | 101 | 这里有几个例子: 102 | 103 | 电子邮件 104 | ```````` 105 | 106 | :: 107 | 108 | from logging import Formatter 109 | mail_handler.setFormatter(Formatter(''' 110 | Message type: %(levelname)s 111 | Location: %(pathname)s:%(lineno)d 112 | Module: %(module)s 113 | Function: %(funcName)s 114 | Time: %(asctime)s 115 | 116 | Message: 117 | 118 | %(message)s 119 | ''')) 120 | 121 | 日志文件 122 | ````````` 123 | 124 | :: 125 | 126 | from logging import Formatter 127 | file_handler.setFormatter(Formatter( 128 | '%(asctime)s %(levelname)s: %(message)s ' 129 | '[in %(pathname)s:%(lineno)d]' 130 | )) 131 | 132 | 复杂的日志格式 133 | `````````````` 134 | 135 | 这里是一系列用户格式化字符串的变量。这里的列表并不完整,你可以通过查看官方文档的 :mod:`logging` 136 | 部分来获取完整的列表。 137 | 138 | .. tabularcolumns:: |p{3cm}|p{12cm}| 139 | 140 | +-------------------+----------------------------------------------------+ 141 | | 格式 | 描述 | 142 | +===================+====================================================+ 143 | | ``%(levelname)s`` | 日志等级。 | 144 | | | (``'DEBUG'``, ``'INFO'``, ``'WARNING'``, | 145 | | | ``'ERROR'``, ``'CRITICAL'``). | 146 | +-------------------+----------------------------------------------------+ 147 | | ``%(pathname)s`` | 调用日志的源文件的全路径(如果可用) | 148 | +-------------------+----------------------------------------------------+ 149 | | ``%(filename)s`` | 文件名。 | 150 | +-------------------+----------------------------------------------------+ 151 | | ``%(module)s`` | 模块名。 | 152 | +-------------------+----------------------------------------------------+ 153 | | ``%(funcName)s`` | 方法名。 | 154 | +-------------------+----------------------------------------------------+ 155 | | ``%(lineno)d`` | 调用日志的代码所在源文件中的行号。(如果可用) | 156 | +-------------------+----------------------------------------------------+ 157 | | ``%(asctime)s`` | 创建日志的可阅读时间。默认的格式是 | 158 | | | ``"2003-07-08 16:49:45,896"`` (逗号后的 | 159 | | | 时间是毫秒)。可以通过复写 | 160 | | | :meth:`~logging.Formatter.formatTime` 方法来修改它 | 161 | +-------------------+----------------------------------------------------+ 162 | | ``%(message)s`` | 日志信息。同 ``msg % args`` | 163 | +-------------------+----------------------------------------------------+ 164 | 165 | 如果你需要更多的定制化格式,你可以实现格式处理器(formatter)的子类。它有以下三个有趣的方法: 166 | 167 | :meth:`~logging.Formatter.format`: 168 | 处理实际的格式。它需要接收一个 :class:`~logging.LogRecord` 对象,并返回一个被 169 | 格式话的字符串。 170 | :meth:`~logging.Formatter.formatTime`: 171 | 调用 `asctime` 进行格式化。如果你需要不同的时间格式,可以复写这个方法。 172 | :meth:`~logging.Formatter.formatException` 173 | 调用异常格式化。它接收一个 :attr:`~sys.exc_info` 元组并返回一个字符串。通常它会很好 174 | 的运行,你并不需要复写它。 175 | 176 | 获取更多的信息,请查看官方文档。 177 | 178 | 179 | 其他代码库 180 | ----------- 181 | 目前为止,我们只配置了你的程序自身的日志。而其他的代码库同样可以需要记录日志。比如,SQLAlchemy 182 | 使用了很多日志。使用 :mod:`logging` 包可以一次性的配置所有的日志,当我并不推荐那样做。因为当 183 | 多个程序在同一个Python解释器上运行是,你将无法单独的对他们进行配置。 184 | 185 | 相对的,我推荐你只对你所关注的日志进行配置,通过 :func:`~logging.getLogger` 方法获取 186 | 所有的日志处理器,并通过迭代获取他们:: 187 | 188 | from logging import getLogger 189 | loggers = [app.logger, getLogger('sqlalchemy'), 190 | getLogger('otherlibrary')] 191 | for logger in loggers: 192 | logger.addHandler(mail_handler) 193 | logger.addHandler(file_handler) 194 | -------------------------------------------------------------------------------- /docs/flaskext.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import Keyword, Name, Comment, String, Error, \ 4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal 5 | 6 | 7 | class FlaskyStyle(Style): 8 | background_color = "#f8f8f8" 9 | default_style = "" 10 | 11 | styles = { 12 | # No corresponding class for the following: 13 | #Text: "", # class: '' 14 | Whitespace: "underline #f8f8f8", # class: 'w' 15 | Error: "#a40000 border:#ef2929", # class: 'err' 16 | Other: "#000000", # class 'x' 17 | 18 | Comment: "italic #8f5902", # class: 'c' 19 | Comment.Preproc: "noitalic", # class: 'cp' 20 | 21 | Keyword: "bold #004461", # class: 'k' 22 | Keyword.Constant: "bold #004461", # class: 'kc' 23 | Keyword.Declaration: "bold #004461", # class: 'kd' 24 | Keyword.Namespace: "bold #004461", # class: 'kn' 25 | Keyword.Pseudo: "bold #004461", # class: 'kp' 26 | Keyword.Reserved: "bold #004461", # class: 'kr' 27 | Keyword.Type: "bold #004461", # class: 'kt' 28 | 29 | Operator: "#582800", # class: 'o' 30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 31 | 32 | Punctuation: "bold #000000", # class: 'p' 33 | 34 | # because special names such as Name.Class, Name.Function, etc. 35 | # are not recognized as such later in the parsing, we choose them 36 | # to look the same as ordinary variables. 37 | Name: "#000000", # class: 'n' 38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 39 | Name.Builtin: "#004461", # class: 'nb' 40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 41 | Name.Class: "#000000", # class: 'nc' - to be revised 42 | Name.Constant: "#000000", # class: 'no' - to be revised 43 | Name.Decorator: "#888", # class: 'nd' - to be revised 44 | Name.Entity: "#ce5c00", # class: 'ni' 45 | Name.Exception: "bold #cc0000", # class: 'ne' 46 | Name.Function: "#000000", # class: 'nf' 47 | Name.Property: "#000000", # class: 'py' 48 | Name.Label: "#f57900", # class: 'nl' 49 | Name.Namespace: "#000000", # class: 'nn' - to be revised 50 | Name.Other: "#000000", # class: 'nx' 51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 52 | Name.Variable: "#000000", # class: 'nv' - to be revised 53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 56 | 57 | Number: "#990000", # class: 'm' 58 | 59 | Literal: "#000000", # class: 'l' 60 | Literal.Date: "#000000", # class: 'ld' 61 | 62 | String: "#4e9a06", # class: 's' 63 | String.Backtick: "#4e9a06", # class: 'sb' 64 | String.Char: "#4e9a06", # class: 'sc' 65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 66 | String.Double: "#4e9a06", # class: 's2' 67 | String.Escape: "#4e9a06", # class: 'se' 68 | String.Heredoc: "#4e9a06", # class: 'sh' 69 | String.Interpol: "#4e9a06", # class: 'si' 70 | String.Other: "#4e9a06", # class: 'sx' 71 | String.Regex: "#4e9a06", # class: 'sr' 72 | String.Single: "#4e9a06", # class: 's1' 73 | String.Symbol: "#4e9a06", # class: 'ss' 74 | 75 | Generic: "#000000", # class: 'g' 76 | Generic.Deleted: "#a40000", # class: 'gd' 77 | Generic.Emph: "italic #000000", # class: 'ge' 78 | Generic.Error: "#ef2929", # class: 'gr' 79 | Generic.Heading: "bold #000080", # class: 'gh' 80 | Generic.Inserted: "#00A000", # class: 'gi' 81 | Generic.Output: "#888", # class: 'go' 82 | Generic.Prompt: "#745334", # class: 'gp' 83 | Generic.Strong: "bold #000000", # class: 'gs' 84 | Generic.Subheading: "bold #800080", # class: 'gu' 85 | Generic.Traceback: "bold #a40000", # class: 'gt' 86 | } 87 | -------------------------------------------------------------------------------- /docs/flaskstyle.sty: -------------------------------------------------------------------------------- 1 | \definecolor{TitleColor}{rgb}{0,0,0} 2 | \definecolor{InnerLinkColor}{rgb}{0,0,0} 3 | 4 | \renewcommand{\maketitle}{% 5 | \begin{titlepage}% 6 | \let\footnotesize\small 7 | \let\footnoterule\relax 8 | \ifsphinxpdfoutput 9 | \begingroup 10 | % This \def is required to deal with multi-line authors; it 11 | % changes \\ to ', ' (comma-space), making it pass muster for 12 | % generating document info in the PDF file. 13 | \def\\{, } 14 | \pdfinfo{ 15 | /Author (\@author) 16 | /Title (\@title) 17 | } 18 | \endgroup 19 | \fi 20 | \begin{flushright}% 21 | %\sphinxlogo% 22 | {\center 23 | \vspace*{3cm} 24 | \includegraphics{logo.pdf} 25 | \vspace{3cm} 26 | \par 27 | {\rm\Huge \@title \par}% 28 | {\em\LARGE \py@release\releaseinfo \par} 29 | {\large 30 | \@date \par 31 | \py@authoraddress \par 32 | }}% 33 | \end{flushright}%\par 34 | \@thanks 35 | \end{titlepage}% 36 | \cleardoublepage% 37 | \setcounter{footnote}{0}% 38 | \let\thanks\relax\let\maketitle\relax 39 | %\gdef\@thanks{}\gdef\@author{}\gdef\@title{} 40 | } 41 | 42 | \fancypagestyle{normal}{ 43 | \fancyhf{} 44 | \fancyfoot[LE,RO]{{\thepage}} 45 | \fancyfoot[LO]{{\nouppercase{\rightmark}}} 46 | \fancyfoot[RE]{{\nouppercase{\leftmark}}} 47 | \fancyhead[LE,RO]{{ \@title, \py@release}} 48 | \renewcommand{\headrulewidth}{0.4pt} 49 | \renewcommand{\footrulewidth}{0.4pt} 50 | } 51 | 52 | \fancypagestyle{plain}{ 53 | \fancyhf{} 54 | \fancyfoot[LE,RO]{{\thepage}} 55 | \renewcommand{\headrulewidth}{0pt} 56 | \renewcommand{\footrulewidth}{0.4pt} 57 | } 58 | 59 | \titleformat{\section}{\Large}% 60 | {\py@TitleColor\thesection}{0.5em}{\py@TitleColor}{\py@NormalColor} 61 | \titleformat{\subsection}{\large}% 62 | {\py@TitleColor\thesubsection}{0.5em}{\py@TitleColor}{\py@NormalColor} 63 | \titleformat{\subsubsection}{}% 64 | {\py@TitleColor\thesubsubsection}{0.5em}{\py@TitleColor}{\py@NormalColor} 65 | \titleformat{\paragraph}{\large}% 66 | {\py@TitleColor}{0em}{\py@TitleColor}{\py@NormalColor} 67 | 68 | \ChNameVar{\raggedleft\normalsize} 69 | \ChNumVar{\raggedleft \bfseries\Large} 70 | \ChTitleVar{\raggedleft \rm\Huge} 71 | 72 | \renewcommand\thepart{\@Roman\c@part} 73 | \renewcommand\part{% 74 | \pagestyle{empty} 75 | \if@noskipsec \leavevmode \fi 76 | \cleardoublepage 77 | \vspace*{6cm}% 78 | \@afterindentfalse 79 | \secdef\@part\@spart} 80 | 81 | \def\@part[#1]#2{% 82 | \ifnum \c@secnumdepth >\m@ne 83 | \refstepcounter{part}% 84 | \addcontentsline{toc}{part}{\thepart\hspace{1em}#1}% 85 | \else 86 | \addcontentsline{toc}{part}{#1}% 87 | \fi 88 | {\parindent \z@ %\center 89 | \interlinepenalty \@M 90 | \normalfont 91 | \ifnum \c@secnumdepth >\m@ne 92 | \rm\Large \partname~\thepart 93 | \par\nobreak 94 | \fi 95 | \MakeUppercase{\rm\Huge #2}% 96 | \markboth{}{}\par}% 97 | \nobreak 98 | \vskip 8ex 99 | \@afterheading} 100 | \def\@spart#1{% 101 | {\parindent \z@ %\center 102 | \interlinepenalty \@M 103 | \normalfont 104 | \huge \bfseries #1\par}% 105 | \nobreak 106 | \vskip 3ex 107 | \@afterheading} 108 | 109 | % use inconsolata font 110 | \usepackage{inconsolata} 111 | 112 | % fix single quotes, for inconsolata. (does not work) 113 | %%\usepackage{textcomp} 114 | %%\begingroup 115 | %% \catcode`'=\active 116 | %% \g@addto@macro\@noligs{\let'\textsinglequote} 117 | %% \endgroup 118 | %%\endinput 119 | -------------------------------------------------------------------------------- /docs/foreword.rst: -------------------------------------------------------------------------------- 1 | 前言 2 | ======== 3 | :译者: suxindichen@douban 4 | 5 | 请在你开始Flask之前读一下这篇文章,希望它回答你一些关于这个项目的初衷和目标 6 | ,以及何时该用此框架何时不该用。 7 | 8 | 9 | "micro"是什么意思? 10 | ----------------------- 11 | 12 | 对我来说,microframework 中的“micro”并不仅仅意味着框架的简单性和轻量性,还意 13 | 味着明显的复杂度限制和用框架所写出的应用的大小。即使只用一个python文件构成 14 | 一个应用也是事实。为了变得平易近人和简洁,一个microframwork会选择牺牲一些 15 | 可能对于大型或者更加复杂的应用来说必不可少的功能。 16 | 17 | 例如,flask使用内部的本地线程对象从而使你不用为了线程安全而在一个请求中来回 18 | 的在方法和方法之间传递对象。虽然这是一个简单的方法并且节省了你很多的时间, 19 | 但是它可能也会在非常大的应用中引发一些问题因为这些本地线程对象可能会在同一 20 | 线程的任何地方发生变化。 21 | 22 | Flask 提供了一些工具来处理着这种方法的缺点,但是它更多的只是一种理论对于更 23 | 大的应用来说,因为理论上本地县城对象会在同一线程的任何地方被修改。 24 | 25 | Flask也是基于配置的,也就是说许多东西会被预先配置。比如说,按照惯例,模板和 26 | 静态文件会在应用程序的python资源树下的子目录中。 27 | 28 | Flask被称为”microframework”的主要原因是保持核心简单和可扩展性的理念。没有 29 | 数据库抽象层,没有表单验证以及其他的任何已经存在的可以处理它的不同库。但是 30 | flask知道扩展的概念。这样你就可以在你的应用程序中添加这些功能,如果它被引用 31 | 的话。目前已经有对象关系映射器,表单验证,上传处理,各种开放认证技术等等之类 32 | 的扩展。 33 | 34 | 35 | 然而flask并没有许多的代码,并且它基于一个非常坚固的基础,这样它就可以非常容 36 | 易的去适应大型的应用。如果你有兴趣,请查阅 :ref:`becomingbig` 章节。   37 | 38 | 如果你对flask的设计原则感到好奇,请转向 :ref:`design` 章节。 39 | 40 | 41 | 一个框架和一个例子 42 | -------------------------- 43 | 44 | Flask 并不仅仅是一个微型框架;它同时也是一个例子。基于Flask,会有一系列的博 45 | 文来阐述如何创建一个框架。Flask自己就是一种在现有库的基础上实现一个框架的方 46 | 式。不像大部分的其他微型框架,Flask不会试着实现它自己的任何东西,它直接使用 47 | 已经存在的代码。 48 | 49 | 50 | Web开发是危险的 51 | ---------------------------- 52 | 53 | 我并不是在开玩笑。好吧,也许有一小点。如果你写一个web应用,你可能允许用户注册 54 | ,并且允许他们在你的服务器上留下数据。用户把数据委托给了你。即使你是唯一可能 55 | 在你的应用中留下数据的用户,你也会希望那些数据安全的存储起来。 56 | 57 | 58 | 不幸的是,有很多中方法去损害一个Web应用程序的安全性。Flask可以防止一种现代Web 59 | 应用中最常见的安全问题:cross-site scripting(XSS).除非你故意的把不安全的HTML 60 | 标记为安全的,Flask和潜在的Jinja2模板引擎会把你覆盖掉。但是引发安全问题的方式 61 | 还有很多。 62 | 63 | 这篇文档是要提醒你注意Web开发中需要注意安全问题的方面。这些安全忧患中的一些远 64 | 远比人所想到的复杂的多,我们有时候会低估了一个漏洞会被利用的可能性,直到一个聪 65 | 明的攻击者指出一种方式来溢出我们的应用。 66 | 67 | 别以为你的应用程序还没有重要到吸引攻击者的程度, 依靠这种攻击方式,可能会有一些 68 | 自动机器人在想方设法在你的数据库中填入垃圾邮件,恶意软件的链接,等等。 69 | 70 | 71 | Python 3的状态 72 | ---------------------- 73 | 74 | 目前Python社区正处在一个提高库对Python编程语言的新的迭代过程的支持。不幸的是, 75 | Python3 中还有一些问题,像缺少对Python3应该采用哪种WSGI方式的决策。这些问题有 76 | 部分原因是Python中的未经审核的变化;一部分原因是每个人都希望参与推动WSGI标准的 77 | 野心。 78 | 79 | 正因为如此,我们推荐不要使用Python3来开发任何Web应用直到WSGI的问题得到解决。 80 | 你会发现一部分框架和Web库宣称支持 Python3,但是它们的支持是基于Python3和 81 | Python3.1中过时的WSGI实现,而这个实现很有可能在不久的将来会发生改变。 82 | 83 | 一旦WSGI的问题得到解决,Werkzeug和Flask将会立刻移植到Python3,并且我们会 84 | 提供一些有用的提示来把现存的应用更新到 Python3.在那之前,我们强烈推荐在 85 | 开发过程中使用激活的Python3 警告python2.6和2.7,以及Unicode文字的 86 | `__future__` 功能。 87 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | 欢迎来到Flask 4 | ================ 5 | 6 | .. image:: _static/logo-full.png 7 | :alt: Flask: web development, one drop at a time 8 | :class: floatingflask 9 | 10 | 欢迎来到Flask.本文档分为几个部分.我推荐你从 :ref:`installation` 开始,然 11 | 后跳到 :ref:`quickstart`.除了快速上手外, :ref:`tutorial` 更详细的介绍了怎 12 | 样在Flask中创建一个完整的(依然微小)的应用程序.如果你想更深入的了解Flask 13 | 内部,查看阅 :ref:`api` 文档.在 :ref:`patterns` 章节描述了常见的模式. 14 | 15 | Flask依赖于两个外部的库: `Jinja2`_ 模板引擎和 `Werkzeug`_ WSGI工具包. 16 | 这些库没有在这里列出文档。如果你想深入了解,查看以下链接: 17 | 18 | - `Jinja2 Documentation `_ 19 | - `Werkzeug Documentation `_ 20 | 21 | .. _Jinja2: http://jinja.pocoo.org/2/ 22 | .. _Werkzeug: http://werkzeug.pocoo.org/ 23 | 24 | .. include:: contents.rst.inc 25 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | 安装 4 | ============ 5 | :译者: suxindichen@douban 6 | 7 | Flask依靠两个外部库, `Werkzeug `_ 和 8 | `Jinja2 `_. Werkzeug是一个WSGI的工具包, 9 | 在web应用和多种服务器之间开发和部署的标准的python 接口。Jinja2呈现模板。 10 | 11 | 那么如何快速获得你计算机中的一切?在这个章节中会介绍很多种方式,但是最了 12 | 不起的要数virtualenv,所以我们第一个先说它。 13 | 14 | .. _virtualenv: 15 | 16 | virtualenv 17 | ---------- 18 | 19 | 当你拥有shell访问权限时,virtualenv 可能是你在开发以及生产环境中想要使用的。 20 | 21 | Virtualenv解决了什么问题?如果你像我一样喜欢Python,你可能会不仅想要在基于 22 | Flask的Web应用,还包括一些其他的应用中使用它。但是,你拥有的项目越多,你用 23 | 不同的版本的Python工作的可能性越大,或者至少是不同版本的Python库。面对现实 24 | 吧;库很经常的破坏向后兼容性,而且想要任何大型的(正经的)应用零依赖是不可 25 | 能的。那么当你的两个或多个项目有依赖性冲突的话,你要怎么做? 26 | 27 | Virtualenv来救援!它从根本上实现了多种并排式的python安装。它实际上并没有安 28 | 装Python的单独的副本,但它确实提供了一种巧妙的方式,让不同的项目环境中分离 29 | 出来。 30 | 31 | 那么让我们来看看Virtualenv是如何工作的! 32 | 33 | 如果你是在Mac OS X 或者Linux下,那么下面的两条命令将会适合你:: 34 | 35 | $ sudo easy_install virtualenv 36 | 37 | 或者更好的:: 38 | 39 | $ sudo pip install virtualenv 40 | 41 | 42 | 任意一个都可以在你的系统中安装virtualenv。它甚至可能在你的包管理中。如果你使用 43 | 的是Ubuntu,尝试:: 44 | 45 | $ sudo apt-get install python-virtualenv 46 | 47 | 如果你在Windows平台上并没有 `easy_install` 命令,你首先必须安装它。查阅 48 | :ref:`windows-easy-install` 章节来获得更多如何做的信息。一旦你安装了它, 49 | 运行上述的命令,记得去掉 `sudo` 前缀。 50 | 51 | 一旦你装上了virtualenv,请调出shell然后创建你自己的环境变量。我通常会创建 52 | 一个包含 `env` 文件夹的项目文件夹: 53 | :: 54 | 55 | $ mkdir myproject 56 | $ cd myproject 57 | $ virtualenv env 58 | New python executable in env/bin/python 59 | Installing setuptools............done. 60 | 61 | 62 | 现在,无论何时你想在一个项目上工作,你只需要激活相应的环境。在OS X和Linux上 63 | ,执行以下操作: 64 | :: 65 | 66 | $ . env/bin/activate 67 | 68 | (注意脚本名称和点号之间的空格。该点意味着这个脚本应该运行在当前shell的上下文。 69 | 如果这条命令不能在你的shell中正常工作,请试着把点号替换为 ``source``) 70 | 71 | 如果你是一个Windows用户,下面的命令是为你准备的:: 72 | 73 | $ env\scripts\activate 74 | 75 | 无论哪种方式,现在你应该正在使用你的virtualenv(看看你的shell提示已经更改到 76 | 显示virtualenv) 77 | 78 | 现在你可以键入下面的命令来激活你virtualenv中的Flask:: 79 | 80 | $ easy_install Flask 81 | 82 | 几秒钟后就准备好了。 83 | 84 | 安装到系统全局 85 | ------------------------ 86 | 87 | 这样也可以,但是我确实不推荐它。只需以root权限运行 `easy_install` 88 | :: 89 | 90 | $ sudo easy_install Flask 91 | 92 | (Windows平台下,在管理员Shell下运行,不要 `sudo` ). 93 | 94 | 95 | 生活在边缘 96 | ------------------ 97 | 98 | 如果你想要使用最新版本的Flask,有两种方法:你可以使用 `easy_insall` 拉出开发版本, 99 | 或者让它来操作一个git检索。无论哪种方式,推荐你使用virtualenv。 100 | 101 | 在一个新的Virtualenv中获得git检索,并运行在在开发模式下 :: 102 | 103 | $ git clone http://github.com/mitsuhiko/flask.git 104 | Initialized empty Git repository in ~/dev/flask/.git/ 105 | $ cd flask 106 | $ virtualenv env 107 | $ . env/bin/activate 108 | New python executable in env/bin/python 109 | Installing setuptools............done. 110 | $ python setup.py develop 111 | ... 112 | Finished processing dependencies for Flask 113 | 114 | 这将引入依赖关系和激活Git的头作为在Virtualenv中当前的版本。然后你只需 115 | 要 ``git pull origin`` 来获得最新的版本。 116 | 117 | 如果你不想用git来得到最新的开发版,可以改用下面的命令:: 118 | 119 | $ mkdir flask 120 | $ cd flask 121 | $ virtualenv env 122 | $ . env/bin/activate 123 | New python executable in env/bin/python 124 | Installing setuptools............done. 125 | $ easy_install Flask==dev 126 | ... 127 | Finished processing dependencies for Flask==dev 128 | 129 | .. _windows-easy-install: 130 | 131 | Windows 平台下的 `easy_install` 132 | --------------------------------- 133 | 134 | 在windows上,安装 `easy_install` 是有一点的复杂因为在Windows上比在类Unix系统上 135 | 有一些轻微的不同的规则,但是它并不难。最简单的安装方式是下载 `ez_setup.py`_ 文件 136 | 然后运行它。运行它最简单的方式是进入到你的下载目录中,然后双击这个文件。 137 | 138 | 接着,添加 `easy_install` 命令和其他Python脚本到命令行搜索路径,方法为:添加你 139 | python安装目录中的Scripts文件夹到环境变量 `PATH` 中。添加方法:右键桌面的“我的电脑” 140 | 图标或者开始菜单中的“计算机”,然后选在“属性”。之后,在Vista和Win7下,单击“高级系统 141 | 设置”;在WinXP下,单击“高级”选项。然后,单击“环境变量”按钮,双击“系统变量”中的“path”变量。 142 | 在那里添加你的Python解释器的 Scripts文件夹;确保你使用分号将它与现有的值隔开。 143 | 假设你在使用默认路径的Python2.6,加入下面的值 :: 144 | 145 | ;C:\Python26\Scripts 146 | 147 | 这样就完成了。要检查它是否正常工作,打开命令提示符然后执行 ``easy_install``.如果在Vista 148 | 或者Win7下你只有用户控制权限,它应该会要求你获得管理员权限。 149 | 150 | .. _ez_setup.py: http://peak.telecommunity.com/dist/ez_setup.py 151 | -------------------------------------------------------------------------------- /docs/latexindex.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Flask Documentation 4 | =================== 5 | 6 | .. include:: contents.rst.inc 7 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | Flask is licensed under a three clause BSD License. It basically means: 5 | do whatever you want with it as long as the copyright in Flask sticks 6 | around, the conditions are not modified and the disclaimer is present. 7 | Furthermore you must not use the names of the authors to promote derivatives 8 | of the software without written consent. 9 | 10 | The full license text can be found below (:ref:`flask-license`). For the 11 | documentation and artwork different licenses apply. 12 | 13 | .. _authors: 14 | 15 | Authors 16 | ------- 17 | 18 | .. include:: ../AUTHORS 19 | 20 | General License Definitions 21 | --------------------------- 22 | 23 | The following section contains the full license texts for Flask and the 24 | documentation. 25 | 26 | - "AUTHORS" hereby refers to all the authors listed in the 27 | :ref:`authors` section. 28 | 29 | - The ":ref:`flask-license`" applies to all the sourcecode shipped as 30 | part of Flask (Flask itself as well as the examples and the unittests) 31 | as well as documentation. 32 | 33 | - The ":ref:`artwork-license`" applies to the project's Horn-Logo. 34 | 35 | .. _flask-license: 36 | 37 | Flask License 38 | ------------- 39 | 40 | .. include:: ../LICENSE 41 | 42 | 43 | .. _artwork-license: 44 | 45 | -------------------------------------------------------------------------------- /docs/logo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wayhome/flask-cn/560e4ce88aa15d410a619ab4fa629ff508b3c4e9/docs/logo.pdf -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^` where ^ is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. changes to make an overview over all changed/added/deprecated items 30 | echo. linkcheck to check all external links for integrity 31 | echo. doctest to run all doctests embedded in the documentation if enabled 32 | goto end 33 | ) 34 | 35 | if "%1" == "clean" ( 36 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 37 | del /q /s %BUILDDIR%\* 38 | goto end 39 | ) 40 | 41 | if "%1" == "html" ( 42 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 43 | echo. 44 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 45 | goto end 46 | ) 47 | 48 | if "%1" == "dirhtml" ( 49 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 52 | goto end 53 | ) 54 | 55 | if "%1" == "singlehtml" ( 56 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 57 | echo. 58 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 59 | goto end 60 | ) 61 | 62 | if "%1" == "pickle" ( 63 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 64 | echo. 65 | echo.Build finished; now you can process the pickle files. 66 | goto end 67 | ) 68 | 69 | if "%1" == "json" ( 70 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 71 | echo. 72 | echo.Build finished; now you can process the JSON files. 73 | goto end 74 | ) 75 | 76 | if "%1" == "htmlhelp" ( 77 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 78 | echo. 79 | echo.Build finished; now you can run HTML Help Workshop with the ^ 80 | .hhp project file in %BUILDDIR%/htmlhelp. 81 | goto end 82 | ) 83 | 84 | if "%1" == "qthelp" ( 85 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 86 | echo. 87 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 88 | .qhcp project file in %BUILDDIR%/qthelp, like this: 89 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Flask.qhcp 90 | echo.To view the help file: 91 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flask.ghc 92 | goto end 93 | ) 94 | 95 | if "%1" == "devhelp" ( 96 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% _build/devhelp 97 | echo. 98 | echo.Build finished. 99 | goto end 100 | ) 101 | 102 | if "%1" == "epub" ( 103 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 104 | echo. 105 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 106 | goto end 107 | ) 108 | 109 | if "%1" == "latex" ( 110 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 111 | echo. 112 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 113 | goto end 114 | ) 115 | 116 | if "%1" == "changes" ( 117 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 118 | echo. 119 | echo.The overview file is in %BUILDDIR%/changes. 120 | goto end 121 | ) 122 | 123 | if "%1" == "linkcheck" ( 124 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 125 | echo. 126 | echo.Link check complete; look for any errors in the above output ^ 127 | or in %BUILDDIR%/linkcheck/output.txt. 128 | goto end 129 | ) 130 | 131 | if "%1" == "doctest" ( 132 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 133 | echo. 134 | echo.Testing of doctests in the sources finished, look at the ^ 135 | results in %BUILDDIR%/doctest/output.txt. 136 | goto end 137 | ) 138 | 139 | :end 140 | -------------------------------------------------------------------------------- /docs/patterns/appdispatch.rst: -------------------------------------------------------------------------------- 1 | .. _app-dispatch: 2 | 3 | 应用程序的派遣 4 | ======================= 5 | 6 | 应用程序的派遣就是在WSGI层面上整合多个Flask应用程序的过程。你可以只将一些Flask应用整合到一个更大的WSGI应用里。甚至你需要的话也可以让Django和Flask应用由同一个解释器来并肩执行。至于如何具体分派那就要取决于应用程序在内部是如何工作的了。 7 | 8 | 这里谈到的内容与 :ref:`module approach ` 里说述的基本不同点是,你要执行的相同或不同的Flask应用之间是完全相互独立的。他们在WSGI层面上进行调度,且根据不同的配置执行。 9 | 10 | 11 | 关于本章节 12 | -------------------------- 13 | 14 | 下面提到的所有使用到的技术和例子,只要是在 ``application`` 对象内的,都可以由任何的WSGI服务器执行,如果你要配置生产环境,请看 :ref:`deployment` 章节。如果是开发环境,Werkzeug已经提供了供开发者使用的内嵌的服务器 :func:`werkzeug.serving.run_simple`:: 15 | 16 | from werkzeug.serving import run_simple 17 | run_simple('localhost', 5000, application, use_reloader=True) 18 | 19 | 请注意 :func:`run_simple ` 不是用来作为生产环境使用的。请使用 :ref:`完备的WSGI服务器 `. 20 | 21 | 22 | 整合应用程序 23 | ---------------------- 24 | 25 | 如果你有几个完全分离的应用,但是你想让他们在同一个Python解释进程下协同工作,你可以使用 :class:`werkzeug.wsgi.DispatcherMiddleware` 里的功能。实现它的原理是每一个Flask应用都是一个合法的WSGI应用,那么他们可以用添加前缀的方式由派遣的中间件来整合成一个更大的应用。 26 | 27 | 举个例子,你可能希望让你的主应用在 `/` 路径下执行,而你的后台在 `/backend` 路径下执行:: 28 | 29 | from werkzeug.wsgi import DispatcherMiddleware 30 | from frontend_app import application as frontend 31 | from backend_app import application as backend 32 | 33 | application = DispatcherMiddleware(frontend, { 34 | '/backend': backend 35 | }) 36 | 37 | 38 | 由子域名进行分派 39 | --------------------- 40 | 41 | 有时候你可能希望同时使用一个应用的多种不同版本的实例。假设应用的创建是在方法里实现的,你可以调用这个方法来对它进行实例化,这样的话实现起来就非常方便。如果你要了解如何让你的应用程序支持在方法里进行应用程序实例化操作,请查阅 :ref:`app-factories` 章节。 42 | 43 | 根据子域名创建应用是非常常见的。比如说你可能会对你的Web服务器进行配置,使其将所有的请求分派给你的应用程序来执行,然后你的应用程序根据子域名信息创建特定用户的实例。当你将你的服务器配置为监听所有的子域名后,你就能非常简单的用一个WSGI应用来实现动态应用的创建了。 44 | 45 | WSGI层是处理这个问题的最佳着手点。你可以编写你自己的WSGI应用程序来查看请求的来源,然后把它委派给你的Flask应用去执行。如果那个应用不存在,那就动态的创建一个并且记住它:: 46 | 47 | from threading import Lock 48 | 49 | class SubdomainDispatcher(object): 50 | 51 | def __init__(self, domain, create_app): 52 | self.domain = domain 53 | self.create_app = create_app 54 | self.lock = Lock() 55 | self.instances = {} 56 | 57 | def get_application(self, host): 58 | host = host.split(':')[0] 59 | assert host.endswith(self.domain), 'Configuration error' 60 | subdomain = host[:-len(self.domain)].rstrip('.') 61 | with self.lock: 62 | app = self.instances.get(subdomain) 63 | if app is None: 64 | app = self.create_app(subdomain) 65 | self.instances[subdomain] = app 66 | return app 67 | 68 | def __call__(self, environ, start_response): 69 | app = self.get_application(environ['HTTP_HOST']) 70 | return app(environ, start_response) 71 | 72 | 73 | 你可以这样调用这个派遣:: 74 | 75 | from myapplication import create_app, get_user_for_subdomain 76 | from werkzeug.exceptions import NotFound 77 | 78 | def make_app(subdomain): 79 | user = get_user_for_subdomain(subdomain) 80 | if user is None: 81 | # if there is no user for that subdomain we still have 82 | # to return a WSGI application that handles that request. 83 | # We can then just return the NotFound() exception as 84 | # application which will render a default 404 page. 85 | # You might also redirect the user to the main page then 86 | return NotFound() 87 | 88 | # otherwise create the application for the specific user 89 | return create_app(user) 90 | 91 | application = SubdomainDispatcher('example.com', make_app) 92 | 93 | 94 | 根据路径分派 95 | ---------------- 96 | 97 | 根据URL地址进行分派也十分类似。相对于查找 `Host` 头来确定子域名,这里你只需要查看请求网址第一个斜杠后的内容就可以了:: 98 | 99 | from threading import Lock 100 | from werkzeug.wsgi import pop_path_info, peek_path_info 101 | 102 | class PathDispatcher(object): 103 | 104 | def __init__(self, default_app, create_app): 105 | self.default_app = default_app 106 | self.create_app = create_app 107 | self.lock = Lock() 108 | self.instances = {} 109 | 110 | def get_application(self, prefix): 111 | with self.lock: 112 | app = self.instances.get(prefix) 113 | if app is None: 114 | app = self.create_app(prefix) 115 | if app is not None: 116 | self.instances[prefix] = app 117 | return app 118 | 119 | def __call__(self, environ, start_response): 120 | app = self.get_application(peek_path_info(environ)) 121 | if app is not None: 122 | pop_path_info(environ) 123 | else: 124 | app = self.default_app 125 | return app(environ, start_response) 126 | 127 | 这里与子域名分派的最大不同点是如果创建的方法返回 `None` ,这里就会退到一个另外的应用程序里:: 128 | 129 | from myapplication import create_app, default_app, get_user_for_prefix 130 | 131 | def make_app(prefix): 132 | user = get_user_for_prefix(prefix) 133 | if user is not None: 134 | return create_app(user) 135 | 136 | application = PathDispatcher('example.com', default_app, make_app) 137 | -------------------------------------------------------------------------------- /docs/patterns/appfactories.rst: -------------------------------------------------------------------------------- 1 | .. _app-factories: 2 | 3 | 应用程序的工厂模式 4 | ===================== 5 | 6 | 如果你已经在你的应用里使用了包和蓝图 (:ref:`blueprints`) 的话,实际上还有许多种非常好的方法让你更爽。一般的做法是在import蓝图后紧接着创建应用程序对象。但是如果将应用程序对象创建过程移到某个方法内,那么你就可以随时地创建应用程序对象的各种实例了。 7 | 8 | 你估计会问,为什么我要这么干? 9 | 10 | 1. 为了测试。这样每一种配置设定,你都可以创建基于该配置的应用实例。 11 | 2. 多个实例。想象一下你需要运行同一个应用的不同版本。当然你也可以在你的web服务器上做和多类似的配置以实现这个目的,但是如果你用了工厂,即使要在一个应用线程下同时运行同一个应用的不同实例也不会让你无从下手。 12 | 13 | 那么该如何实现呢? 14 | 15 | 一个基本的工厂 16 | --------------- 17 | 18 | 也就是说在方法里配置应用程序,就像这样:: 19 | 20 | def create_app(config_filename): 21 | app = Flask(__name__) 22 | app.config.from_pyfile(config_filename) 23 | 24 | from yourapplication.views.admin import admin 25 | from yourapplication.views.frontend import frontend 26 | app.register_blueprint(admin) 27 | app.register_blueprint(frontend) 28 | 29 | return app 30 | 31 | 这么做的不足之处是你不能在import蓝图后在蓝图内调用应用程序对象。但是你也可以在一个请求里面调用它。怎么拿到应用程序的当前配置呢?使用 :data:`~flask.current_app`:: 32 | 33 | from flask import current_app, Blueprint, render_template 34 | admin = Blueprint('admin', __name__, url_prefix='/admin') 35 | 36 | @admin.route('/') 37 | def index(): 38 | return render_template(current_app.config['INDEX_TEMPLATE']) 39 | 40 | 在上述例子中,我们尝试了在当前配置里查一个模板的名称。 41 | 42 | 使用应用程序 43 | ------------------ 44 | 45 | 要使用应用程序我们就必须先创建它。如下面的例子所示,有一个 `run.py` 文件负责执行应用:: 46 | 47 | from yourapplication import create_app 48 | app = create_app('/path/to/config.cfg') 49 | app.run() 50 | 51 | 更好的工厂 52 | -------------------- 53 | 54 | 上面提到的工厂方法内容还是有点蠢,你还可以对它继续改进。以下就是一些既简单又可行的方法: 55 | 56 | 1. 让单元测试接受配置变量变得可行,这样一来你就不用在文件系统内单独开辟配置文件了。 57 | 2. 在应用执行的同时调用蓝图里的某一个方法,这样你就有地方修改应用的一些特性(比如挂上诸如before / after request handlers(请求前/后执行者)的操作)。 58 | 3. 如果需要的话,可以在创建应用时添加WSGI中间件。 59 | -------------------------------------------------------------------------------- /docs/patterns/caching.rst: -------------------------------------------------------------------------------- 1 | .. _caching-pattern: 2 | 3 | Caching 4 | ======= 5 | 6 | When your application runs slow, throw some caches in. Well, at least 7 | it's the easiest way to speed up things. What does a cache do? Say you 8 | have a function that takes some time to complete but the results would 9 | still be good enough if they were 5 minutes old. So then the idea is that 10 | you actually put the result of that calculation into a cache for some 11 | time. 12 | 13 | Flask itself does not provide caching for you, but Werkzeug, one of the 14 | libraries it is based on, has some very basic cache support. It supports 15 | multiple cache backends, normally you want to use a memcached server. 16 | 17 | Setting up a Cache 18 | ------------------ 19 | 20 | You create a cache object once and keep it around, similar to how 21 | :class:`~flask.Flask` objects are created. If you are using the 22 | development server you can create a 23 | :class:`~werkzeug.contrib.cache.SimpleCache` object, that one is a simple 24 | cache that keeps the item stored in the memory of the Python interpreter:: 25 | 26 | from werkzeug.contrib.cache import SimpleCache 27 | cache = SimpleCache() 28 | 29 | If you want to use memcached, make sure to have one of the memcache modules 30 | supported (you get them from `PyPI `_) and a 31 | memcached server running somewhere. This is how you connect to such an 32 | memcached server then:: 33 | 34 | from werkzeug.contrib.cache import MemcachedCache 35 | cache = MemcachedCache(['127.0.0.1:11211']) 36 | 37 | If you are using App Engine, you can connect to the App Engine memcache 38 | server easily:: 39 | 40 | from werkzeug.contrib.cache import GAEMemcachedCache 41 | cache = GAEMemcachedCache() 42 | 43 | Using a Cache 44 | ------------- 45 | 46 | Now how can one use such a cache? There are two very important 47 | operations: :meth:`~werkzeug.contrib.cache.BaseCache.get` and 48 | :meth:`~werkzeug.contrib.cache.BaseCache.set`. This is how to use them: 49 | 50 | To get an item from the cache call 51 | :meth:`~werkzeug.contrib.cache.BaseCache.get` with a string as key name. 52 | If something is in the cache, it is returned. Otherwise that function 53 | will return `None`:: 54 | 55 | rv = cache.get('my-item') 56 | 57 | To add items to the cache, use the :meth:`~werkzeug.contrib.cache.BaseCache.set` 58 | method instead. The first argument is the key and the second the value 59 | that should be set. Also a timeout can be provided after which the cache 60 | will automatically remove item. 61 | 62 | Here a full example how this looks like normally:: 63 | 64 | def get_my_item(): 65 | rv = cache.get('my-item') 66 | if rv is None: 67 | rv = calculate_value() 68 | cache.set('my-item', rv, timeout=5 * 60) 69 | return rv 70 | -------------------------------------------------------------------------------- /docs/patterns/distribute.rst: -------------------------------------------------------------------------------- 1 | .. _distribute-deployment: 2 | 3 | Deploying with Distribute 4 | ========================= 5 | 6 | `distribute`_, formerly setuptools, is an extension library that is 7 | commonly used to (like the name says) distribute Python libraries and 8 | extensions. It extends distutils, a basic module installation system 9 | shipped with Python to also support various more complex constructs that 10 | make larger applications easier to distribute: 11 | 12 | - **support for dependencies**: a library or application can declare a 13 | list of other libraries it depends on which will be installed 14 | automatically for you. 15 | - **package registry**: setuptools registers your package with your 16 | Python installation. This makes it possible to query information 17 | provided by one package from another package. The best known feature of 18 | this system is the entry point support which allows one package to 19 | declare an "entry point" another package can hook into to extend the 20 | other package. 21 | - **installation manager**: `easy_install`, which comes with distribute 22 | can install other libraries for you. You can also use `pip`_ which 23 | sooner or later will replace `easy_install` which does more than just 24 | installing packages for you. 25 | 26 | Flask itself, and all the libraries you can find on the cheeseshop 27 | are distributed with either distribute, the older setuptools or distutils. 28 | 29 | In this case we assume your application is called 30 | `yourapplication.py` and you are not using a module, but a :ref:`package 31 | `. Distributing resources with standard modules is 32 | not supported by `distribute`_ so we will not bother with it. If you have 33 | not yet converted your application into a package, head over to the 34 | :ref:`larger-applications` pattern to see how this can be done. 35 | 36 | A working deployment with distribute is the first step into more complex 37 | and more automated deployment scenarios. If you want to fully automate 38 | the process, also read the :ref:`fabric-deployment` chapter. 39 | 40 | Basic Setup Script 41 | ------------------ 42 | 43 | Because you have Flask running, you either have setuptools or distribute 44 | available on your system anyways. If you do not, fear not, there is a 45 | script to install it for you: `distribute_setup.py`_. Just download and 46 | run with your Python interpreter. 47 | 48 | Standard disclaimer applies: :ref:`you better use a virtualenv 49 | `. 50 | 51 | Your setup code always goes into a file named `setup.py` next to your 52 | application. The name of the file is only convention, but because 53 | everybody will look for a file with that name, you better not change it. 54 | 55 | Yes, even if you are using `distribute`, you are importing from a package 56 | called `setuptools`. `distribute` is fully backwards compatible with 57 | `setuptools`, so it also uses the same import name. 58 | 59 | A basic `setup.py` file for a Flask application looks like this:: 60 | 61 | from setuptools import setup 62 | 63 | setup( 64 | name='Your Application', 65 | version='1.0', 66 | long_description=__doc__, 67 | packages=['yourapplication'], 68 | include_package_data=True, 69 | zip_safe=False, 70 | install_requires=['Flask'] 71 | ) 72 | 73 | Please keep in mind that you have to list subpackages explicitly. If you 74 | want distribute to lookup the packages for you automatically, you can use 75 | the `find_packages` function:: 76 | 77 | from setuptools import setup, find_packages 78 | 79 | setup( 80 | ... 81 | packages=find_packages() 82 | ) 83 | 84 | Most parameters to the `setup` function should be self explanatory, 85 | `include_package_data` and `zip_safe` might not be. 86 | `include_package_data` tells distribute to look for a `MANIFEST.in` file 87 | and install all the entries that match as package data. We will use this 88 | to distribute the static files and templates along with the Python module 89 | (see :ref:`distributing-resources`). The `zip_safe` flag can be used to 90 | force or prevent zip Archive creation. In general you probably don't want 91 | your packages to be installed as zip files because some tools do not 92 | support them and they make debugging a lot harder. 93 | 94 | 95 | .. _distributing-resources: 96 | 97 | Distributing Resources 98 | ---------------------- 99 | 100 | If you try to install the package you just created, you will notice that 101 | folders like `static` or `templates` are not installed for you. The 102 | reason for this is that distribute does not know which files to add for 103 | you. What you should do, is to create a `MANIFEST.in` file next to your 104 | `setup.py` file. This file lists all the files that should be added to 105 | your tarball:: 106 | 107 | recursive-include yourapplication/templates * 108 | recursive-include yourapplication/static * 109 | 110 | Don't forget that even if you enlist them in your `MANIFEST.in` file, they 111 | won't be installed for you unless you set the `include_package_data` 112 | parameter of the `setup` function to `True`! 113 | 114 | 115 | Declaring Dependencies 116 | ---------------------- 117 | 118 | Dependencies are declared in the `install_requires` parameter as list. 119 | Each item in that list is the name of a package that should be pulled from 120 | PyPI on installation. By default it will always use the most recent 121 | version, but you can also provide minimum and maximum version 122 | requirements. Here some examples:: 123 | 124 | install_requires=[ 125 | 'Flask>=0.2', 126 | 'SQLAlchemy>=0.6', 127 | 'BrokenPackage>=0.7,<=1.0' 128 | ] 129 | 130 | I mentioned earlier that dependencies are pulled from PyPI. What if you 131 | want to depend on a package that cannot be found on PyPI and won't be 132 | because it is an internal package you don't want to share with anyone? 133 | Just still do as if there was a PyPI entry for it and provide a list of 134 | alternative locations where distribute should look for tarballs:: 135 | 136 | dependency_links=['http://example.com/yourfiles'] 137 | 138 | Make sure that page has a directory listing and the links on the page are 139 | pointing to the actual tarballs with their correct filenames as this is 140 | how distribute will find the files. If you have an internal company 141 | server that contains the packages, provide the URL to that server there. 142 | 143 | 144 | Installing / Developing 145 | ----------------------- 146 | 147 | To install your application (ideally into a virtualenv) just run the 148 | `setup.py` script with the `install` parameter. It will install your 149 | application into the virtualenv's site-packages folder and also download 150 | and install all dependencies:: 151 | 152 | $ python setup.py install 153 | 154 | If you are developing on the package and also want the requirements to be 155 | installed, you can use the `develop` command instead:: 156 | 157 | $ python setup.py develop 158 | 159 | This has the advantage of just installing a link to the site-packages 160 | folder instead of copying the data over. You can then continue to work on 161 | the code without having to run `install` again after each change. 162 | 163 | 164 | .. _distribute: http://pypi.python.org/pypi/distribute 165 | .. _pip: http://pypi.python.org/pypi/pip 166 | .. _distribute_setup.py: http://python-distribute.org/distribute_setup.py 167 | -------------------------------------------------------------------------------- /docs/patterns/errorpages.rst: -------------------------------------------------------------------------------- 1 | Custom Error Pages 2 | ================== 3 | 4 | Flask comes with a handy :func:`~flask.abort` function that aborts a 5 | request with an HTTP error code early. It will also provide a plain black 6 | and white error page for you with a basic description, but nothing fancy. 7 | 8 | Depending on the error code it is less or more likely for the user to 9 | actually see such an error. 10 | 11 | Common Error Codes 12 | ------------------ 13 | 14 | The following error codes are some that are often displayed to the user, 15 | even if the application behaves correctly: 16 | 17 | *404 Not Found* 18 | The good old "chap, you made a mistake typing that URL" message. So 19 | common that even novices to the internet know that 404 means: damn, 20 | the thing I was looking for is not there. It's a very good idea to 21 | make sure there is actually something useful on a 404 page, at least a 22 | link back to the index. 23 | 24 | *403 Forbidden* 25 | If you have some kind of access control on your website, you will have 26 | to send a 403 code for disallowed resources. So make sure the user 27 | is not lost when they try to access a forbidden resource. 28 | 29 | *410 Gone* 30 | Did you know that there the "404 Not Found" has a brother named "410 31 | Gone"? Few people actually implement that, but the idea is that 32 | resources that previously existed and got deleted answer with 410 33 | instead of 404. If you are not deleting documents permanently from 34 | the database but just mark them as deleted, do the user a favour and 35 | use the 410 code instead and display a message that what they were 36 | looking for was deleted for all eternity. 37 | 38 | *500 Internal Server Error* 39 | Usually happens on programming errors or if the server is overloaded. 40 | A terrible good idea to have a nice page there, because your 41 | application *will* fail sooner or later (see also: 42 | :ref:`application-errors`). 43 | 44 | 45 | Error Handlers 46 | -------------- 47 | 48 | An error handler is a function, just like a view function, but it is 49 | called when an error happens and is passed that error. The error is most 50 | likely a :exc:`~werkzeug.exceptions.HTTPException`, but in one case it 51 | can be a different error: a handler for internal server errors will be 52 | passed other exception instances as well if they are uncaught. 53 | 54 | An error handler is registered with the :meth:`~flask.Flask.errorhandler` 55 | decorator and the error code of the exception. Keep in mind that Flask 56 | will *not* set the error code for you, so make sure to also provide the 57 | HTTP status code when returning a response. 58 | 59 | Here an example implementation for a "404 Page Not Found" exception:: 60 | 61 | from flask import render_template 62 | 63 | @app.errorhandler(404) 64 | def page_not_found(e): 65 | return render_template('404.html'), 404 66 | 67 | An example template might be this: 68 | 69 | .. sourcecode:: html+jinja 70 | 71 | {% extends "layout.html" %} 72 | {% block title %}Page Not Found{% endblock %} 73 | {% block body %} 74 |

Page Not Found

75 |

What you were looking for is just not there. 76 |

go somewhere nice 77 | {% endblock %} 78 | -------------------------------------------------------------------------------- /docs/patterns/fabric.rst: -------------------------------------------------------------------------------- 1 | .. _fabric-deployment: 2 | 3 | Deploying with Fabric 4 | ===================== 5 | 6 | `Fabric`_ is a tool for Python similar to Makefiles but with the ability 7 | to execute commands on a remote server. In combination with a properly 8 | set up Python package (:ref:`larger-applications`) and a good concept for 9 | configurations (:ref:`config`) it is very easy to deploy Flask 10 | applications to external servers. 11 | 12 | Before we get started, here a quick checklist of things we have to ensure 13 | upfront: 14 | 15 | - Fabric 1.0 has to be installed locally. This tutorial assumes the 16 | latest version of Fabric. 17 | - The application already has to be a package and requires a working 18 | `setup.py` file (:ref:`distribute-deployment`). 19 | - In the following example we are using `mod_wsgi` for the remote 20 | servers. You can of course use your own favourite server there, but 21 | for this example we chose Apache + `mod_wsgi` because it's very easy 22 | to setup and has a simple way to reload applications without root 23 | access. 24 | 25 | Creating the first Fabfile 26 | -------------------------- 27 | 28 | A fabfile is what controls what Fabric executes. It is named `fabfile.py` 29 | and executed by the `fab` command. All the functions defined in that file 30 | will show up as `fab` subcommands. They are executed on one or more 31 | hosts. These hosts can be defined either in the fabfile or on the command 32 | line. In this case we will add them to the fabfile. 33 | 34 | This is a basic first example that has the ability to upload the current 35 | sourcecode to the server and install it into a pre-existing 36 | virtual environment:: 37 | 38 | from fabric.api import * 39 | 40 | # the user to use for the remote commands 41 | env.user = 'appuser' 42 | # the servers where the commands are executed 43 | env.hosts = ['server1.example.com', 'server2.example.com'] 44 | 45 | def pack(): 46 | # create a new source distribution as tarball 47 | local('python setup.py sdist --formats=gztar', capture=False) 48 | 49 | def deploy(): 50 | # figure out the release name and version 51 | dist = local('python setup.py --fullname', capture=True).strip() 52 | # upload the source tarball to the temporary folder on the server 53 | put('dist/%s.tar.gz' % dist, '/tmp/yourapplication.tar.gz') 54 | # create a place where we can unzip the tarball, then enter 55 | # that directory and unzip it 56 | run('mkdir /tmp/yourapplication') 57 | with cd('/tmp/yourapplication'): 58 | run('tar xzf /tmp/yourapplication.tar.gz') 59 | # now setup the package with our virtual environment's 60 | # python interpreter 61 | run('/var/www/yourapplication/env/bin/python setup.py install') 62 | # now that all is set up, delete the folder again 63 | run('rm -rf /tmp/yourapplication /tmp/yourapplication.tar.gz') 64 | # and finally touch the .wsgi file so that mod_wsgi triggers 65 | # a reload of the application 66 | run('touch /var/www/yourapplication.wsgi') 67 | 68 | The example above is well documented and should be straightforward. Here 69 | a recap of the most common commands fabric provides: 70 | 71 | - `run` - executes a command on a remote server 72 | - `local` - executes a command on the local machine 73 | - `put` - uploads a file to the remote server 74 | - `cd` - changes the directory on the serverside. This has to be used 75 | in combination with the `with` statement. 76 | 77 | Running Fabfiles 78 | ---------------- 79 | 80 | Now how do you execute that fabfile? You use the `fab` command. To 81 | deploy the current version of the code on the remote server you would use 82 | this command:: 83 | 84 | $ fab pack deploy 85 | 86 | However this requires that our server already has the 87 | ``/var/www/yourapplication`` folder created and 88 | ``/var/www/yourapplication/env`` to be a virtual environment. Furthermore 89 | are we not creating the configuration or `.wsgi` file on the server. So 90 | how do we bootstrap a new server into our infrastructure? 91 | 92 | This now depends on the number of servers we want to set up. If we just 93 | have one application server (which the majority of applications will 94 | have), creating a command in the fabfile for this is overkill. But 95 | obviously you can do that. In that case you would probably call it 96 | `setup` or `bootstrap` and then pass the servername explicitly on the 97 | command line:: 98 | 99 | $ fab -H newserver.example.com bootstrap 100 | 101 | To setup a new server you would roughly do these steps: 102 | 103 | 1. Create the directory structure in ``/var/www``:: 104 | 105 | $ mkdir /var/www/yourapplication 106 | $ cd /var/www/yourapplication 107 | $ virtualenv --distribute env 108 | 109 | 2. Upload a new `application.wsgi` file to the server and the 110 | configuration file for the application (eg: `application.cfg`) 111 | 112 | 3. Create a new Apache config for `yourapplication` and activate it. 113 | Make sure to activate watching for changes of the `.wsgi` file so 114 | that we can automatically reload the application by touching it. 115 | (See :ref:`mod_wsgi-deployment` for more information) 116 | 117 | So now the question is, where do the `application.wsgi` and 118 | `application.cfg` files come from? 119 | 120 | The WSGI File 121 | ------------- 122 | 123 | The WSGI file has to import the application and also to set an environment 124 | variable so that the application knows where to look for the config. This 125 | is a short example that does exactly that:: 126 | 127 | import os 128 | os.environ['YOURAPPLICATION_CONFIG'] = '/var/www/yourapplication/application.cfg' 129 | from yourapplication import app 130 | 131 | The application itself then has to initialize itself like this to look for 132 | the config at that environment variable:: 133 | 134 | app = Flask(__name__) 135 | app.config.from_object('yourapplication.default_config') 136 | app.config.from_envvar('YOURAPPLICATION_CONFIG') 137 | 138 | This approach is explained in detail in the :ref:`config` section of the 139 | documentation. 140 | 141 | The Configuration File 142 | ---------------------- 143 | 144 | Now as mentioned above, the application will find the correct 145 | configuration file by looking up the `YOURAPPLICATION_CONFIG` environment 146 | variable. So we have to put the configuration in a place where the 147 | application will able to find it. Configuration files have the unfriendly 148 | quality of being different on all computers, so you do not version them 149 | usually. 150 | 151 | A popular approach is to store configuration files for different servers 152 | in a separate version control repository and check them out on all 153 | servers. Then symlink the file that is active for the server into the 154 | location where it's expected (eg: ``/var/www/yourapplication``). 155 | 156 | Either way, in our case here we only expect one or two servers and we can 157 | upload them ahead of time by hand. 158 | 159 | First Deployment 160 | ---------------- 161 | 162 | Now we can do our first deployment. We have set up the servers so that 163 | they have their virtual environments and activated apache configs. Now we 164 | can pack up the application and deploy it:: 165 | 166 | $ fab pack deploy 167 | 168 | Fabric will now connect to all servers and run the commands as written 169 | down in the fabfile. First it will execute pack so that we have our 170 | tarball ready and then it will execute deploy and upload the source code 171 | to all servers and install it there. Thanks to the `setup.py` file we 172 | will automatically pull in the required libraries into our virtual 173 | environment. 174 | 175 | Next Steps 176 | ---------- 177 | 178 | From that point onwards there is so much that can be done to make 179 | deployment actually fun: 180 | 181 | - Create a `bootstrap` command that initializes new servers. It could 182 | initialize a new virtual environment, setup apache appropriately etc. 183 | - Put configuration files into a separate version control repository 184 | and symlink the active configs into place. 185 | - You could also put your application code into a repository and check 186 | out the latest version on the server and then install. That way you 187 | can also easily go back to older versions. 188 | - hook in testing functionality so that you can deploy to an external 189 | server and run the testsuite. 190 | 191 | Working with Fabric is fun and you will notice that it's quite magical to 192 | type ``fab deploy`` and see your application being deployed automatically 193 | to one or more remote servers. 194 | 195 | 196 | .. _Fabric: http://fabfile.org/ 197 | -------------------------------------------------------------------------------- /docs/patterns/favicon.rst: -------------------------------------------------------------------------------- 1 | Adding a favicon 2 | ================ 3 | 4 | A "favicon" is an icon used by browsers for tabs and bookmarks. This helps 5 | to distinguish your website and to give it a unique brand. 6 | 7 | A common question is how to add a favicon to a flask application. First, of 8 | course, you need an icon. It should be 16 × 16 pixels and in the ICO file 9 | format. This is not a requirement but a de-facto standard supported by all 10 | relevant browsers. Put the icon in your static directory as 11 | :file:`favicon.ico`. 12 | 13 | Now, to get browsers to find your icon, the correct way is to add a link 14 | tag in your HTML. So, for example: 15 | 16 | .. sourcecode:: html+jinja 17 | 18 | 19 | 20 | That's all you need for most browsers, however some really old ones do not 21 | support this standard. The old de-facto standard is to serve this file, 22 | with this name, at the website root. If your application is not mounted at 23 | the root path of the domain you either need to configure the webserver to 24 | serve the icon at the root or if you can't do that you're out of luck. If 25 | however your application is the root you can simply route a redirect:: 26 | 27 | app.add_url_rule('/favicon.ico', 28 | redirect_to=url_for('static', filename='favicon.ico')) 29 | 30 | If you want to save the extra redirect request you can also write a view 31 | using :func:`~flask.send_from_directory`:: 32 | 33 | import os 34 | from flask import send_from_directory 35 | 36 | @app.route('/favicon.ico') 37 | def favicon(): 38 | return send_from_directory(os.path.join(app.root_path, 'static'), 39 | 'favicon.ico', mimetype='image/vnd.microsoft.icon') 40 | 41 | We can leave out the explicit mimetype and it will be guessed, but we may 42 | as well specify it to avoid the extra guessing, as it will always be the 43 | same. 44 | 45 | The above will serve the icon via your application and if possible it's 46 | better to configure your dedicated web server to serve it; refer to the 47 | webserver's documentation. 48 | 49 | See also 50 | -------- 51 | 52 | * The `Favicon `_ article on 53 | Wikipedia 54 | -------------------------------------------------------------------------------- /docs/patterns/fileuploads.rst: -------------------------------------------------------------------------------- 1 | .. _uploading-files: 2 | 3 | Uploading Files 4 | =============== 5 | 6 | Ah yes, the good old problem of file uploads. The basic idea of file 7 | uploads is actually quite simple. It basically works like this: 8 | 9 | 1. A ``

`` tag is marked with ``enctype=multipart/form-data`` 10 | and an ```` is placed in that form. 11 | 2. The application accesses the file from the :attr:`~flask.request.files` 12 | dictionary on the request object. 13 | 3. use the :meth:`~werkzeug.datastructures.FileStorage.save` method of the file to save 14 | the file permanently somewhere on the filesystem. 15 | 16 | A Gentle Introduction 17 | --------------------- 18 | 19 | Let's start with a very basic application that uploads a file to a 20 | specific upload folder and displays a file to the user. Let's look at the 21 | bootstrapping code for our application:: 22 | 23 | import os 24 | from flask import Flask, request, redirect, url_for 25 | from werkzeug import secure_filename 26 | 27 | UPLOAD_FOLDER = '/path/to/the/uploads' 28 | ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif']) 29 | 30 | app = Flask(__name__) 31 | 32 | So first we need a couple of imports. Most should be straightforward, the 33 | :func:`werkzeug.secure_filename` is explained a little bit later. The 34 | `UPLOAD_FOLDER` is where we will store the uploaded files and the 35 | `ALLOWED_EXTENSIONS` is the set of allowed file extensions. Then we add a 36 | URL rule by hand to the application. Now usually we're not doing that, so 37 | why here? The reasons is that we want the webserver (or our development 38 | server) to serve these files for us and so we only need a rule to generate 39 | the URL to these files. 40 | 41 | Why do we limit the extensions that are allowed? You probably don't want 42 | your users to be able to upload everything there if the server is directly 43 | sending out the data to the client. That way you can make sure that users 44 | are not able to upload HTML files that would cause XSS problems (see 45 | :ref:`xss`). Also make sure to disallow `.php` files if the server 46 | executes them, but who has PHP installed on his server, right? :) 47 | 48 | Next the functions that check if an extension is valid and that uploads 49 | the file and redirects the user to the URL for the uploaded file:: 50 | 51 | def allowed_file(filename): 52 | return '.' in filename and \ 53 | filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS 54 | 55 | @app.route('/', methods=['GET', 'POST']) 56 | def upload_file(): 57 | if request.method == 'POST': 58 | file = request.files['file'] 59 | if file and allowed_file(file.filename): 60 | filename = secure_filename(file.filename) 61 | file.save(os.path.join(UPLOAD_FOLDER, filename)) 62 | return redirect(url_for('uploaded_file', 63 | filename=filename)) 64 | return ''' 65 | 66 | Upload new File 67 |

Upload new File

68 | 69 |

70 | 71 |

72 | ''' 73 | 74 | So what does that :func:`~werkzeug.utils.secure_filename` function actually do? 75 | Now the problem is that there is that principle called "never trust user 76 | input". This is also true for the filename of an uploaded file. All 77 | submitted form data can be forged, and filenames can be dangerous. For 78 | the moment just remember: always use that function to secure a filename 79 | before storing it directly on the filesystem. 80 | 81 | .. admonition:: Information for the Pros 82 | 83 | So you're interested in what that :func:`~werkzeug.utils.secure_filename` 84 | function does and what the problem is if you're not using it? So just 85 | imagine someone would send the following information as `filename` to 86 | your application:: 87 | 88 | filename = "../../../../home/username/.bashrc" 89 | 90 | Assuming the number of ``../`` is correct and you would join this with 91 | the `UPLOAD_FOLDER` the user might have the ability to modify a file on 92 | the server's filesystem he or she should not modify. This does require some 93 | knowledge about how the application looks like, but trust me, hackers 94 | are patient :) 95 | 96 | Now let's look how that function works: 97 | 98 | >>> secure_filename('../../../../home/username/.bashrc') 99 | 'home_username_.bashrc' 100 | 101 | Now one last thing is missing: the serving of the uploaded files. As of 102 | Flask 0.5 we can use a function that does that for us:: 103 | 104 | from flask import send_from_directory 105 | 106 | @app.route('/uploads/') 107 | def uploaded_file(filename): 108 | return send_from_directory(app.config['UPLOAD_FOLDER'], 109 | filename) 110 | 111 | Alternatively you can register `uploaded_file` as `build_only` rule and 112 | use the :class:`~werkzeug.wsgi.SharedDataMiddleware`. This also works with 113 | older versions of Flask:: 114 | 115 | from werkzeug import SharedDataMiddleware 116 | app.add_url_rule('/uploads/', 'uploaded_file', 117 | build_only=True) 118 | app.wsgi_app = SharedDataMiddleware(app.wsgi_app, { 119 | '/uploads': UPLOAD_FOLDER 120 | }) 121 | 122 | If you now run the application everything should work as expected. 123 | 124 | 125 | Improving Uploads 126 | ----------------- 127 | 128 | .. versionadded:: 0.6 129 | 130 | So how exactly does Flask handle uploads? Well it will store them in the 131 | webserver's memory if the files are reasonable small otherwise in a 132 | temporary location (as returned by :func:`tempfile.gettempdir`). But how 133 | do you specify the maximum file size after which an upload is aborted? By 134 | default Flask will happily accept file uploads to an unlimited amount of 135 | memory, but you can limit that by setting the ``MAX_CONTENT_LENGTH`` 136 | config key:: 137 | 138 | from flask import Flask, Request 139 | 140 | app = Flask(__name__) 141 | app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 142 | 143 | The code above will limited the maximum allowed payload to 16 megabytes. 144 | If a larger file is transmitted, Flask will raise an 145 | :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception. 146 | 147 | This feature was added in Flask 0.6 but can be achieved in older versions 148 | as well by subclassing the request object. For more information on that 149 | consult the Werkzeug documentation on file handling. 150 | 151 | 152 | Upload Progress Bars 153 | -------------------- 154 | 155 | A while ago many developers had the idea to read the incoming file in 156 | small chunks and store the upload progress in the database to be able to 157 | poll the progress with JavaScript from the client. Long story short: the 158 | client asks the server every 5 seconds how much it has transmitted 159 | already. Do you realize the irony? The client is asking for something it 160 | should already know. 161 | 162 | Now there are better solutions to that work faster and more reliable. The 163 | web changed a lot lately and you can use HTML5, Java, Silverlight or Flash 164 | to get a nicer uploading experience on the client side. Look at the 165 | following libraries for some nice examples how to do that: 166 | 167 | - `Plupload `_ - HTML5, Java, Flash 168 | - `SWFUpload `_ - Flash 169 | - `JumpLoader `_ - Java 170 | 171 | 172 | An Easier Solution 173 | ------------------ 174 | 175 | Because the common pattern for file uploads exists almost unchanged in all 176 | applications dealing with uploads, there is a Flask extension called 177 | `Flask-Uploads`_ that implements a full fledged upload mechanism with 178 | white and blacklisting of extensions and more. 179 | 180 | .. _Flask-Uploads: http://packages.python.org/Flask-Uploads/ 181 | -------------------------------------------------------------------------------- /docs/patterns/flashing.rst: -------------------------------------------------------------------------------- 1 | .. _message-flashing-pattern: 2 | 3 | Message Flashing 4 | ================ 5 | 6 | Good applications and user interfaces are all about feedback. If the user 7 | does not get enough feedback they will probably end up hating the 8 | application. Flask provides a really simple way to give feedback to a 9 | user with the flashing system. The flashing system basically makes it 10 | possible to record a message at the end of a request and access it next 11 | request and only next request. This is usually combined with a layout 12 | template that does this. 13 | 14 | Simple Flashing 15 | --------------- 16 | 17 | So here is a full example:: 18 | 19 | from flask import flash, redirect, url_for, render_template 20 | 21 | @app.route('/') 22 | def index(): 23 | return render_template('index.html') 24 | 25 | @app.route('/login', methods=['GET', 'POST']) 26 | def login(): 27 | error = None 28 | if request.method == 'POST': 29 | if request.form['username'] != 'admin' or \ 30 | request.form['password'] != 'secret': 31 | error = 'Invalid credentials' 32 | else: 33 | flash('You were successfully logged in') 34 | return redirect(url_for('index')) 35 | return render_template('login.html', error=error) 36 | 37 | And here the ``layout.html`` template which does the magic: 38 | 39 | .. sourcecode:: html+jinja 40 | 41 | 42 | My Application 43 | {% with messages = get_flashed_messages() %} 44 | {% if messages %} 45 |
    46 | {% for message in messages %} 47 |
  • {{ message }}
  • 48 | {% endfor %} 49 |
50 | {% endif %} 51 | {% endwith %} 52 | {% block body %}{% endblock %} 53 | 54 | And here the index.html template: 55 | 56 | .. sourcecode:: html+jinja 57 | 58 | {% extends "layout.html" %} 59 | {% block body %} 60 |

Overview

61 |

Do you want to log in? 62 | {% endblock %} 63 | 64 | And of course the login template: 65 | 66 | .. sourcecode:: html+jinja 67 | 68 | {% extends "layout.html" %} 69 | {% block body %} 70 |

Login

71 | {% if error %} 72 |

Error: {{ error }} 73 | {% endif %} 74 |

75 |
76 |
Username: 77 |
79 |
Password: 80 |
81 |
82 |

83 |

84 | {% endblock %} 85 | 86 | Flashing With Categories 87 | ------------------------ 88 | 89 | .. versionadded:: 0.3 90 | 91 | It is also possible to provide categories when flashing a message. The 92 | default category if nothing is provided is ``'message'``. Alternative 93 | categories can be used to give the user better feedback. For example 94 | error messages could be displayed with a red background. 95 | 96 | To flash a message with a different category, just use the second argument 97 | to the :func:`~flask.flash` function:: 98 | 99 | flash(u'Invalid password provided', 'error') 100 | 101 | Inside the template you then have to tell the 102 | :func:`~flask.get_flashed_messages` function to also return the 103 | categories. The loop looks slightly different in that situation then: 104 | 105 | .. sourcecode:: html+jinja 106 | 107 | {% with messages = get_flashed_messages(with_categories=true) %} 108 | {% if messages %} 109 |
    110 | {% for category, message in messages %} 111 |
  • {{ message }}
  • 112 | {% endfor %} 113 |
114 | {% endif %} 115 | {% endwith %} 116 | 117 | This is just one example of how to render these flashed messages. One 118 | might also use the category to add a prefix such as 119 | ``Error:`` to the message. 120 | -------------------------------------------------------------------------------- /docs/patterns/index.rst: -------------------------------------------------------------------------------- 1 | .. _patterns: 2 | 3 | Flask的模式 4 | ================== 5 | :译者: fermin.yang#gmail.com 6 | 7 | 在众多Web应用里,很多情况下你都能找到一些共同点。举个例子,许多应用都使用了关系型数据库和用户认证。在这种情况下就需要在请求开始时打开数据库连接并且获取当前登录用户的相关信息。在请求结束时,就需要再次关闭数据库连接。 8 | 9 | 在 `Flask 语块库 `_ 里,你能找到更多类似的模块或模式的应用。 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | packages 15 | appfactories 16 | appdispatch 17 | urlprocessors 18 | distribute 19 | fabric 20 | sqlite3 21 | sqlalchemy 22 | fileuploads 23 | caching 24 | viewdecorators 25 | wtforms 26 | templateinheritance 27 | flashing 28 | jquery 29 | errorpages 30 | lazyloading 31 | mongokit 32 | favicon 33 | -------------------------------------------------------------------------------- /docs/patterns/jquery.rst: -------------------------------------------------------------------------------- 1 | AJAX with jQuery 2 | ================ 3 | 4 | `jQuery`_ is a small JavaScript library commonly used to simplify working 5 | with the DOM and JavaScript in general. It is the perfect tool to make 6 | web applications more dynamic by exchanging JSON between server and 7 | client. 8 | 9 | JSON itself is a very lightweight transport format, very similar to how 10 | Python primitives (numbers, strings, dicts and lists) look like which is 11 | widely supported and very easy to parse. It became popular a few years 12 | ago and quickly replaced XML as transport format in web applications. 13 | 14 | If you have Python 2.6 JSON will work out of the box, in Python 2.5 you 15 | will have to install the `simplejson`_ library from PyPI. 16 | 17 | .. _jQuery: http://jquery.com/ 18 | .. _simplejson: http://pypi.python.org/pypi/simplejson 19 | 20 | Loading jQuery 21 | -------------- 22 | 23 | In order to use jQuery, you have to download it first and place it in the 24 | static folder of your application and then ensure it's loaded. Ideally 25 | you have a layout template that is used for all pages where you just have 26 | to add a script statement to the bottom of your `` to load jQuery: 27 | 28 | .. sourcecode:: html 29 | 30 | 32 | 33 | Another method is using Google's `AJAX Libraries API 34 | `_ to load jQuery: 35 | 36 | .. sourcecode:: html 37 | 38 | 39 | 41 | 42 | In this case you have to put jQuery into your static folder as a fallback, but it will 43 | first try to load it directly from Google. This has the advantage that your 44 | website will probably load faster for users if they went to at least one 45 | other website before using the same jQuery version from Google because it 46 | will already be in the browser cache. 47 | 48 | Where is My Site? 49 | ----------------- 50 | 51 | Do you know where your application is? If you are developing the answer 52 | is quite simple: it's on localhost port something and directly on the root 53 | of that server. But what if you later decide to move your application to 54 | a different location? For example to ``http://example.com/myapp``? On 55 | the server side this never was a problem because we were using the handy 56 | :func:`~flask.url_for` function that could answer that question for 57 | us, but if we are using jQuery we should not hardcode the path to 58 | the application but make that dynamic, so how can we do that? 59 | 60 | A simple method would be to add a script tag to our page that sets a 61 | global variable to the prefix to the root of the application. Something 62 | like this: 63 | 64 | .. sourcecode:: html+jinja 65 | 66 | 69 | 70 | The ``|safe`` is necessary so that Jinja does not escape the JSON encoded 71 | string with HTML rules. Usually this would be necessary, but we are 72 | inside a `script` block here where different rules apply. 73 | 74 | .. admonition:: Information for Pros 75 | 76 | In HTML the `script` tag is declared `CDATA` which means that entities 77 | will not be parsed. Everything until ```` is handled as script. 78 | This also means that there must never be any ``"|tojson|safe }}`` is rendered as 81 | ``"<\/script>"``). 82 | 83 | 84 | JSON View Functions 85 | ------------------- 86 | 87 | Now let's create a server side function that accepts two URL arguments of 88 | numbers which should be added together and then sent back to the 89 | application in a JSON object. This is a really ridiculous example and is 90 | something you usually would do on the client side alone, but a simple 91 | example that shows how you would use jQuery and Flask nonetheless:: 92 | 93 | from flask import Flask, jsonify, render_template, request 94 | app = Flask(__name__) 95 | 96 | @app.route('/_add_numbers') 97 | def add_numbers(): 98 | a = request.args.get('a', 0, type=int) 99 | b = request.args.get('b', 0, type=int) 100 | return jsonify(result=a + b) 101 | 102 | @app.route('/') 103 | def index(): 104 | return render_template('index.html') 105 | 106 | As you can see I also added an `index` method here that renders a 107 | template. This template will load jQuery as above and have a little form 108 | we can add two numbers and a link to trigger the function on the server 109 | side. 110 | 111 | Note that we are using the :meth:`~werkzeug.datastructures.MultiDict.get` method here 112 | which will never fail. If the key is missing a default value (here ``0``) 113 | is returned. Furthermore it can convert values to a specific type (like 114 | in our case `int`). This is especially handy for code that is 115 | triggered by a script (APIs, JavaScript etc.) because you don't need 116 | special error reporting in that case. 117 | 118 | The HTML 119 | -------- 120 | 121 | Your index.html template either has to extend a `layout.html` template with 122 | jQuery loaded and the `$SCRIPT_ROOT` variable set, or do that on the top. 123 | Here's the HTML code needed for our little application (`index.html`). 124 | Notice that we also drop the script directly into the HTML here. It is 125 | usually a better idea to have that in a separate script file: 126 | 127 | .. sourcecode:: html 128 | 129 | 142 |

jQuery Example

143 |

+ 144 | = 145 | ? 146 |

calculate server side 147 | 148 | I won't got into detail here about how jQuery works, just a very quick 149 | explanation of the little bit of code above: 150 | 151 | 1. ``$(function() { ... })`` specifies code that should run once the 152 | browser is done loading the basic parts of the page. 153 | 2. ``$('selector')`` selects an element and lets you operate on it. 154 | 3. ``element.bind('event', func)`` specifies a function that should run 155 | when the user clicked on the element. If that function returns 156 | `false`, the default behaviour will not kick in (in this case, navigate 157 | to the `#` URL). 158 | 4. ``$.getJSON(url, data, func)`` sends a `GET` request to `url` and will 159 | send the contents of the `data` object as query parameters. Once the 160 | data arrived, it will call the given function with the return value as 161 | argument. Note that we can use the `$SCRIPT_ROOT` variable here that 162 | we set earlier. 163 | 164 | If you don't get the whole picture, download the `sourcecode 165 | for this example 166 | `_ 167 | from github. 168 | -------------------------------------------------------------------------------- /docs/patterns/lazyloading.rst: -------------------------------------------------------------------------------- 1 | Lazily Loading Views 2 | ==================== 3 | 4 | Flask is usually used with the decorators. Decorators are simple and you 5 | have the URL right next to the function that is called for that specific 6 | URL. However there is a downside to this approach: it means all your code 7 | that uses decorators has to be imported upfront or Flask will never 8 | actually find your function. 9 | 10 | This can be a problem if your application has to import quick. It might 11 | have to do that on systems like Google's App Engine or other systems. So 12 | if you suddenly notice that your application outgrows this approach you 13 | can fall back to a centralized URL mapping. 14 | 15 | The system that enables having a central URL map is the 16 | :meth:`~flask.Flask.add_url_rule` function. Instead of using decorators, 17 | you have a file that sets up the application with all URLs. 18 | 19 | Converting to Centralized URL Map 20 | --------------------------------- 21 | 22 | Imagine the current application looks somewhat like this:: 23 | 24 | from flask import Flask 25 | app = Flask(__name__) 26 | 27 | @app.route('/') 28 | def index(): 29 | pass 30 | 31 | @app.route('/user/') 32 | def user(username): 33 | pass 34 | 35 | Then the centralized approach you would have one file with the views 36 | (`views.py`) but without any decorator:: 37 | 38 | def index(): 39 | pass 40 | 41 | def user(username): 42 | pass 43 | 44 | And then a file that sets up an application which maps the functions to 45 | URLs:: 46 | 47 | from flask import Flask 48 | from yourapplication import views 49 | app = Flask(__name__) 50 | app.add_url_rule('/', view_func=views.index) 51 | app.add_url_rule('/user/', view_func=views.user) 52 | 53 | Loading Late 54 | ------------ 55 | 56 | So far we only split up the views and the routing, but the module is still 57 | loaded upfront. The trick to actually load the view function as needed. 58 | This can be accomplished with a helper class that behaves just like a 59 | function but internally imports the real function on first use:: 60 | 61 | from werkzeug import import_string, cached_property 62 | 63 | class LazyView(object): 64 | 65 | def __init__(self, import_name): 66 | self.__module__, self.__name__ = import_name.rsplit('.', 1) 67 | self.import_name = import_name 68 | 69 | @cached_property 70 | def view(self): 71 | return import_string(self.import_name) 72 | 73 | def __call__(self, *args, **kwargs): 74 | return self.view(*args, **kwargs) 75 | 76 | What's important here is is that `__module__` and `__name__` are properly 77 | set. This is used by Flask internally to figure out how to name the 78 | URL rules in case you don't provide a name for the rule yourself. 79 | 80 | Then you can define your central place to combine the views like this:: 81 | 82 | from flask import Flask 83 | from yourapplication.helpers import LazyView 84 | app = Flask(__name__) 85 | app.add_url_rule('/', 86 | view_func=LazyView('yourapplication.views.index')) 87 | app.add_url_rule('/user/', 88 | view_func=LazyView('yourapplication.views.user')) 89 | 90 | You can further optimize this in terms of amount of keystrokes needed to 91 | write this by having a function that calls into 92 | :meth:`~flask.Flask.add_url_rule` by prefixing a string with the project 93 | name and a dot, and by wrapping `view_func` in a `LazyView` as needed:: 94 | 95 | def url(url_rule, import_name, **options): 96 | view = LazyView('yourapplication.' + import_name) 97 | app.add_url_rule(url_rule, view_func=view, **options) 98 | 99 | url('/', 'views.index') 100 | url('/user/', 'views.user') 101 | 102 | One thing to keep in mind is that before and after request handlers have 103 | to be in a file that is imported upfront to work properly on the first 104 | request. The same goes for any kind of remaining decorator. 105 | -------------------------------------------------------------------------------- /docs/patterns/mongokit.rst: -------------------------------------------------------------------------------- 1 | .. mongokit-pattern: 2 | 3 | MongoKit in Flask 4 | ================= 5 | 6 | Using a document database rather than a full DBMS gets more common these days. 7 | This pattern shows how to use MongoKit, a document mapper library, to 8 | integrate with MongoDB. 9 | 10 | This pattern requires a running MongoDB server and the MongoKit library 11 | installed. 12 | 13 | There are two very common ways to use MongoKit. I will outline each of them 14 | here: 15 | 16 | 17 | Declarative 18 | ----------- 19 | 20 | The default behaviour of MongoKit is the declarative one that is based on 21 | common ideas from Django or the SQLAlchemy declarative extension. 22 | 23 | Here an example `app.py` module for your application:: 24 | 25 | from flask import Flask 26 | from mongokit import Connection, Document 27 | 28 | # configuration 29 | MONGODB_HOST = 'localhost' 30 | MONGODB_PORT = 27017 31 | 32 | # create the little application object 33 | app = Flask(__name__) 34 | app.config.from_object(__name__) 35 | 36 | # connect to the database 37 | connection = Connection(app.config['MONGODB_HOST'], 38 | app.config['MONGODB_PORT']) 39 | 40 | 41 | To define your models, just subclass the `Document` class that is imported 42 | from MongoKit. If you've seen the SQLAlchemy pattern you may wonder why we do 43 | not have a session and even do not define a `init_db` function here. On the 44 | one hand, MongoKit does not have something like a session. This sometimes 45 | makes it more to type but also makes it blazingly fast. On the other hand, 46 | MongoDB is schemaless. This means you can modify the data structure from one 47 | insert query to the next without any problem. MongoKit is just schemaless 48 | too, but implements some validation to ensure data integrity. 49 | 50 | Here is an example document (put this also into `app.py`, e.g.):: 51 | 52 | def max_length(length): 53 | def validate(value): 54 | if len(value) <= length: 55 | return True 56 | raise Exception('%s must be at most %s characters long' % length) 57 | return validate 58 | 59 | class User(Document): 60 | structure = { 61 | 'name': unicode, 62 | 'email': unicode, 63 | } 64 | validators = { 65 | 'name': max_length(50), 66 | 'email': max_length(120) 67 | } 68 | use_dot_notation = True 69 | def __repr__(self): 70 | return '' % (self.name) 71 | 72 | # register the User document with our current connection 73 | connection.register([User]) 74 | 75 | 76 | This example shows you how to define your schema (named structure), a 77 | validator for the maximum character length and uses a special MongoKit feature 78 | called `use_dot_notation`. Per default MongoKit behaves like a python 79 | dictionary but with `use_dot_notation` set to `True` you can use your 80 | documents like you use models in nearly any other ORM by using dots to 81 | separate between attributes. 82 | 83 | You can insert entries into the database like this: 84 | 85 | >>> from yourapplication.database import connection 86 | >>> from yourapplication.models import User 87 | >>> collection = connection['test'].users 88 | >>> user = collection.User() 89 | >>> user['name'] = u'admin' 90 | >>> user['email'] = u'admin@localhost' 91 | >>> user.save() 92 | 93 | Note that MongoKit is kinda strict with used column types, you must not use a 94 | common `str` type for either `name` or `email` but unicode. 95 | 96 | Querying is simple as well: 97 | 98 | >>> list(collection.User.find()) 99 | [] 100 | >>> collection.User.find_one({'name': u'admin'}) 101 | 102 | 103 | .. _MongoKit: http://bytebucket.org/namlook/mongokit/ 104 | 105 | 106 | PyMongo Compatibility Layer 107 | --------------------------- 108 | 109 | If you just want to use PyMongo, you can do that with MongoKit as well. You 110 | may use this process if you need the best performance to get. Note that this 111 | example does not show how to couple it with Flask, see the above MongoKit code 112 | for examples:: 113 | 114 | from MongoKit import Connection 115 | 116 | connection = Connection() 117 | 118 | To insert data you can use the `insert` method. We have to get a 119 | collection first, this is somewhat the same as a table in the SQL world. 120 | 121 | >>> collection = connection['test'].users 122 | >>> user = {'name': u'admin', 'email': u'admin@localhost'} 123 | >>> collection.insert(user) 124 | 125 | print list(collection.find()) 126 | print collection.find_one({'name': u'admin'}) 127 | 128 | MongoKit will automatically commit for us. 129 | 130 | To query your database, you use the collection directly: 131 | 132 | >>> list(collection.find()) 133 | [{u'_id': ObjectId('4c271729e13823182f000000'), u'name': u'admin', u'email': u'admin@localhost'}] 134 | >>> collection.find_one({'name': u'admin'}) 135 | {u'_id': ObjectId('4c271729e13823182f000000'), u'name': u'admin', u'email': u'admin@localhost'} 136 | 137 | These results are also dict-like objects: 138 | 139 | >>> r = collection.find_one({'name': u'admin'}) 140 | >>> r['email'] 141 | u'admin@localhost' 142 | 143 | For more information about MongoKit, head over to the 144 | `website `_. 145 | -------------------------------------------------------------------------------- /docs/patterns/packages.rst: -------------------------------------------------------------------------------- 1 | .. _larger-applications: 2 | 3 | 更大的应用 4 | =================== 5 | 6 | 对于更大的应用而言,使用包来代替模块是一个不错的选择。而且它也十分方便,试想一下一个小的应用就像这样:: 7 | 8 | /yourapplication 9 | /yourapplication.py 10 | /static 11 | /style.css 12 | /templates 13 | layout.html 14 | index.html 15 | login.html 16 | ... 17 | 18 | 简单的包 19 | --------------- 20 | 21 | 要把它变成大型项目,只要在已有的项目根目录下建立再创建一个 `yourapplication` 目录,然后把原根目录下所有的东西都移到这个新建目录里面,然后将 `yourapplication.py` 重命名为 `__init__.py` 就好了。(请确认要事先删除所有 `.pyc` 文件,否则程序很有可能会挂) 22 | 23 | 你应该看到的是如下的结构:: 24 | 25 | /yourapplication 26 | /yourapplication 27 | /__init__.py 28 | /static 29 | /style.css 30 | /templates 31 | layout.html 32 | index.html 33 | login.html 34 | ... 35 | 36 | 但是这样的话我们怎么让应用跑起来呢?直接执行 ``python yourapplication/__init__.py`` 是不行的。因为Python的设计者并不希望包内的模块成为一个启动文件。不过这个问题不大,只要再创建一个名为 `runserver.py` ,使其与里面的 `yourapplication` 在同一级,然后在里面添加如下内容:: 37 | 38 | from yourapplication import app 39 | app.run(debug=True) 40 | 41 | 我们这么做是为了什么呢?这意味着现在我们可以将原来一个单独的应用重构为许多模块拼接而成。你只需要记住要遵循如下的准则: 42 | 43 | 1. `Flask` 应用对象的创建必须放在 `__init__.py` 文件内。只有这样每个模块才能安全的import(引入)它,且 `__name__` 变量才能解析到正确的包。 44 | 2. 所有的view(视图)方法(指那些在头上有 :meth:`~flask.Flask.route` 修饰的方法)必须放在 `__init__.py` 文件里import。并在 **应用程序对象创建后** 在模块被调用的地方,而不是他自身所在位置import view模块。 45 | 46 | 请看示例 `__init__.py`:: 47 | 48 | from flask import Flask 49 | app = Flask(__name__) 50 | 51 | import yourapplication.views 52 | 53 | 这是一个 `views.py` 的例子:: 54 | 55 | from yourapplication import app 56 | 57 | @app.route('/') 58 | def index(): 59 | return 'Hello World!' 60 | 61 | 然后你应该的项目结构应该像这样:: 62 | 63 | /yourapplication 64 | /yourapplication 65 | /__init__.py 66 | /views.py 67 | /static 68 | /style.css 69 | /templates 70 | layout.html 71 | index.html 72 | login.html 73 | ... 74 | 75 | .. admonition:: 回环Imports(引入) 76 | 77 | 每个Python程序员都痛恨这个问题,不过我们还是加了这个功能: 78 | 回环Imports (意思是说有两个模块互相依赖。在上述例子中 `views.py` 依赖于 `__init__.py` )。要注意在通常情况下这是很危险的做法,不过在这里问题还不大。这是因为我们不会在 `__init__.py` 里面使用视图,我们只是需要确认模块是否被引入而且我们是在文件的最后做这件事的。 79 | 80 | 不过即使这么做还是会导致一些问题的产生。什么?除了这个你还要上修饰?那根本是行不通的。你还是去看看 :ref:`becomingbig` 这一节试试能不能给你点别的灵感吧。 81 | 82 | 83 | .. _working-with-modules: 84 | 85 | 使用Blueprints(蓝图) 86 | ----------------------- 87 | 88 | 如果你要做一个大家伙,我们十分建议你将它先分成许多小的组,每个组由一个blueprint(蓝图)协助实现。如果你需要了解有关于此的详细内容,请查阅文档内的 :ref:`blueprints` 章节。 89 | -------------------------------------------------------------------------------- /docs/patterns/sqlalchemy.rst: -------------------------------------------------------------------------------- 1 | .. _sqlalchemy-pattern: 2 | 3 | SQLAlchemy in Flask 4 | =================== 5 | 6 | Many people prefer `SQLAlchemy`_ for database access. In this case it's 7 | encouraged to use a package instead of a module for your flask application 8 | and drop the models into a separate module (:ref:`larger-applications`). 9 | While that is not necessary, it makes a lot of sense. 10 | 11 | There are four very common ways to use SQLAlchemy. I will outline each 12 | of them here: 13 | 14 | Flask-SQLAlchemy Extension 15 | -------------------------- 16 | 17 | Because SQLAlchemy is a common database abstraction layer and object 18 | relational mapper that requires a little bit of configuration effort, 19 | there is a Flask extension that handles that for you. This is recommended 20 | if you want to get started quickly. 21 | 22 | You can download `Flask-SQLAlchemy`_ from `PyPI 23 | `_. 24 | 25 | .. _Flask-SQLAlchemy: http://packages.python.org/Flask-SQLAlchemy/ 26 | 27 | 28 | Declarative 29 | ----------- 30 | 31 | The declarative extension in SQLAlchemy is the most recent method of using 32 | SQLAlchemy. It allows you to define tables and models in one go, similar 33 | to how Django works. In addition to the following text I recommend the 34 | official documentation on the `declarative`_ extension. 35 | 36 | Here the example `database.py` module for your application:: 37 | 38 | from sqlalchemy import create_engine 39 | from sqlalchemy.orm import scoped_session, sessionmaker 40 | from sqlalchemy.ext.declarative import declarative_base 41 | 42 | engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) 43 | db_session = scoped_session(sessionmaker(autocommit=False, 44 | autoflush=False, 45 | bind=engine)) 46 | Base = declarative_base() 47 | Base.query = db_session.query_property() 48 | 49 | def init_db(): 50 | # import all modules here that might define models so that 51 | # they will be registered properly on the metadata. Otherwise 52 | # you will have to import them first before calling init_db() 53 | import yourapplication.models 54 | Base.metadata.create_all(bind=engine) 55 | 56 | To define your models, just subclass the `Base` class that was created by 57 | the code above. If you are wondering why we don't have to care about 58 | threads here (like we did in the SQLite3 example above with the 59 | :data:`~flask.g` object): that's because SQLAlchemy does that for us 60 | already with the :class:`~sqlalchemy.orm.scoped_session`. 61 | 62 | To use SQLAlchemy in a declarative way with your application, you just 63 | have to put the following code into your application module. Flask will 64 | automatically remove database sessions at the end of the request for you:: 65 | 66 | from yourapplication.database import db_session 67 | 68 | @app.teardown_request 69 | def shutdown_session(exception=None): 70 | db_session.remove() 71 | 72 | Here is an example model (put this into `models.py`, e.g.):: 73 | 74 | from sqlalchemy import Column, Integer, String 75 | from yourapplication.database import Base 76 | 77 | class User(Base): 78 | __tablename__ = 'users' 79 | id = Column(Integer, primary_key=True) 80 | name = Column(String(50), unique=True) 81 | email = Column(String(120), unique=True) 82 | 83 | def __init__(self, name=None, email=None): 84 | self.name = name 85 | self.email = email 86 | 87 | def __repr__(self): 88 | return '' % (self.name) 89 | 90 | To create the database you can use the `init_db` function: 91 | 92 | >>> from yourapplication.database import init_db 93 | >>> init_db() 94 | 95 | You can insert entries into the database like this: 96 | 97 | >>> from yourapplication.database import db_session 98 | >>> from yourapplication.models import User 99 | >>> u = User('admin', 'admin@localhost') 100 | >>> db_session.add(u) 101 | >>> db_session.commit() 102 | 103 | Querying is simple as well: 104 | 105 | >>> User.query.all() 106 | [] 107 | >>> User.query.filter(User.name == 'admin').first() 108 | 109 | 110 | .. _SQLAlchemy: http://www.sqlalchemy.org/ 111 | .. _declarative: 112 | http://www.sqlalchemy.org/docs/orm/extensions/declarative.html 113 | 114 | Manual Object Relational Mapping 115 | -------------------------------- 116 | 117 | Manual object relational mapping has a few upsides and a few downsides 118 | versus the declarative approach from above. The main difference is that 119 | you define tables and classes separately and map them together. It's more 120 | flexible but a little more to type. In general it works like the 121 | declarative approach, so make sure to also split up your application into 122 | multiple modules in a package. 123 | 124 | Here is an example `database.py` module for your application:: 125 | 126 | from sqlalchemy import create_engine, MetaData 127 | from sqlalchemy.orm import scoped_session, sessionmaker 128 | 129 | engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) 130 | metadata = MetaData() 131 | db_session = scoped_session(sessionmaker(autocommit=False, 132 | autoflush=False, 133 | bind=engine)) 134 | def init_db(): 135 | metadata.create_all(bind=engine) 136 | 137 | As for the declarative approach you need to close the session after 138 | each request. Put this into your application module:: 139 | 140 | from yourapplication.database import db_session 141 | 142 | @app.teardown_request 143 | def shutdown_session(exception=None): 144 | db_session.remove() 145 | 146 | Here is an example table and model (put this into `models.py`):: 147 | 148 | from sqlalchemy import Table, Column, Integer, String 149 | from sqlalchemy.orm import mapper 150 | from yourapplication.database import metadata, db_session 151 | 152 | class User(object): 153 | query = db_session.query_property() 154 | 155 | def __init__(self, name=None, email=None): 156 | self.name = name 157 | self.email = email 158 | 159 | def __repr__(self): 160 | return '' % (self.name, self.email) 161 | 162 | users = Table('users', metadata, 163 | Column('id', Integer, primary_key=True), 164 | Column('name', String(50), unique=True), 165 | Column('email', String(120), unique=True) 166 | ) 167 | mapper(User, users) 168 | 169 | Querying and inserting works exactly the same as in the example above. 170 | 171 | 172 | SQL Abstraction Layer 173 | --------------------- 174 | 175 | If you just want to use the database system (and SQL) abstraction layer 176 | you basically only need the engine:: 177 | 178 | from sqlalchemy import create_engine, MetaData 179 | 180 | engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) 181 | metadata = MetaData(bind=engine) 182 | 183 | Then you can either declare the tables in your code like in the examples 184 | above, or automatically load them:: 185 | 186 | users = Table('users', metadata, autoload=True) 187 | 188 | To insert data you can use the `insert` method. We have to get a 189 | connection first so that we can use a transaction: 190 | 191 | >>> con = engine.connect() 192 | >>> con.execute(users.insert(name='admin', email='admin@localhost')) 193 | 194 | SQLAlchemy will automatically commit for us. 195 | 196 | To query your database, you use the engine directly or use a connection: 197 | 198 | >>> users.select(users.c.id == 1).execute().first() 199 | (1, u'admin', u'admin@localhost') 200 | 201 | These results are also dict-like tuples: 202 | 203 | >>> r = users.select(users.c.id == 1).execute().first() 204 | >>> r['name'] 205 | u'admin' 206 | 207 | You can also pass strings of SQL statements to the 208 | :meth:`~sqlalchemy.engine.base.Connection.execute` method: 209 | 210 | >>> engine.execute('select * from users where id = :1', [1]).first() 211 | (1, u'admin', u'admin@localhost') 212 | 213 | For more information about SQLAlchemy, head over to the 214 | `website `_. 215 | -------------------------------------------------------------------------------- /docs/patterns/sqlite3.rst: -------------------------------------------------------------------------------- 1 | .. _sqlite3: 2 | 3 | Using SQLite 3 with Flask 4 | ========================= 5 | 6 | In Flask you can implement the opening of database connections at the 7 | beginning of the request and closing at the end with the 8 | :meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.teardown_request` 9 | decorators in combination with the special :class:`~flask.g` object. 10 | 11 | So here is a simple example of how you can use SQLite 3 with Flask:: 12 | 13 | import sqlite3 14 | from flask import g 15 | 16 | DATABASE = '/path/to/database.db' 17 | 18 | def connect_db(): 19 | return sqlite3.connect(DATABASE) 20 | 21 | @app.before_request 22 | def before_request(): 23 | g.db = connect_db() 24 | 25 | @app.teardown_request 26 | def teardown_request(exception): 27 | g.db.close() 28 | 29 | Connect on Demand 30 | ----------------- 31 | 32 | The downside of this approach is that this will only work if Flask 33 | executed the before-request handlers for you. If you are attempting to 34 | use the database from a script or the interactive Python shell you would 35 | have to do something like this:: 36 | 37 | with app.test_request_context() 38 | app.preprocess_request() 39 | # now you can use the g.db object 40 | 41 | In order to trigger the execution of the connection code. You won't be 42 | able to drop the dependency on the request context this way, but you could 43 | make it so that the application connects when necessary:: 44 | 45 | def get_connection(): 46 | db = getattr(g, '_db', None) 47 | if db is None: 48 | db = g._db = connect_db() 49 | return db 50 | 51 | Downside here is that you have to use ``db = get_connection()`` instead of 52 | just being able to use ``g.db`` directly. 53 | 54 | .. _easy-querying: 55 | 56 | Easy Querying 57 | ------------- 58 | 59 | Now in each request handling function you can access `g.db` to get the 60 | current open database connection. To simplify working with SQLite, a 61 | helper function can be useful:: 62 | 63 | def query_db(query, args=(), one=False): 64 | cur = g.db.execute(query, args) 65 | rv = [dict((cur.description[idx][0], value) 66 | for idx, value in enumerate(row)) for row in cur.fetchall()] 67 | return (rv[0] if rv else None) if one else rv 68 | 69 | This handy little function makes working with the database much more 70 | pleasant than it is by just using the raw cursor and connection objects. 71 | 72 | Here is how you can use it:: 73 | 74 | for user in query_db('select * from users'): 75 | print user['username'], 'has the id', user['user_id'] 76 | 77 | Or if you just want a single result:: 78 | 79 | user = query_db('select * from users where username = ?', 80 | [the_username], one=True) 81 | if user is None: 82 | print 'No such user' 83 | else: 84 | print the_username, 'has the id', user['user_id'] 85 | 86 | To pass variable parts to the SQL statement, use a question mark in the 87 | statement and pass in the arguments as a list. Never directly add them to 88 | the SQL statement with string formatting because this makes it possible 89 | to attack the application using `SQL Injections 90 | `_. 91 | 92 | Initial Schemas 93 | --------------- 94 | 95 | Relational databases need schemas, so applications often ship a 96 | `schema.sql` file that creates the database. It's a good idea to provide 97 | a function that creates the database based on that schema. This function 98 | can do that for you:: 99 | 100 | from contextlib import closing 101 | 102 | def init_db(): 103 | with closing(connect_db()) as db: 104 | with app.open_resource('schema.sql') as f: 105 | db.cursor().executescript(f.read()) 106 | db.commit() 107 | 108 | You can then create such a database from the python shell: 109 | 110 | >>> from yourapplication import init_db 111 | >>> init_db() 112 | -------------------------------------------------------------------------------- /docs/patterns/templateinheritance.rst: -------------------------------------------------------------------------------- 1 | .. _template-inheritance: 2 | 3 | Template Inheritance 4 | ==================== 5 | 6 | The most powerful part of Jinja is template inheritance. Template inheritance 7 | allows you to build a base "skeleton" template that contains all the common 8 | elements of your site and defines **blocks** that child templates can override. 9 | 10 | Sounds complicated but is very basic. It's easiest to understand it by starting 11 | with an example. 12 | 13 | 14 | Base Template 15 | ------------- 16 | 17 | This template, which we'll call ``layout.html``, defines a simple HTML skeleton 18 | document that you might use for a simple two-column page. It's the job of 19 | "child" templates to fill the empty blocks with content: 20 | 21 | .. sourcecode:: html+jinja 22 | 23 | 24 | 25 | 26 | {% block head %} 27 | 28 | {% block title %}{% endblock %} - My Webpage 29 | {% endblock %} 30 | 31 | 32 |

{% block content %}{% endblock %}
33 | 38 | 39 | 40 | In this example, the ``{% block %}`` tags define four blocks that child templates 41 | can fill in. All the `block` tag does is to tell the template engine that a 42 | child template may override those portions of the template. 43 | 44 | Child Template 45 | -------------- 46 | 47 | A child template might look like this: 48 | 49 | .. sourcecode:: html+jinja 50 | 51 | {% extends "layout.html" %} 52 | {% block title %}Index{% endblock %} 53 | {% block head %} 54 | {{ super() }} 55 | 58 | {% endblock %} 59 | {% block content %} 60 |

Index

61 |

62 | Welcome on my awesome homepage. 63 | {% endblock %} 64 | 65 | The ``{% extends %}`` tag is the key here. It tells the template engine that 66 | this template "extends" another template. When the template system evaluates 67 | this template, first it locates the parent. The extends tag must be the 68 | first tag in the template. To render the contents of a block defined in 69 | the parent template, use ``{{ super() }}``. 70 | -------------------------------------------------------------------------------- /docs/patterns/urlprocessors.rst: -------------------------------------------------------------------------------- 1 | Using URL Processors 2 | ==================== 3 | 4 | .. versionadded:: 0.7 5 | 6 | Flask 0.7 introduces the concept of URL processors. The idea is that you 7 | might have a bunch of resources with common parts in the URL that you 8 | don't always explicitly want to provide. For instance you might have a 9 | bunch of URLs that have the language code in it but you don't want to have 10 | to handle it in every single function yourself. 11 | 12 | URL processors are especially helpful when combined with blueprints. We 13 | will handle both application specific URL processors here as well as 14 | blueprint specifics. 15 | 16 | Internationalized Application URLs 17 | ---------------------------------- 18 | 19 | Consider an application like this:: 20 | 21 | from flask import Flask, g 22 | 23 | app = Flask(__name__) 24 | 25 | @app.route('//') 26 | def index(lang_code): 27 | g.lang_code = lang_code 28 | ... 29 | 30 | @app.route('//about') 31 | def about(lang_code): 32 | g.lang_code = lang_code 33 | ... 34 | 35 | This is an awful lot of reptition as you have to handle the language code 36 | setting on the :data:`~flask.g` object yourself in every single function. 37 | Sure, a decorator could be used to simplify this, but if you want to 38 | generate URLs from one function to another you would have to still provide 39 | the language code explicitly which can be annoying. 40 | 41 | For the latter, this is where :func:`~flask.Flask.url_defaults` functions 42 | come in. They can automatically inject values into a call for 43 | :func:`~flask.url_for` automatically. The code below checks if the 44 | language code is not yet in the dictionary of URL values and if the 45 | endpoint wants a value named ``'lang_code'``:: 46 | 47 | @app.url_defaults 48 | def add_language_code(endpoint, values): 49 | if 'lang_code' in values or not g.lang_code: 50 | return 51 | if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): 52 | values['lang_code'] = g.lang_code 53 | 54 | The method :meth:`~werkzeug.routing.Map.is_endpoint_expecting` of the URL 55 | map can be used to figure out if it would make sense to provide a language 56 | code for the given endpoint. 57 | 58 | The reverse of that function are 59 | :meth:`~flask.Flask.url_value_preprocessor`\s. They are executed right 60 | after the request was matched and can execute code based on the URL 61 | values. The idea is that they pull information out of the values 62 | dictionary and put it somewhere else:: 63 | 64 | @app.url_value_preprocessor 65 | def pull_lang_code(endpoint, values): 66 | g.lang_code = values.pop('lang_code', None) 67 | 68 | That way you no longer have to do the `lang_code` assigment to 69 | :data:`~flask.g` in every function. You can further improve that by 70 | writing your own decorator that prefixes URLs with the language code, but 71 | the more beautiful solution is using a blueprint. Once the 72 | ``'lang_code'`` is popped from the values dictionary and it will no longer 73 | be forwarded to the view function reducing the code to this:: 74 | 75 | from flask import Flask, g 76 | 77 | app = Flask(__name__) 78 | 79 | @app.url_defaults 80 | def add_language_code(endpoint, values): 81 | if 'lang_code' in values or not g.lang_code: 82 | return 83 | if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): 84 | values['lang_code'] = g.lang_code 85 | 86 | @app.url_value_preprocessor 87 | def pull_lang_code(endpoint, values): 88 | g.lang_code = values.pop('lang_code', None) 89 | 90 | @app.route('//') 91 | def index(): 92 | ... 93 | 94 | @app.route('//about') 95 | def about(): 96 | ... 97 | 98 | Internationalized Blueprint URLs 99 | -------------------------------- 100 | 101 | Because blueprints can automatically prefix all URLs with a common string 102 | it's easy to automatically do that for every function. Furthermore 103 | blueprints can have per-blueprint URL processors which removes a whole lot 104 | of logic from the :meth:`~flask.Flask.url_defaults` function because it no 105 | longer has to check if the URL is really interested in a ``'lang_code'`` 106 | parameter:: 107 | 108 | from flask import Blueprint, g 109 | 110 | bp = Blueprint('frontend', __name__, url_prefix='/') 111 | 112 | @bp.url_defaults 113 | def add_language_code(endpoint, values): 114 | values.setdefault('lang_code', g.lang_code) 115 | 116 | @bp.url_value_preprocessor 117 | def pull_lang_code(endpoint, values): 118 | g.lang_code = values.pop('lang_code') 119 | 120 | @bp.route('/') 121 | def index(): 122 | ... 123 | 124 | @bp.route('/about') 125 | def about(): 126 | ... 127 | -------------------------------------------------------------------------------- /docs/patterns/viewdecorators.rst: -------------------------------------------------------------------------------- 1 | View Decorators 2 | =============== 3 | 4 | Python has a really interesting feature called function decorators. This 5 | allow some really neat things for web applications. Because each view in 6 | Flask is a function decorators can be used to inject additional 7 | functionality to one or more functions. The :meth:`~flask.Flask.route` 8 | decorator is the one you probably used already. But there are use cases 9 | for implementing your own decorator. For instance, imagine you have a 10 | view that should only be used by people that are logged in to. If a user 11 | goes to the site and is not logged in, they should be redirected to the 12 | login page. This is a good example of a use case where a decorator is an 13 | excellent solution. 14 | 15 | Login Required Decorator 16 | ------------------------ 17 | 18 | So let's implement such a decorator. A decorator is a function that 19 | returns a function. Pretty simple actually. The only thing you have to 20 | keep in mind when implementing something like this is to update the 21 | `__name__`, `__module__` and some other attributes of a function. This is 22 | often forgotten, but you don't have to do that by hand, there is a 23 | function for that that is used like a decorator (:func:`functools.wraps`). 24 | 25 | This example assumes that the login page is called ``'login'`` and that 26 | the current user is stored as `g.user` and `None` if there is no-one 27 | logged in:: 28 | 29 | from functools import wraps 30 | from flask import g, request, redirect, url_for 31 | 32 | def login_required(f): 33 | @wraps(f) 34 | def decorated_function(*args, **kwargs): 35 | if g.user is None: 36 | return redirect(url_for('login', next=request.url)) 37 | return f(*args, **kwargs) 38 | return decorated_function 39 | 40 | So how would you use that decorator now? Apply it as innermost decorator 41 | to a view function. When applying further decorators, always remember 42 | that the :meth:`~flask.Flask.route` decorator is the outermost:: 43 | 44 | @app.route('/secret_page') 45 | @login_required 46 | def secret_page(): 47 | pass 48 | 49 | Caching Decorator 50 | ----------------- 51 | 52 | Imagine you have a view function that does an expensive calculation and 53 | because of that you would like to cache the generated results for a 54 | certain amount of time. A decorator would be nice for that. We're 55 | assuming you have set up a cache like mentioned in :ref:`caching-pattern`. 56 | 57 | Here an example cache function. It generates the cache key from a 58 | specific prefix (actually a format string) and the current path of the 59 | request. Notice that we are using a function that first creates the 60 | decorator that then decorates the function. Sounds awful? Unfortunately 61 | it is a little bit more complex, but the code should still be 62 | straightforward to read. 63 | 64 | The decorated function will then work as follows 65 | 66 | 1. get the unique cache key for the current request base on the current 67 | path. 68 | 2. get the value for that key from the cache. If the cache returned 69 | something we will return that value. 70 | 3. otherwise the original function is called and the return value is 71 | stored in the cache for the timeout provided (by default 5 minutes). 72 | 73 | Here the code:: 74 | 75 | from functools import wraps 76 | from flask import request 77 | 78 | def cached(timeout=5 * 60, key='view/%s'): 79 | def decorator(f): 80 | @wraps(f) 81 | def decorated_function(*args, **kwargs): 82 | cache_key = key % request.path 83 | rv = cache.get(cache_key) 84 | if rv is not None: 85 | return rv 86 | rv = f(*args, **kwargs) 87 | cache.set(cache_key, rv, timeout=timeout) 88 | return rv 89 | return decorated_function 90 | return decorator 91 | 92 | Notice that this assumes an instantiated `cache` object is available, see 93 | :ref:`caching-pattern` for more information. 94 | 95 | 96 | Templating Decorator 97 | -------------------- 98 | 99 | A common pattern invented by the TurboGears guys a while back is a 100 | templating decorator. The idea of that decorator is that you return a 101 | dictionary with the values passed to the template from the view function 102 | and the template is automatically rendered. With that, the following 103 | three examples do exactly the same:: 104 | 105 | @app.route('/') 106 | def index(): 107 | return render_template('index.html', value=42) 108 | 109 | @app.route('/') 110 | @templated('index.html') 111 | def index(): 112 | return dict(value=42) 113 | 114 | @app.route('/') 115 | @templated() 116 | def index(): 117 | return dict(value=42) 118 | 119 | As you can see, if no template name is provided it will use the endpoint 120 | of the URL map with dots converted to slashes + ``'.html'``. Otherwise 121 | the provided template name is used. When the decorated function returns, 122 | the dictionary returned is passed to the template rendering function. If 123 | `None` is returned, an empty dictionary is assumed, if something else than 124 | a dictionary is returned we return it from the function unchanged. That 125 | way you can still use the redirect function or return simple strings. 126 | 127 | Here the code for that decorator:: 128 | 129 | from functools import wraps 130 | from flask import request 131 | 132 | def templated(template=None): 133 | def decorator(f): 134 | @wraps(f) 135 | def decorated_function(*args, **kwargs): 136 | template_name = template 137 | if template_name is None: 138 | template_name = request.endpoint \ 139 | .replace('.', '/') + '.html' 140 | ctx = f(*args, **kwargs) 141 | if ctx is None: 142 | ctx = {} 143 | elif not isinstance(ctx, dict): 144 | return ctx 145 | return render_template(template_name, **ctx) 146 | return decorated_function 147 | return decorator 148 | 149 | 150 | Endpoint Decorator 151 | ------------------ 152 | 153 | When you want to use the werkzeug routing system for more flexibility you 154 | need to map the endpoint as defined in the :class:`~werkzeug.routing.Rule` 155 | to a view function. This is possible with this decorator. For example:: 156 | 157 | from flask import Flask 158 | from werkzeug.routing import Rule 159 | 160 | app = Flask(__name__) 161 | app.url_map.add(Rule('/', endpoint='index')) 162 | 163 | @app.endpoint('index') 164 | def my_index(): 165 | return "Hello world" 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /docs/patterns/wtforms.rst: -------------------------------------------------------------------------------- 1 | Form Validation with WTForms 2 | ============================ 3 | 4 | When you have to work with form data submitted by a browser view code 5 | quickly becomes very hard to read. There are libraries out there designed 6 | to make this process easier to manage. One of them is `WTForms`_ which we 7 | will handle here. If you find yourself in the situation of having many 8 | forms, you might want to give it a try. 9 | 10 | When you are working with WTForms you have to define your forms as classes 11 | first. I recommend breaking up the application into multiple modules 12 | (:ref:`larger-applications`) for that and adding a separate module for the 13 | forms. 14 | 15 | .. admonition:: Getting most of WTForms with an Extension 16 | 17 | The `Flask-WTF`_ extension expands on this pattern and adds a few 18 | handful little helpers that make working with forms and Flask more 19 | fun. You can get it from `PyPI 20 | `_. 21 | 22 | .. _Flask-WTF: http://packages.python.org/Flask-WTF/ 23 | 24 | The Forms 25 | --------- 26 | 27 | This is an example form for a typical registration page:: 28 | 29 | from wtforms import Form, BooleanField, TextField, validators 30 | 31 | class RegistrationForm(Form): 32 | username = TextField('Username', [validators.Length(min=4, max=25)]) 33 | email = TextField('Email Address', [validators.Length(min=6, max=35)]) 34 | password = PasswordField('New Password', [ 35 | validators.Required(), 36 | validators.EqualTo('confirm', message='Passwords must match') 37 | ]) 38 | confirm = PasswordField('Repeat Password') 39 | accept_tos = BooleanField('I accept the TOS', [validators.Required()]) 40 | 41 | In the View 42 | ----------- 43 | 44 | In the view function, the usage of this form looks like this:: 45 | 46 | @app.route('/register', methods=['GET', 'POST']) 47 | def register(): 48 | form = RegistrationForm(request.form) 49 | if request.method == 'POST' and form.validate(): 50 | user = User(form.username.data, form.email.data, 51 | form.password.data) 52 | db_session.add(user) 53 | flash('Thanks for registering') 54 | return redirect(url_for('login')) 55 | return render_template('register.html', form=form) 56 | 57 | Notice that we are implying that the view is using SQLAlchemy here 58 | (:ref:`sqlalchemy-pattern`) but this is no requirement of course. Adapt 59 | the code as necessary. 60 | 61 | Things to remember: 62 | 63 | 1. create the form from the request :attr:`~flask.request.form` value if 64 | the data is submitted via the HTTP `POST` method and 65 | :attr:`~flask.request.args` if the data is submitted as `GET`. 66 | 2. to validate the data, call the :func:`~wtforms.form.Form.validate` 67 | method which will return `True` if the data validates, `False` 68 | otherwise. 69 | 3. to access individual values from the form, access `form..data`. 70 | 71 | Forms in Templates 72 | ------------------ 73 | 74 | Now to the template side. When you pass the form to the templates you can 75 | easily render them there. Look at the following example template to see 76 | how easy this is. WTForms does half the form generation for us already. 77 | To make it even nicer, we can write a macro that renders a field with 78 | label and a list of errors if there are any. 79 | 80 | Here's an example `_formhelpers.html` template with such a macro: 81 | 82 | .. sourcecode:: html+jinja 83 | 84 | {% macro render_field(field) %} 85 |

{{ field.label }} 86 |
{{ field(**kwargs)|safe }} 87 | {% if field.errors %} 88 |
    89 | {% for error in field.errors %}
  • {{ error }}{% endfor %} 90 |
91 | {% endif %} 92 |
93 | {% endmacro %} 94 | 95 | This macro accepts a couple of keyword arguments that are forwarded to 96 | WTForm's field function that renders the field for us. The keyword 97 | arguments will be inserted as HTML attributes. So for example you can 98 | call ``render_field(form.username, class='username')`` to add a class to 99 | the input element. Note that WTForms returns standard Python unicode 100 | strings, so we have to tell Jinja2 that this data is already HTML escaped 101 | with the `|safe` filter. 102 | 103 | Here the `register.html` template for the function we used above which 104 | takes advantage of the `_formhelpers.html` template: 105 | 106 | .. sourcecode:: html+jinja 107 | 108 | {% from "_formhelpers.html" import render_field %} 109 |
110 |
111 | {{ render_field(form.username) }} 112 | {{ render_field(form.email) }} 113 | {{ render_field(form.password) }} 114 | {{ render_field(form.confirm) }} 115 | {{ render_field(form.accept_tos) }} 116 |
117 |

118 |

119 | 120 | For more information about WTForms, head over to the `WTForms 121 | website`_. 122 | 123 | .. _WTForms: http://wtforms.simplecodes.com/ 124 | .. _WTForms website: http://wtforms.simplecodes.com/ 125 | -------------------------------------------------------------------------------- /docs/security.rst: -------------------------------------------------------------------------------- 1 | Security Considerations 2 | ======================= 3 | 4 | Web applications usually face all kinds of security problems and it's very 5 | hard to get everything right. Flask tries to solve a few of these things 6 | for you, but there are a couple more you have to take care of yourself. 7 | 8 | .. _xss: 9 | 10 | Cross-Site Scripting (XSS) 11 | -------------------------- 12 | 13 | Cross site scripting is the concept of injecting arbitrary HTML (and with 14 | it JavaScript) into the context of a website. To remedy this, developers 15 | have to properly escape text so that it cannot include arbitrary HTML 16 | tags. For more information on that have a look at the Wikipedia article 17 | on `Cross-Site Scripting 18 | `_. 19 | 20 | Flask configures Jinja2 to automatically escape all values unless 21 | explicitly told otherwise. This should rule out all XSS problems caused 22 | in templates, but there are still other places where you have to be 23 | careful: 24 | 25 | - generating HTML without the help of Jinja2 26 | - calling :class:`~flask.Markup` on data submitted by users 27 | - sending out HTML from uploaded files, never do that, use the 28 | `Content-Disposition: attachment` header to prevent that problem. 29 | - sending out textfiles from uploaded files. Some browsers are using 30 | content-type guessing based on the first few bytes so users could 31 | trick a browser to execute HTML. 32 | 33 | Another thing that is very important are unquoted attributes. While 34 | Jinja2 can protect you from XSS issues by escaping HTML, there is one 35 | thing it cannot protect you from: XSS by attribute injection. To counter 36 | this possible attack vector, be sure to always quote your attributes with 37 | either double or single quotes when using Jinja expressions in them: 38 | 39 | .. sourcecode:: html+jinja 40 | 41 | the text 42 | 43 | Why is this necessary? Because if you would not be doing that, an 44 | attacker could easily inject custom JavaScript handlers. For example an 45 | attacker could inject this piece of HTML+JavaScript: 46 | 47 | .. sourcecode:: html 48 | 49 | onmouseover=alert(document.cookie) 50 | 51 | When the user would then move with the mouse over the link, the cookie 52 | would be presented to the user in an alert window. But instead of showing 53 | the cookie to the user, a good attacker might also execute any other 54 | JavaScript code. In combination with CSS injections the attacker might 55 | even make the element fill out the entire page so that the user would 56 | just have to have the mouse anywhere on the page to trigger the attack. 57 | 58 | Cross-Site Request Forgery (CSRF) 59 | --------------------------------- 60 | 61 | Another big problem is CSRF. This is a very complex topic and I won't 62 | outline it here in detail just mention what it is and how to theoretically 63 | prevent it. 64 | 65 | If your authentication information is stored in cookies, you have implicit 66 | state management. The state of "being logged in" is controlled by a 67 | cookie, and that cookie is sent with each request to a page. 68 | Unfortunately that includes requests triggered by 3rd party sites. If you 69 | don't keep that in mind, some people might be able to trick your 70 | application's users with social engineering to do stupid things without 71 | them knowing. 72 | 73 | Say you have a specific URL that, when you sent `POST` requests to will 74 | delete a user's profile (say `http://example.com/user/delete`). If an 75 | attacker now creates a page that sends a post request to that page with 76 | some JavaScript they just has to trick some users to load that page and 77 | their profiles will end up being deleted. 78 | 79 | Imagine you were to run Facebook with millions of concurrent users and 80 | someone would send out links to images of little kittens. When users 81 | would go to that page, their profiles would get deleted while they are 82 | looking at images of fluffy cats. 83 | 84 | How can you prevent that? Basically for each request that modifies 85 | content on the server you would have to either use a one-time token and 86 | store that in the cookie **and** also transmit it with the form data. 87 | After receiving the data on the server again, you would then have to 88 | compare the two tokens and ensure they are equal. 89 | 90 | Why does Flask not do that for you? The ideal place for this to happen is 91 | the form validation framework, which does not exist in Flask. 92 | 93 | .. _json-security: 94 | 95 | JSON Security 96 | ------------- 97 | 98 | .. admonition:: ECMAScript 5 Changes 99 | 100 | Starting with ECMAScript 5 the behavior of literals changed. Now they 101 | are not constructed with the constructor of ``Array`` and others, but 102 | with the builtin constructor of ``Array`` which closes this particular 103 | attack vector. 104 | 105 | JSON itself is a high-level serialization format, so there is barely 106 | anything that could cause security problems, right? You can't declare 107 | recursive structures that could cause problems and the only thing that 108 | could possibly break are very large responses that can cause some kind of 109 | denial of service at the receiver's side. 110 | 111 | However there is a catch. Due to how browsers work the CSRF issue comes 112 | up with JSON unfortunately. Fortunately there is also a weird part of the 113 | JavaScript specification that can be used to solve that problem easily and 114 | Flask is kinda doing that for you by preventing you from doing dangerous 115 | stuff. Unfortunately that protection is only there for 116 | :func:`~flask.jsonify` so you are still at risk when using other ways to 117 | generate JSON. 118 | 119 | So what is the issue and how to avoid it? The problem are arrays at 120 | top-level in JSON. Imagine you send the following data out in a JSON 121 | request. Say that's exporting the names and email addresses of all your 122 | friends for a part of the user interface that is written in JavaScript. 123 | Not very uncommon: 124 | 125 | .. sourcecode:: javascript 126 | 127 | [ 128 | {"username": "admin", 129 | "email": "admin@localhost"} 130 | ] 131 | 132 | And it is doing that of course only as long as you are logged in and only 133 | for you. And it is doing that for all `GET` requests to a certain URL, 134 | say the URL for that request is 135 | ``http://example.com/api/get_friends.json``. 136 | 137 | So now what happens if a clever hacker is embedding this to his website 138 | and social engineers a victim to visiting his site: 139 | 140 | .. sourcecode:: html 141 | 142 | 154 | 156 | 160 | 161 | If you know a bit of JavaScript internals you might know that it's 162 | possible to patch constructors and register callbacks for setters. An 163 | attacker can use this (like above) to get all the data you exported in 164 | your JSON file. The browser will totally ignore the ``application/json`` 165 | mimetype if ``text/javascript`` is defined as content type in the script 166 | tag and evaluate that as JavaScript. Because top-level array elements are 167 | allowed (albeit useless) and we hooked in our own constructor, after that 168 | page loaded the data from the JSON response is in the `captured` array. 169 | 170 | Because it is a syntax error in JavaScript to have an object literal 171 | (``{...}``) toplevel an attacker could not just do a request to an 172 | external URL with the script tag to load up the data. So what Flask does 173 | is to only allow objects as toplevel elements when using 174 | :func:`~flask.jsonify`. Make sure to do the same when using an ordinary 175 | JSON generate function. 176 | -------------------------------------------------------------------------------- /docs/shell.rst: -------------------------------------------------------------------------------- 1 | .. _shell: 2 | 3 | Working with the Shell 4 | ====================== 5 | 6 | .. versionadded:: 0.3 7 | 8 | One of the reasons everybody loves Python is the interactive shell. It 9 | basically allows you to execute Python commands in real time and 10 | immediately get results back. Flask itself does not come with an 11 | interactive shell, because it does not require any specific setup upfront, 12 | just import your application and start playing around. 13 | 14 | There are however some handy helpers to make playing around in the shell a 15 | more pleasant experience. The main issue with interactive console 16 | sessions is that you're not triggering a request like a browser does which 17 | means that :data:`~flask.g`, :data:`~flask.request` and others are not 18 | available. But the code you want to test might depend on them, so what 19 | can you do? 20 | 21 | This is where some helper functions come in handy. Keep in mind however 22 | that these functions are not only there for interactive shell usage, but 23 | also for unittesting and other situations that require a faked request 24 | context. 25 | 26 | Generally it's recommended that you read the :ref:`request-context` 27 | chapter of the documentation first. 28 | 29 | Creating a Request Context 30 | -------------------------- 31 | 32 | The easiest way to create a proper request context from the shell is by 33 | using the :attr:`~flask.Flask.test_request_context` method which creates 34 | us a :class:`~flask.ctx.RequestContext`: 35 | 36 | >>> ctx = app.test_request_context() 37 | 38 | Normally you would use the `with` statement to make this request object 39 | active, but in the shell it's easier to use the 40 | :meth:`~flask.ctx.RequestContext.push` and 41 | :meth:`~flask.ctx.RequestContext.pop` methods by hand: 42 | 43 | >>> ctx.push() 44 | 45 | From that point onwards you can work with the request object until you 46 | call `pop`: 47 | 48 | >>> ctx.pop() 49 | 50 | Firing Before/After Request 51 | --------------------------- 52 | 53 | By just creating a request context, you still don't have run the code that 54 | is normally run before a request. This might result in your database 55 | being unavailable if you are connecting to the database in a 56 | before-request callback or the current user not being stored on the 57 | :data:`~flask.g` object etc. 58 | 59 | This however can easily be done yourself. Just call 60 | :meth:`~flask.Flask.preprocess_request`: 61 | 62 | >>> ctx = app.test_request_context() 63 | >>> ctx.push() 64 | >>> app.preprocess_request() 65 | 66 | Keep in mind that the :meth:`~flask.Flask.preprocess_request` function 67 | might return a response object, in that case just ignore it. 68 | 69 | To shutdown a request, you need to trick a bit before the after request 70 | functions (triggered by :meth:`~flask.Flask.process_response`) operate on 71 | a response object: 72 | 73 | >>> app.process_response(app.response_class()) 74 | 75 | >>> ctx.pop() 76 | 77 | The functions registered as :meth:`~flask.Flask.teardown_request` are 78 | automatically called when the context is popped. So this is the perfect 79 | place to automatically tear down resources that were needed by the request 80 | context (such as database connections). 81 | 82 | 83 | Further Improving the Shell Experience 84 | -------------------------------------- 85 | 86 | If you like the idea of experimenting in a shell, create yourself a module 87 | with stuff you want to star import into your interactive session. There 88 | you could also define some more helper methods for common things such as 89 | initializing the database, dropping tables etc. 90 | 91 | Just put them into a module (like `shelltools` and import from there): 92 | 93 | >>> from shelltools import * 94 | -------------------------------------------------------------------------------- /docs/styleguide.rst: -------------------------------------------------------------------------------- 1 | Pocoo Styleguide 2 | ================ 3 | 4 | The Pocoo styleguide is the styleguide for all Pocoo Projects, including 5 | Flask. This styleguide is a requirement for Patches to Flask and a 6 | recommendation for Flask extensions. 7 | 8 | In general the Pocoo Styleguide closely follows :pep:`8` with some small 9 | differences and extensions. 10 | 11 | General Layout 12 | -------------- 13 | 14 | Indentation: 15 | 4 real spaces. No tabs, no exceptions. 16 | 17 | Maximum line length: 18 | 79 characters with a soft limit for 84 if absolutely necessary. Try 19 | to avoid too nested code by cleverly placing `break`, `continue` and 20 | `return` statements. 21 | 22 | Continuing long statements: 23 | To continue a statement you can use backslashes in which case you should 24 | align the next line with the last dot or equal sign, or indent four 25 | spaces:: 26 | 27 | this_is_a_very_long(function_call, 'with many parameters') \ 28 | .that_returns_an_object_with_an_attribute 29 | 30 | MyModel.query.filter(MyModel.scalar > 120) \ 31 | .order_by(MyModel.name.desc()) \ 32 | .limit(10) 33 | 34 | If you break in a statement with parentheses or braces, align to the 35 | braces:: 36 | 37 | this_is_a_very_long(function_call, 'with many parameters', 38 | 23, 42, 'and even more') 39 | 40 | For lists or tuples with many items, break immediately after the 41 | opening brace:: 42 | 43 | items = [ 44 | 'this is the first', 'set of items', 'with more items', 45 | 'to come in this line', 'like this' 46 | ] 47 | 48 | Blank lines: 49 | Top level functions and classes are separated by two lines, everything 50 | else by one. Do not use too many blank lines to separate logical 51 | segments in code. Example:: 52 | 53 | def hello(name): 54 | print 'Hello %s!' % name 55 | 56 | 57 | def goodbye(name): 58 | print 'See you %s.' % name 59 | 60 | 61 | class MyClass(object): 62 | """This is a simple docstring""" 63 | 64 | def __init__(self, name): 65 | self.name = name 66 | 67 | def get_annoying_name(self): 68 | return self.name.upper() + '!!!!111' 69 | 70 | Expressions and Statements 71 | -------------------------- 72 | 73 | General whitespace rules: 74 | - No whitespace for unary operators that are not words 75 | (e.g.: ``-``, ``~`` etc.) as well on the inner side of parentheses. 76 | - Whitespace is placed between binary operators. 77 | 78 | Good:: 79 | 80 | exp = -1.05 81 | value = (item_value / item_count) * offset / exp 82 | value = my_list[index] 83 | value = my_dict['key'] 84 | 85 | Bad:: 86 | 87 | exp = - 1.05 88 | value = ( item_value / item_count ) * offset / exp 89 | value = (item_value/item_count)*offset/exp 90 | value=( item_value/item_count ) * offset/exp 91 | value = my_list[ index ] 92 | value = my_dict ['key'] 93 | 94 | Yoda statements are a no-go: 95 | Never compare constant with variable, always variable with constant: 96 | 97 | Good:: 98 | 99 | if method == 'md5': 100 | pass 101 | 102 | Bad:: 103 | 104 | if 'md5' == method: 105 | pass 106 | 107 | Comparisons: 108 | - against arbitrary types: ``==`` and ``!=`` 109 | - against singletons with ``is`` and ``is not`` (eg: ``foo is not 110 | None``) 111 | - never compare something with `True` or `False` (for example never 112 | do ``foo == False``, do ``not foo`` instead) 113 | 114 | Negated containment checks: 115 | use ``foo not in bar`` instead of ``not foo in bar`` 116 | 117 | Instance checks: 118 | ``isinstance(a, C)`` instead of ``type(A) is C``, but try to avoid 119 | instance checks in general. Check for features. 120 | 121 | 122 | Naming Conventions 123 | ------------------ 124 | 125 | - Class names: ``CamelCase``, with acronyms kept uppercase (``HTTPWriter`` 126 | and not ``HttpWriter``) 127 | - Variable names: ``lowercase_with_underscores`` 128 | - Method and function names: ``lowercase_with_underscores`` 129 | - Constants: ``UPPERCASE_WITH_UNDERSCORES`` 130 | - precompiled regular expressions: ``name_re`` 131 | 132 | Protected members are prefixed with a single underscore. Double 133 | underscores are reserved for mixin classes. 134 | 135 | On classes with keywords, trailing underscores are appended. Clashes with 136 | builtins are allowed and **must not** be resolved by appending an 137 | underline to the variable name. If the function needs to access a 138 | shadowed builtin, rebind the builtin to a different name instead. 139 | 140 | Function and method arguments: 141 | - class methods: ``cls`` as first parameter 142 | - instance methods: ``self`` as first parameter 143 | - lambdas for properties might have the first parameter replaced 144 | with ``x`` like in ``display_name = property(lambda x: x.real_name 145 | or x.username)`` 146 | 147 | 148 | Docstrings 149 | ---------- 150 | 151 | Docstring conventions: 152 | All docstrings are formatted with reStructuredText as understood by 153 | Sphinx. Depending on the number of lines in the docstring, they are 154 | laid out differently. If it's just one line, the closing triple 155 | quote is on the same line as the opening, otherwise the text is on 156 | the same line as the opening quote and the triple quote that closes 157 | the string on its own line:: 158 | 159 | def foo(): 160 | """This is a simple docstring""" 161 | 162 | 163 | def bar(): 164 | """This is a longer docstring with so much information in there 165 | that it spans three lines. In this case the closing triple quote 166 | is on its own line. 167 | """ 168 | 169 | Module header: 170 | The module header consists of an utf-8 encoding declaration (if non 171 | ASCII letters are used, but it is recommended all the time) and a 172 | standard docstring:: 173 | 174 | # -*- coding: utf-8 -*- 175 | """ 176 | package.module 177 | ~~~~~~~~~~~~~~ 178 | 179 | A brief description goes here. 180 | 181 | :copyright: (c) YEAR by AUTHOR. 182 | :license: LICENSE_NAME, see LICENSE_FILE for more details. 183 | """ 184 | 185 | Please keep in mind that proper copyrights and license files are a 186 | requirement for approved Flask extensions. 187 | 188 | 189 | Comments 190 | -------- 191 | 192 | Rules for comments are similar to docstrings. Both are formatted with 193 | reStructuredText. If a comment is used to document an attribute, put a 194 | colon after the opening pound sign (``#``):: 195 | 196 | class User(object): 197 | #: the name of the user as unicode string 198 | name = Column(String) 199 | #: the sha1 hash of the password + inline salt 200 | pw_hash = Column(String) 201 | -------------------------------------------------------------------------------- /docs/templating.rst: -------------------------------------------------------------------------------- 1 | .. _templating: 2 | 3 | 模版 4 | ==== 5 | :译者: feichao#zoho.com 6 | 7 | Flask使用Jinja2作为默认模版。你可以使用任意其他的模版来替代它,但是Flask要求必须安装Jinja2。这是为了能让Flask使用更多的扩展。而这些扩展依赖于Jinja2. 8 | 9 | 这篇文章只是简单的介绍了Jinja2是如何与Flask相互配合的。如果你想更多的了解Jinja2这个引擎本身,可以去看 `Jinja2模版的官方文档 `_ 10 | 11 | Jinja安装 12 | --------- 13 | 14 | Flask默认的Jinja配置为: 15 | 16 | - ``.html``, ``.htm``, ``.xml``, ``.xhtml`` 文件默认开启自动转义 17 | - 模版文件可以通过 ``{% autoescaping %}`` 标签来选择是否开启自动转义 18 | - Flask在Jinja2的模版中增加了一些全局变量和辅助方法,它们的值是默认的。 19 | 20 | 标准上下文 21 | ---------- 22 | Jinja2的模版默认存在以下全局变量: 23 | 24 | .. data:: config 25 | :noindex: 26 | 27 | 当前的configuration对象 (:data:`flask.config`) 28 | 29 | .. versionadded:: 0.6 30 | 31 | .. data:: request 32 | :noindex: 33 | 34 | 当前的request对象 (:class:`flask.request`) 35 | 36 | .. data:: session 37 | :noindex: 38 | 39 | 当前的session对象 (:class:`flask.session`) 40 | 41 | .. data:: g 42 | :noindex: 43 | 44 | 用来保存一个request的全局变量(译者:不同的请求有不同的全局变量,g保存的是当前请求的全局变量) (:data:`flask.g`) 45 | 46 | .. function:: url_for 47 | :noindex: 48 | 49 | :func:`flask.url_for` 函数 50 | 51 | .. function:: get_flashed_messages 52 | :noindex: 53 | 54 | :func:`flask.get_flashed_messages` 函数 55 | 56 | .. admonition:: 在Jinja上下文中的行为 57 | 58 | 这些变量属于Jinja的上下文变量,而不是普通的全局变量。它们的区别是上下文变量在导入的模版中默认是不可见的。这样做的原因一部分是因为性能的关系,还有一部分是可以让程序更加的清晰。 59 | 60 | 对使用者来说,这样有什么区别呢?如果你想导入一个宏,它需要访问request对象,那么有两种方法可以实现: 61 | 62 | 1. 将request对象或request对象的某个属性作为一个参数传给导入的宏。 63 | 2. "with context" 的方式来导入宏。 64 | 65 | 像下面这样导入: 66 | 67 | .. sourcecode:: jinja 68 | 69 | {% from '_helpers.html' import my_macro with context %} 70 | 71 | 标准过滤器 72 | ---------- 73 | 74 | Jinja2含有如下过滤器(包含了Jinja2模版引擎自带的): 75 | 76 | .. function:: tojson 77 | :noindex: 78 | 79 | 这个函数是用来将对象转换成JSON格式。如果你要实时的生成JavaScript,那么这个功能是非常实用的。 80 | 要注意不能在 `script` 标签里面进行转义。所以如果你想在 `script` 标签里面使用这个函数,要确保用 ``|safe`` 来关闭自动转义: 81 | 82 | .. sourcecode:: html+jinja 83 | 84 | 87 | 88 | ``|tojson`` 过滤器会自动转义前置的斜杠。 89 | 90 | 控制自动转义 91 | ------------ 92 | 93 | 自动转义就是自动帮你将特殊的字符替换成转义符号。HTML(或者XML, XHTML)的特殊字符有 ``&``, ``>``, ``<``, ``"``, ``'`` 。因为这些字符在文档中有它自己特殊的含义,所以如果你想在文章中使用这些符号,必须将它替换成转义符号。如果不这样做,不仅用户使用不了这些符号,还会导致安全问题。(更多 :ref:`xss`) 94 | 95 | 但是有时候你需要在模版中禁用自动转义。如果你想直接将HTML插入页面,比如将markdown语言转换成HTML,那么你就需要这样做了。 96 | 97 | 有3种方法可以关闭自动转义: 98 | 99 | - 在Python文件中进行转义。先在 :class:`~flask.Markup` 对象中进行转义,然后将它传送给模版。一般推荐使用这个方式。 100 | - 在模版文件中进行转义。通过 ``|safe`` 过滤器来表示字符串是安全的(``{{ myvariable|safe }}``) 101 | - 暂时禁用全局的自动转义功能。 102 | 103 | 要想在模版中禁用全局自动转义功能,可以用 ``{% autoescaping %}`` 语句块: 104 | 105 | .. sourcecode:: html+jinja 106 | 107 | {% autoescaping false %} 108 |

autoescaping is disableed here 109 |

{{ will_not_be_escaped }} 110 | {% endautoescape %} 111 | 112 | 在这么做的时候,要语句块中使用到的变量非常小心。 113 | 114 | 引入过滤器 115 | ---------- 116 | 117 | 如果你想在Jinja2中引入你自己的过滤器,有2种方法可以做到。你可以把他们放在某个应用的 118 | :attr:`~flask.Flask.jinja_env` 对象里面或者用 119 | :meth:`~flask.Flask.template_filter` 装饰器。 120 | 121 | 下面的两个例子都把对象的元素颠倒过来 :: 122 | 123 | @app.template_filter('reverse') 124 | def reverse_filter(s): 125 | return s[::-1] 126 | 127 | def reverse_filter(s): 128 | return s[::-1] 129 | app.jinja_env.filters['reverse'] = reverse_filter 130 | 131 | 在装饰器里,如果你想用函数的名字来做装饰器的名字,那么装饰器参数可以省略。 132 | 133 | 上下文处理器 134 | ------------- 135 | 136 | Flask中的上下文处理器是为了把新的变量自动插入到模版的上下文。上下文处理器在模版被呈现之前运行,它可以把新的值插入到模版中。上下文处理器是一个返回一个字典的函数。字典的键名和键值会与模版中想对应的变量的进行合并 :: 137 | 138 | @app.context_processor 139 | def inject_user(): 140 | return dict(user=g.user) 141 | 142 | 上面的上下文处理器在模版创建了一个 `user` 的变量,它的值是 `g.user` 。这个例子不是很实用,因为 `g` 变量在模版中总是可以访问的,但是它展示了上下文处理器的使用方法。 143 | -------------------------------------------------------------------------------- /docs/testing.rst: -------------------------------------------------------------------------------- 1 | .. _testing: 2 | 3 | 测试Flask应用程序 4 | ========================== 5 | :译者: fermin.yang#gmail.com 6 | 7 | **物未测,必有漏。** 8 | 9 | 这句话其实是我瞎掰的说的不一定对, 不过也没有很超过。未经测试的应用程序的代码很难进行改进,且程序员经常在未经测试的应用程序上面搞很容易抓狂。如果这个应用程序可以自动测试,你就可以安全的作更改且马上可以知道哪里出了问题。 10 | 11 | Flask提供了一种通过暴露Wekzeug测试 :class:`~werkzeug.test.Client` (客户端)且同时处理本地上下文的方法来替你测试你的应用程序。然后你可以将其应用在你喜欢的测试方式里。在这个文档里,我们将使用 :mod:`unittest` 包,这个包是随着Python一起已经预安装好的。 12 | 13 | 要先有应用程序 14 | --------------- 15 | 16 | 首先,我们需要一个应用程序来进行测试;我们将使用 :ref:`tutorial` 作为我们的测试项目。如果你还没有的话,你可以在 `示例项目`_ 里获取代码。 17 | 18 | .. _示例项目: 19 | http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/ 20 | 21 | 测试骨架 22 | -------------------- 23 | 24 | 为了测试这个项目,我们要新增一个模块 (`flaskr_tests.py`) 。 且在那里建立一个 unittest 的骨架:: 25 | 26 | import os 27 | import flaskr 28 | import unittest 29 | import tempfile 30 | 31 | class FlaskrTestCase(unittest.TestCase): 32 | 33 | def setUp(self): 34 | self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() 35 | flaskr.app.config['TESTING'] = True 36 | self.app = flaskr.app.test_client() 37 | flaskr.init_db() 38 | 39 | def tearDown(self): 40 | os.close(self.db_fd) 41 | os.unlink(flaskr.app.config['DATABASE']) 42 | 43 | if __name__ == '__main__': 44 | unittest.main() 45 | 46 | 在 :meth:`~unittest.TestCase.setUp` 方法内的代码会建立一个新的测试客户端并且初始化一个新的数据库。此方法会在测试方法执行前先被调用。为了在测试结束删除建立的数据库,我们选择在 :meth:`~unittest.TestCase.tearDown` 方法内关闭并删除这个数据库文件。此外,在准备过程中配置标记将被激活。他的作用是在处理请求时禁用错误捕捉以便于你能在针对应用程序做测试时得到更详细的错误报告。 47 | 48 | 该测试客户端会提供一个简易的应用程序交互界面。我们可以通过它向应用程序触发测试请求, 测试客户端则会一手掌控所有信息。 49 | 50 | 由于SQLite3是一个基于文件系统的数据库形式,所以我们可以十分容易地使用临时文件的形式来建立一个临时的数据库并对其进行初始化。方法 :func:`~tempfile.mkstemp` 为我们做了两件事: 他返回了一个低级别的文件句柄和一个随机的文件名,后者就是我们使用的数据库文件名。我们只要保持有 `db_fd` 我们就能使用 :func:`os.close` 方法来关闭该文件。 51 | 52 | 如果我们现在运行测试套件,我们应该可以看到如下的输出结果:: 53 | 54 | $ python flaskr_tests.py 55 | 56 | ---------------------------------------------------------------------- 57 | Ran 0 tests in 0.000s 58 | 59 | OK 60 | 61 | 尽管这个测试程序没有执行任何的实际测试,但是从这里我们可以看到我们的flaskr程序没有语法错误,否则在引入应用程序类库时就会抛出异常不再执行了。 62 | 63 | 处女测 64 | -------------- 65 | 66 | 现在是时候来测试应用程序的功能了。我们现在确认一下如果我们访问应用程序的根节点 (``/``),应用程序应显示 "No entries here so far" 。我们在类里添加了一个新的方法来实现这个功能,如下:: 67 | 68 | class FlaskrTestCase(unittest.TestCase): 69 | 70 | def setUp(self): 71 | self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() 72 | self.app = flaskr.app.test_client() 73 | flaskr.init_db() 74 | 75 | def tearDown(self): 76 | os.close(self.db_fd) 77 | os.unlink(flaskr.DATABASE) 78 | 79 | def test_empty_db(self): 80 | rv = self.app.get('/') 81 | assert 'No entries here so far' in rv.data 82 | 83 | 注意我们的测试方法是以 `test` 开头的;这会让 :mod:`unittest` 模块自动将此方法作为测试方法来执行。 84 | 85 | 通过使用 `self.app.get` 我们可以把一个HTTP `GET` 请求通过给定的路径发送到应用程序。返回值是一个 :class:`~flask.Flask.response_class` 对象。 86 | 我们现在可以用 :attr:`~werkzeug.wrappers.BaseResponse.data` 属性来对应用程序进行核查。对应这个例子,我们需要核查 ``'No entries here so far'`` 是输出结果的一部分。 87 | 88 | 再将它执行一次你应该可以看到一次成功的测试结果:: 89 | 90 | $ python flaskr_tests.py 91 | . 92 | ---------------------------------------------------------------------- 93 | Ran 1 test in 0.034s 94 | 95 | OK 96 | 97 | 日志的输入输出 98 | ------------------ 99 | 100 | 关于这个应用程序,其绝大部分功能是供给管理员使用的,所以我们需要一个途径来记录应用程序运行。为了达到这个目的,我们向登录和登出页面发送了一些带有表单数据(用户名和密码)的请求。由于登录登出请求会跳转页面,所以我们告诉客户端要它 `follow_redirects` (跟踪跳转)。 101 | 102 | 在你的 `FlaskrTestCase` 类里添加如下两个方法:: 103 | 104 | def login(self, username, password): 105 | return self.app.post('/login', data=dict( 106 | username=username, 107 | password=password 108 | ), follow_redirects=True) 109 | 110 | def logout(self): 111 | return self.app.get('/logout', follow_redirects=True) 112 | 113 | 现在,我们就可以很方便的通过检查日志查看是否有非法登录的情况。在类里添加一个新的测试方法:: 114 | 115 | def test_login_logout(self): 116 | rv = self.login('admin', 'default') 117 | assert 'You were logged in' in rv.data 118 | rv = self.logout() 119 | assert 'You were logged out' in rv.data 120 | rv = self.login('adminx', 'default') 121 | assert 'Invalid username' in rv.data 122 | rv = self.login('admin', 'defaultx') 123 | assert 'Invalid password' in rv.data 124 | 125 | 测试添加功能 126 | -------------------- 127 | 128 | 我们同时还需要测试添加消息的功能是否正常。再添加一个新的测试方法,像这样:: 129 | 130 | def test_messages(self): 131 | self.login('admin', 'default') 132 | rv = self.app.post('/add', data=dict( 133 | title='', 134 | text='HTML allowed here' 135 | ), follow_redirects=True) 136 | assert 'No entries here so far' not in rv.data 137 | assert '<Hello>' in rv.data 138 | assert 'HTML allowed here' in rv.data 139 | 140 | 这里,我们测试了HTML语法只能在内容里使用,而标题里不行。结果和预想的一样。 141 | 142 | 运行测试我们应该可以得到三条通过的测试结果:: 143 | 144 | $ python flaskr_tests.py 145 | ... 146 | ---------------------------------------------------------------------- 147 | Ran 3 tests in 0.332s 148 | 149 | OK 150 | 151 | 对于那些更复杂的注入带有头和状态代码的测试,你可以在Flask的源码包里找到`MiniTwit Example`_ 项目,里面有更多更大型的测试用例。 152 | 153 | 154 | .. _MiniTwit Example: 155 | http://github.com/mitsuhiko/flask/tree/master/examples/minitwit/ 156 | 157 | 158 | 其他测试技巧 159 | -------------------- 160 | 161 | 除了使用上述的测试客户端意外,还可以通过使用方法 :meth:`~flask.Flask.test_request_context` ,将其和 `with` 语句组合可以产生一个临时的请求上下文。通过此功能你可以像在视图功能里一样访问这些类 :class:`~flask.request`,:class:`~flask.g` 和 :class:`~flask.session` 。这里有一个使用此方法的完整例子:: 162 | 163 | app = flask.Flask(__name__) 164 | 165 | with app.test_request_context('/?name=Peter'): 166 | assert flask.request.path == '/' 167 | assert flask.request.args['name'] == 'Peter' 168 | 169 | 所有其他上下文约束的对象都可以使用相同的方法。 170 | 171 | 如果你想要在不同的配置环境下测试应用程序,看起来好像没有什么好办法,可以考虑切换到应用程序工厂模式,(可查阅 :ref:`app-factories`). 172 | 173 | 注意不管你是否使用测试请求上下文,方法 :meth:`~flask.Flask.before_request` 在方法:meth:`~flask.Flask.after_request` 被执行之前不一定会被执行。然而方法:meth:`~flask.Flask.teardown_request` 在测试方法离开 `with` 语块时一定会被执行。 如果你确实希望方法 :meth:`~flask.Flask.before_request` 也被执行的话, 你需要自行调用:meth:`~flask.Flask.preprocess_request` 方法:: 174 | 175 | app = flask.Flask(__name__) 176 | 177 | with app.test_request_context('/?name=Peter'): 178 | app.preprocess_request() 179 | ... 180 | 181 | 在打开数据库连接或做类似的工作时,这一步就显得十分必要。这取决于你是如何设计你的应用程序的。 182 | 183 | 保持现场 184 | -------------------------- 185 | 186 | .. versionadded:: 0.4 187 | 188 | 有时候我们需要触发一个常规的请求后将上下文现场保持一个较长的时间,以便于触发更多的内部检查。 有了 Flask 0.4 或以上版本,通过使用方法 :meth:`~flask.Flask.test_client` 并加上 `with` 语块就可以做到了:: 189 | 190 | app = flask.Flask(__name__) 191 | 192 | with app.test_client() as c: 193 | rv = c.get('/?tequila=42') 194 | assert request.args['tequila'] == '42' 195 | 196 | 如果你使用了方法 :meth:`~flask.Flask.test_client` 但是没有加上 `with` 语块, `assert` 语句会报错。这是因为这里的 `request` 不可用 (因为此操作在实际请求之外).不管如何, 记住任何 :meth:`~flask.Flask.after_request` 方法在此时已经执行,所以你的数据库连接和其他所有操作可能已经被关闭了。 197 | -------------------------------------------------------------------------------- /docs/tutorial/css.rst: -------------------------------------------------------------------------------- 1 | .. _tutorial-css: 2 | 3 | 第七步: 添加样式 4 | ==================== 5 | 6 | 现在其他的东西都能工作了,是时候来给我们的应用添加一点样式了。 7 | 我们先前创建了 `static` 文件夹,在这里面新建一个css文件 `style.css` : 8 | 9 | .. sourcecode:: css 10 | 11 | body { font-family: sans-serif; background: #eee; } 12 | a, h1, h2 { color: #377BA8; } 13 | h1, h2 { font-family: 'Georgia', serif; margin: 0; } 14 | h1 { border-bottom: 2px solid #eee; } 15 | h2 { font-size: 1.2em; } 16 | 17 | .page { margin: 2em auto; width: 35em; border: 5px solid #ccc; 18 | padding: 0.8em; background: white; } 19 | .entries { list-style: none; margin: 0; padding: 0; } 20 | .entries li { margin: 0.8em 1.2em; } 21 | .entries li h2 { margin-left: -1em; } 22 | .add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; } 23 | .add-entry dl { font-weight: bold; } 24 | .metanav { text-align: right; font-size: 0.8em; padding: 0.3em; 25 | margin-bottom: 1em; background: #fafafa; } 26 | .flash { background: #CEE5F5; padding: 0.5em; 27 | border: 1px solid #AACBE2; } 28 | .error { background: #F0D6D6; padding: 0.5em; } 29 | 30 | 继续 :ref:`tutorial-testing`. 31 | -------------------------------------------------------------------------------- /docs/tutorial/dbcon.rst: -------------------------------------------------------------------------------- 1 | .. _tutorial-dbcon: 2 | 3 | 第四步:请求数据库连接 4 | ---------------------- 5 | 6 | 现在,我们知道了如何来创建一个数据库连接,如何来执行脚本,但是我们如何能优 7 | 雅的为每一次的请求创建连接?数据库连接在所以的函数中都是需要的,所以能自动 8 | 在请求之前初始化,请求结束后关闭就显得很有意义。 9 | 10 | Flask提供了 :meth:`~flask.Flask.after_request` 和 11 | :meth:`~flask.Flask.before_request` 装饰器来让我们做到这一点:: 12 | 13 | @app.before_request 14 | def before_request(): 15 | g.db = connect_db() 16 | 17 | @app.after_request 18 | def after_request(response): 19 | g.db.close() 20 | return response 21 | 22 | 用 :meth:`~flask.Flask.before_request` 装饰的函数在每次请求之前 23 | 被调用,它没有参数。用 :meth:`~flask.Flask.after_request` 装饰的函数是在每 24 | 次请求结束后被调用,而且它需要传入response。这类函数必须返回同一个response 25 | 对象或者一个不同的response对象。在这里,我们不对response做修改,返回同一个 26 | 对象。 27 | 28 | 我们把当前的数据库连接保存在一个特殊的对象 :data:`~flask.g` 里面,这个对象 29 | flask已经为我们提供了。这个对象只能用来为一个请求保存信息,每一个函数都可以 30 | 访问这个对象。不要用其他的对象来保存信息,因为在多线程的环境下会无法工作。 31 | :data:`~flask.g` 对象是一个特殊的对象,它会在后台做一些魔术来确保它能够跟我 32 | 们预想的一样执行。 33 | 34 | 继续 :ref:`tutorial-views`. 35 | -------------------------------------------------------------------------------- /docs/tutorial/dbinit.rst: -------------------------------------------------------------------------------- 1 | .. _tutorial-dbinit: 2 | 3 | 第三步: 创建一个数据库 4 | ======================= 5 | 6 | 我们原来就说过,Flask是一个数据去驱动的应用,更准确的来说,是一个基于关系数 7 | 据库的应用。这样的系统需要一个模式来决定怎么去存储信息。所以在第一次开启服 8 | 务器前就把模式创建好很重要。 9 | 10 | 这个模式可以通过管道的方式把 `schema.sql` 输入到 `sqlite3` 命令中,如下所示 11 | :: 12 | 13 | sqlite3 /tmp/flaskr.db < schema.sql 14 | 15 | 这种方法的缺点是需要安装sqlite3命令,但是并不是每一个系统中都有安装。而且你 16 | 必须给出数据库的路径,否则就会出错。添加一个函数来对数据库进行初始化是一个 17 | 不错的想法。 18 | 19 | 如果你想这么做,首先要从contextlib package中import 20 | :func:`contextlib.closing` 函数。如果你想用Python 2.5,那么还需要开启 21 | `with` 声明,(从 `_future_` 中的import内容要在所以import的最前面):: 22 | 23 | from __future__ import with_statement 24 | from contextlib import closing 25 | 26 | 下面我们创建一个叫 `init_db` 的函数来初始化数据库。在这里,我们可以使用前面 27 | 定义的 `connect_db` 函数。只需要把这个函数添加到 `connect_db` 函数的下面:: 28 | 29 | def init_db(): 30 | with closing(connect_db()) as db: 31 | with app.open_resource('schema.sql') as f: 32 | db.cursor().executescript(f.read()) 33 | db.commit() 34 | 35 | 通过 :func: `~contextlib.closing` 辅助函数,我们可以在 `with` 模块中保持数 36 | 据库连接。applicationg对象的 :func:`~flask.Flask.open_resource` 方法支持也 37 | 支持这个功能,所以我们可以在 `with` 模块中直接使用它。这个函数用来从这个应 38 | 用的所在位置( `flaskr` 目录)打开一个文件,然后允许你通过它来读取文件。我 39 | 们在这里使用这个函数是为了在数据库连接上执行一个脚本。 40 | 41 | 当你连接到数据库后,我们就得到了一个连接对象(这里我们把它叫做 `db` ),这 42 | 个对象会给我们提供一个指针。这个指针有一个方法可以来执行完整的数据库命令。 43 | 最后,我们还要来提交我们对数据库所做的改变。如果你不明确的来提交修改, 44 | SQLite3和其他的事务数据库都不会自动提交这些修改。 45 | 46 | 现在我们可以打开一个Pythonshell,然后import函数,调用函数。这样就能创建一个 47 | 数据库了:: 48 | 49 | >>> from flaskr import init_db 50 | >>> init_db() 51 | 52 | .. admonition:: Troubleshooting 53 | 54 | 如果你得到了一个表无法被找到的异常,检查下,你是否调用了 `init_db` ,而 55 | 且你表的名字是正确的(单数 复数问题)。 56 | 57 | 继续 :ref:`tutorial-dbcon` 58 | -------------------------------------------------------------------------------- /docs/tutorial/folders.rst: -------------------------------------------------------------------------------- 1 | .. _tutorial-folders: 2 | 3 | 初始准备: 创建目录 4 | ================== 5 | 6 | 在我们开始之前,让我们先创建应用所需的目录 :: 7 | 8 | /flaskr 9 | /static 10 | /templates 11 | 12 | `flask` 13 | 目录不是python的package,只是我们放文件的地方。我们将要把我们在以后步骤 14 | 中用到的数据库模式和主要的模块放在这个目录中。 `static` 目录可以被网络 15 | 上的用户通过 `HTTP` 访问。css和javascript文件就存放在这个目录下。 16 | Flask在 `template` 下查找 `jinja2`_ 的模版文件。把所有的模版文件放在这个 17 | 目录下。 18 | 19 | 继续 :ref:`tutorial-schema`. 20 | 21 | .. _Jinja2: http://jinja.pocoo.org/2/ 22 | -------------------------------------------------------------------------------- /docs/tutorial/index.rst: -------------------------------------------------------------------------------- 1 | .. _tutorial: 2 | 3 | 教程 4 | ===== 5 | 6 | 想用Python和Flask来开发网络应用吗?那么你可以通过例子来学习。在这个tutorial 7 | 里面,我们会创建一个精简的博客程序。它只支持一个用户创建文章,而且不支持 8 | feed和评论。虽然很简单,但是这个博客还是包含了你需要开始学习的所有的东西。 9 | 我们将使用Flask,而数据库则采用SQLite。SQLite包含在python中,所以我们其他什 10 | 么都不需要了。 11 | 12 | 如果你想要提前看到全部的源代码,或者来与自己写的做一个比较,可以查看 13 | `example source`_. 14 | 15 | .. _example source: 16 | http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/ 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | 21 | introduction 22 | folders 23 | schema 24 | setup 25 | dbinit 26 | dbcon 27 | views 28 | templates 29 | css 30 | testing 31 | -------------------------------------------------------------------------------- /docs/tutorial/introduction.rst: -------------------------------------------------------------------------------- 1 | .. _tutorial-introduction: 2 | 3 | 介绍 Flaskr 4 | =========== 5 | 6 | 这里我们把我们的blog程序叫做flaskr,你可以选一个不那么web 2.0的名字 ;) 7 | 基本上我们想让它晚餐如下的功能 : 8 | 9 | 1. 根据配置文件里面的认证信息让用户登陆登出。只支持一个用户 10 | 2. 用户登陆后,可以向页面添加文章,题目只能是纯文字,内容可以使用一部分 11 | 的HTML语言。这里我们假设用户是可信任的,所以对输入的HTML不会进行处理 12 | 3. 页面以倒序的顺序(后发布的在上方),在一个页面中显示所有的文章。用户 13 | 登陆后可以添加新文章。 14 | 15 | 我们为我们的应用选择SQLite3因为它对这种大小的应用足够了。但是更大的应用 16 | 就很有必要使用 `SQLAlchemy`_ ,它更加智能的处理数据库连接,通过它可以一 17 | 次连接到不同的关系数据库而且可以做到更多。你也可以考虑使用最流行NoSQL数 18 | 据库之一如果你的数据更加适合这类数据库。 19 | 20 | 这是来自最终应用的一个截图: 21 | 22 | .. image:: ../_static/flaskr.png 23 | :align: center 24 | :class: screenshot 25 | :alt: screenshot of the final application 26 | 27 | 继续 :ref:`tutorial-folders`. 28 | 29 | .. _SQLAlchemy: http://www.sqlalchemy.org/ 30 | -------------------------------------------------------------------------------- /docs/tutorial/schema.rst: -------------------------------------------------------------------------------- 1 | .. _tutorial-schema: 2 | 3 | 第一步: 数据库模式 4 | ================== 5 | 6 | 首先我们要创建数据库模式。对于这个应用一个表就足够了,而且我们只需要支持 7 | SQLite,所以很简单。只要把下面的内容放入一个叫 `schema.sql` 的文件中,这 8 | 个文件应存放在 `flaskr` 文件夹中: 9 | 10 | .. sourcecode:: sql 11 | 12 | drop table if exists entries; 13 | create table entries ( 14 | id integer primary key autoincrement, 15 | title string not null, 16 | text string not null 17 | ); 18 | 19 | 这个模式由一个叫 `entries` 的表组成,表里面的每一行都有 `id` `title` 20 | `text` 字段。 `id` 是一个自动增加的整数,而且它是主键,其他的两个是字符 21 | 串,而且不能为null 22 | 23 | 继续 :ref:`tutorial-setup`. 24 | -------------------------------------------------------------------------------- /docs/tutorial/setup.rst: -------------------------------------------------------------------------------- 1 | .. _例程构建: 2 | 3 | 第二步: 应用程序构建代码 4 | ============================== 5 | 6 | 现在我们已经准备好了模式,终于可以创建应用程序的模块了。让我们把他叫做 7 | `flaskr.py` ,并把它放在 `flaskr` 目录下。首先,我们把需要的模块和配置 8 | 导入。如果是小应用的话,可以直接把配置放在主模块里面,就跟我们将要做的 9 | 一样。但是一个更加清晰的方案是创建一个独立的 `.ini` 或 `.py` 文件,然 10 | 后导入或装载到主模块中。 11 | 12 | :: 13 | 14 | # all the imports 15 | import sqlite3 16 | from flask import Flask, request, session, g, redirect, url_for, \ 17 | abort, render_template, flash 18 | 19 | # configuration 20 | DATABASE = '/tmp/flaskr.db' 21 | DEBUG = True 22 | SECRET_KEY = 'development key' 23 | USERNAME = 'admin' 24 | PASSWORD = 'default' 25 | 26 | 下一步我们要创建真正的应用,然后用同一个文件中的配置来初始化:: 27 | 28 | # create our little application :) 29 | app = Flask(__name__) 30 | app.config.from_object(__name__) 31 | 32 | :meth:`~flask.Config.from_object` 会识别给出的对象(如果是一个字符串,它 33 | 会自动导入这个模块),然后查找所有已定义的大写变量。在我们这个例子里,配置 34 | 在几行代码前。你也可以把它移动到一个单独的文件中。 35 | 36 | 从配置文件中读取配置也是一个好方法。:meth:`~flask.Config.from_envvar` 就是 37 | 用来做这个事情的:: 38 | 39 | app.config.from_envvar('FLASKR_SETTINGS', silent=True) 40 | 41 | 通过那种方法,就可以设置环境变量 :envvar:`FLASKR_SETTINGS` 来装载指定的配 42 | 置文件,装载后会覆盖默认值。silent参数是为了告诉Flask不要报错,即使没有设置 43 | 环境变量。 44 | 45 | 我们需要设置 `secret_key` 来确保客户端Session的安全。合理的设置这个值,而且 46 | 越复杂越好。Debug标志用来指示是否开启交互debugger。永远不要在生产环境下开始 47 | debug标志,因为这样会允许用户在服务器上执行代码! 48 | 49 | 我们还添加了一个方法来快速的连接到指定的数据库。这个方法不仅可以在有用户请 50 | 求时打开一个连接,还可以在交互的Python shell和脚本中使用。这对以后会很方便。 51 | 52 | :: 53 | 54 | def connect_db(): 55 | return sqlite3.connect(app.config['DATABASE']) 56 | 57 | 最后如果我们想把那个文件当做一个独立的应用来运行,我们需要在文件的末尾加 58 | 一行代码来开启服务器 :: 59 | 60 | if __name__ == '__main__': 61 | app.run() 62 | 63 | 现在我们应该可以顺利的运行这个应用了。如果你访问服务器,你会得到一个404, 64 | 页面没有找到的错误,因为我们还没有创建任何视图。但是我们会在后面再提到它。 65 | 首先,我们应该要先让数据库跑起来。 66 | 67 | .. admonition:: 让服务器可以被外部访问 68 | 69 | 你想然你的服务器被外部访问吗?查看 70 | :ref:`externally visible server ` 部分来获取更多的信息 71 | 72 | 继续 :ref:`tutorial-dbinit`. 73 | -------------------------------------------------------------------------------- /docs/tutorial/templates.rst: -------------------------------------------------------------------------------- 1 | .. _tutorial-templates: 2 | 3 | 第六步: 模版 4 | ===================== 5 | 6 | 现在我们可以开始制作我们的网页模版了。如果我们现在访问URL,我们会得到一个 7 | “Flask无法找到模版文件”的异常。我们的模版将使用 `Jinja2`_ 的格式,而且默 8 | 认是打开自动转义的。这也就是说,除非我们在代码中用 :class:`~flask.Markup` 9 | 标记一个值,或者在模版中用 ``|safe`` 过滤器,否则Jinja2会将一些特殊字符, 10 | 如 ``<`` 或 ``>`` 用XML格式来转义。 11 | 12 | 我们将使用模版继承机制来使所有的页面使用同一个布局。 13 | 14 | 把以下的模版放在 `template` 目录下: 15 | 16 | .. _Jinja2: http://jinja.pocoo.org/2/documentation/templates 17 | 18 | layout.html 19 | ----------- 20 | 21 | 这个模版包含了HTML的主要结构,标题和一个登陆的链接(或者登出如果用户已经登 22 | 陆)。它还负责显示flashed messages。 ``{% block body %}`` 可以被子模版的相 23 | 同名字( ``body`` )的结构所替换 24 | 25 | :class:`~flask.session` 字典在模版中也是可以访问的。所以你可以用session来 26 | 检查用户是否已登陆。注意在Jinja中,你可以访问对象或字典的未使用过的属性和 27 | 成员。就如下面的代码一样,即使session中没有 ``'logged_in'`` : 28 | 29 | .. sourcecode:: html+jinja 30 | 31 | 32 | Flaskr 33 | 34 |

35 |

Flaskr

36 |
37 | {% if not session.logged_in %} 38 | log in 39 | {% else %} 40 | log out 41 | {% endif %} 42 |
43 | {% for message in get_flashed_messages() %} 44 |
{{ message }}
45 | {% endfor %} 46 | {% block body %}{% endblock %} 47 |
48 | 49 | show_entries.html 50 | ----------------- 51 | 52 | 这个模版继承自上面的 `layout.html` ,来显示文章。 `for` 循环遍历所有的文章。 53 | 我们通过 :func:`~flask.render_template` 来传入参数。我们还告诉表单使用 54 | `HTTP` 的 `POST` 方法提交到 `add_entry` 函数: 55 | 56 | .. sourcecode:: html+jinja 57 | 58 | {% extends "layout.html" %} 59 | {% block body %} 60 | {% if session.logged_in %} 61 |
62 |
63 |
Title: 64 |
65 |
Text: 66 |
67 |
68 |
69 |
70 | {% endif %} 71 |
    72 | {% for entry in entries %} 73 |
  • {{ entry.title }}

    {{ entry.text|safe }} 74 | {% else %} 75 |
  • Unbelievable. No entries here so far 76 | {% endfor %} 77 |
78 | {% endblock %} 79 | 80 | login.html 81 | ---------- 82 | 83 | 最后是登陆页面的模版。它仅仅是显示一个表单来允许用户登陆: 84 | 85 | .. sourcecode:: html+jinja 86 | 87 | {% extends "layout.html" %} 88 | {% block body %} 89 |

Login

90 | {% if error %}

Error: {{ error }}{% endif %} 91 |

92 |
93 |
Username: 94 |
95 |
Password: 96 |
97 |
98 |
99 |
100 | {% endblock %} 101 | 102 | 继续 :ref:`tutorial-css`. 103 | -------------------------------------------------------------------------------- /docs/tutorial/testing.rst: -------------------------------------------------------------------------------- 1 | .. _tutorial-testing: 2 | 3 | 附加: 自动测试 4 | ============================== 5 | 6 | 由于你已经完成了整个应用,而且一切都运行的很完美,所以从将来修改的角度 7 | 看,添加自动测试代码也许不是一个好主意。文档:ref:`testing` 区域中以上 8 | 面的应用为例子演示了如何进行自动单元测试。你可以去看看测试Flask应用是 9 | 多么简单的一件事。 10 | -------------------------------------------------------------------------------- /docs/tutorial/views.rst: -------------------------------------------------------------------------------- 1 | .. _tutorial-views: 2 | 3 | 第五步: 视图函数 4 | ================ 5 | 6 | 现在数据库连接已经可以工作了,我们终于可以开始写我们的视图函数了。我们一共 7 | 需要写4个: 8 | 9 | 显示文章 10 | -------- 11 | 12 | 这个视图将会显示数据库中所有的文章。它会绑定在应用的根地址,并且从数据库中 13 | 查询出文章的标题和内容。最新发表的文章会显示在最上方。从cursor返回的数据是 14 | 存放在一个tuple中,而且以select语句中的指定的顺序排序。对我们这个小应用来说 15 | tuple已经满足要求了,但是也许你想把它转换成dict。那么,你可以参考 16 | :ref:`easy-querying` 的示例。 17 | 18 | 视图函数会把所有的文章以字典的方式传送给 `show_entries.html` 19 | 模版,然后向浏览器返回render过的:: 20 | 21 | @app.route('/') 22 | def show_entries(): 23 | cur = g.db.execute('select title, text from entries order by id desc') 24 | entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()] 25 | return render_template('show_entries.html', entries=entries) 26 | 27 | 添加一篇新文章 28 | -------------- 29 | 30 | 这个视图用来让已登陆的用户发表新文章。它只对以 `POST` 31 | 方式提交的请求回应,实际的表单显示在 `show_entries` 32 | 页面上。如果一切都没有出问题的话,我们用 `~flask.flash` 33 | 向下一次请求发送一条信息,然后重定向回 `show_entries` 页面:: 34 | 35 | @app.route('/add', methods=['POST']) 36 | def add_entry(): 37 | if not session.get('logged_in'): 38 | abort(401) 39 | g.db.execute('insert into entries (title, text) values (?, ?)', 40 | [request.form['title'], request.form['text']]) 41 | g.db.commit() 42 | flash('New entry was successfully posted') 43 | return redirect(url_for('show_entries')) 44 | 45 | 注意,我们在这里检查了用户是否已经登陆( `logged_in` 46 | 键在session中存在,而且值为 `True` )。 47 | 48 | .. admonition:: Security Note 49 | 50 | Be sure to use question marks when building SQL statements, as done in the 51 | example above. Otherwise, your app will be vulnerable to SQL injection when 52 | you use string formatting to build SQL statements. 53 | See :ref:`sqlite3` for more. 54 | 55 | 登陆和登出 56 | ---------- 57 | 58 | 这些函数是用来让用户登陆和注销的。登陆函数会检查用户名和秘密,并和配置文件 59 | 中的数据进行比较,并相应的设置session中的 `logged_in` 60 | 键。如果用户登陆成功,那么这个键会被设置成 `True` ,然后用户会被重定向到 61 | `show_entries` 页面。并且还会flash一条消息来提示用户登陆成功。如果登陆发生 62 | 错误,那么模版会得知这一点,然后提示用户重新登陆:: 63 | 64 | @app.route('/login', methods=['GET', 'POST']) 65 | def login(): 66 | error = None 67 | if request.method == 'POST': 68 | if request.form['username'] != app.config['USERNAME']: 69 | error = 'Invalid username' 70 | elif request.form['password'] != app.config['PASSWORD']: 71 | error = 'Invalid password' 72 | else: 73 | session['logged_in'] = True 74 | flash('You were logged in') 75 | return redirect(url_for('show_entries')) 76 | return render_template('login.html', error=error) 77 | 78 | 注销函数所作的正好相反。它从session中删除 `logged_in` 键。我们在这里使用的 79 | 一个简洁的小技巧:如果你在使用字典的 :meth:`~dict.pop` 方法时,给了它第二个 80 | 参数(默认),那么这个方法在处理的时候,会先查询是否存在这个键,如果存在, 81 | 则删除它,如果不存在,那么什么都不做。这个特性很有用,因为这样我们在处理的 82 | 时候,就不需要先检查用户是否已登陆。 83 | 84 | :: 85 | 86 | @app.route('/logout') 87 | def logout(): 88 | session.pop('logged_in', None) 89 | flash('You were logged out') 90 | return redirect(url_for('show_entries')) 91 | 92 | 继续 :ref:`tutorial-templates`. 93 | -------------------------------------------------------------------------------- /docs/unicode.rst: -------------------------------------------------------------------------------- 1 | Flask中的Unicode 2 | ================= 3 | 4 | Flask就像Jinja2和Werkzeug那样,是完全基于Unicode的。 5 | 不只是这几个库,绝大多数的Python中网络相关的库都是这样处理文本的。 6 | 如果你对Unicode还不够了解,你可以阅读这篇文章 `The Absolute Minimum Every Software Developer 7 | Absolutely, Positively Must Know About Unicode and Character Sets 8 | `_ 。 9 | 这部分文档只是希望能帮助你了解一些基础知识,这样你就可以处理一些与Unicode有关的东西了。 10 | 11 | 自动转换 12 | -------------------- 13 | 14 | Flask默认基于以下一些假设(当然,你可以更改),给予你一些基础的、简单的Unicode支持: 15 | 16 | - 你的网站的源代码使用的是UTF-8编码。 17 | - 在程序内部你将始终使用Unicode,除非对于只有ASCII编码的字符的字符串。 18 | - 编码、解码只会发生在你需要用某种协议传输字节(bytes)形式的数据。 19 | 20 | 这对于你有什么用呢? 21 | 22 | HTTP协议基于字节码(bytes)。 23 | 不仅是协议, 24 | 也用于标记文件在服务器位置(即URI、URL)的系统中。 25 | 26 | However HTML which 27 | is usually transmitted on top of HTTP supports a large variety of 28 | character sets and which ones are used, are transmitted in an HTTP header. 29 | To not make this too complex Flask just assumes that if you are sending 30 | Unicode out you want it to be UTF-8 encoded. Flask will do the encoding 31 | and setting of the appropriate headers for you. 32 | 33 | The same is true if you are talking to databases with the help of 34 | SQLAlchemy or a similar ORM system. Some databases have a protocol that 35 | already transmits Unicode and if they do not, SQLAlchemy or your other ORM 36 | should take care of that. 37 | 38 | The Golden Rule 39 | --------------- 40 | 41 | So the rule of thumb: if you are not dealing with binary data, work with 42 | Unicode. What does working with Unicode in Python 2.x mean? 43 | 44 | - as long as you are using ASCII charpoints only (basically numbers, 45 | some special characters of latin letters without umlauts or anything 46 | fancy) you can use regular string literals (``'Hello World'``). 47 | - if you need anything else than ASCII in a string you have to mark 48 | this string as Unicode string by prefixing it with a lowercase `u`. 49 | (like ``u'Hänsel und Gretel'``) 50 | - if you are using non-Unicode characters in your Python files you have 51 | to tell Python which encoding your file uses. Again, I recommend 52 | UTF-8 for this purpose. To tell the interpreter your encoding you can 53 | put the ``# -*- coding: utf-8 -*-`` into the first or second line of 54 | your Python source file. 55 | - Jinja is configured to decode the template files from UTF-8. So make 56 | sure to tell your editor to save the file as UTF-8 there as well. 57 | 58 | Encoding and Decoding Yourself 59 | ------------------------------ 60 | 61 | If you are talking with a filesystem or something that is not really based 62 | on Unicode you will have to ensure that you decode properly when working 63 | with Unicode interface. So for example if you want to load a file on the 64 | filesystem and embed it into a Jinja2 template you will have to decode it 65 | from the encoding of that file. Here the old problem that text files do 66 | not specify their encoding comes into play. So do yourself a favour and 67 | limit yourself to UTF-8 for text files as well. 68 | 69 | Anyways. To load such a file with Unicode you can use the built-in 70 | :meth:`str.decode` method:: 71 | 72 | def read_file(filename, charset='utf-8'): 73 | with open(filename, 'r') as f: 74 | return f.read().decode(charset) 75 | 76 | To go from Unicode into a specific charset such as UTF-8 you can use the 77 | :meth:`unicode.encode` method:: 78 | 79 | def write_file(filename, contents, charset='utf-8'): 80 | with open(filename, 'w') as f: 81 | f.write(contents.encode(charset)) 82 | 83 | Configuring Editors 84 | ------------------- 85 | 86 | Most editors save as UTF-8 by default nowadays but in case your editor is 87 | not configured to do this you have to change it. Here some common ways to 88 | set your editor to store as UTF-8: 89 | 90 | - Vim: put ``set enc=utf-8`` to your ``.vimrc`` file. 91 | 92 | - Emacs: either use an encoding cookie or put this into your ``.emacs`` 93 | file:: 94 | 95 | (prefer-coding-system 'utf-8) 96 | (setq default-buffer-file-coding-system 'utf-8) 97 | 98 | - Notepad++: 99 | 100 | 1. Go to *Settings -> Preferences ...* 101 | 2. Select the "New Document/Default Directory" tab 102 | 3. Select "UTF-8 without BOM" as encoding 103 | 104 | It is also recommended to use the Unix newline format, you can select 105 | it in the same panel but this is not a requirement. 106 | -------------------------------------------------------------------------------- /docs/views.rst: -------------------------------------------------------------------------------- 1 | .. _views: 2 | 3 | Pluggable Views 4 | =============== 5 | 6 | .. versionadded:: 0.7 7 | 8 | Flask 0.7 introduces pluggable views inspired by the generic views from 9 | Django which are based on classes instead of functions. The main 10 | intention is that you can replace parts of the implementations and this 11 | way have customizable pluggable views. 12 | 13 | Basic Principle 14 | --------------- 15 | 16 | Consider you have a function that loads a list of objects from the 17 | database and renders into a template:: 18 | 19 | @app.route('/users/') 20 | def show_users(page): 21 | users = User.query.all() 22 | return render_template('users.html', users=users) 23 | 24 | This is simple and flexible, but if you want to provide this view in a 25 | generic fashion that can be adapted to other models and templates as well 26 | you might want more flexibility. This is where pluggable class based 27 | views come into place. As the first step to convert this into a class 28 | based view you would do this:: 29 | 30 | 31 | from flask.views import View 32 | 33 | class ShowUsers(View): 34 | 35 | def dispatch_request(self): 36 | users = User.query.all() 37 | return render_template('users.html', objects=users) 38 | 39 | app.add_url_rule('/users/', ShowUsers.as_view('show_users')) 40 | 41 | As you can see what you have to do is to create a subclass of 42 | :class:`flask.views.View` and implement 43 | :meth:`~flask.views.View.dispatch_request`. Then we have to convert that 44 | class into an actual view function by using the 45 | :meth:`~flask.views.View.as_view` class method. The string you pass to 46 | that function is the name of the endpoint that view will then have. But 47 | this by itself is not helpful, so let's refactor the code a bit:: 48 | 49 | 50 | from flask.views import View 51 | 52 | class ListView(View): 53 | 54 | def get_template_name(self): 55 | raise NotImplementedError() 56 | 57 | def render_template(self, context): 58 | return render_template(self.get_template_name(), **context) 59 | 60 | def dispatch_request(self): 61 | context = {'objects': self.get_objects()} 62 | return self.render_template(context) 63 | 64 | class UserView(ListView): 65 | 66 | def get_template_name(self): 67 | return 'users.html' 68 | 69 | def get_objects(self): 70 | return User.query.all() 71 | 72 | This of course is not that helpful for such a small example, but it's good 73 | enough to explain the basic principle. When you have a class based view 74 | the question comes up what `self` points to. The way this works is that 75 | whenever the request is dispatched a new instance of the class is created 76 | and the :meth:`~flask.views.View.dispatch_request` method is called with 77 | the parameters from the URL rule. The class itself is instanciated with 78 | the parameters passed to the :meth:`~flask.views.View.as_view` function. 79 | For instance you can write a class like this:: 80 | 81 | class RenderTemplateView(View): 82 | def __init__(self, template_name): 83 | self.template_name = template_name 84 | def dispatch_request(self): 85 | return render_template(self.template_name) 86 | 87 | And then you can register it like this:: 88 | 89 | app.add_url_rule('/about', view_func=RenderTemplateView.as_view( 90 | 'about_page', template_name='about.html')) 91 | 92 | Method Hints 93 | ------------ 94 | 95 | Pluggable views are attached to the application like a regular function by 96 | either using :func:`~flask.Flask.route` or better 97 | :meth:`~flask.Flask.add_url_rule`. That however also means that you would 98 | have to provide the names of the HTTP methods the view supports when you 99 | attach this. In order to move that information to the class you can 100 | provide a :attr:`~flask.views.View.methods` attribute that has this 101 | information:: 102 | 103 | class MyView(View): 104 | methods = ['GET', 'POST'] 105 | 106 | def dispatch_request(self): 107 | if request.method == 'POST': 108 | ... 109 | ... 110 | 111 | app.add_url_rule('/myview', view_func=MyView.as_view('myview')) 112 | 113 | Method Based Dispatching 114 | ------------------------ 115 | 116 | For RESTful APIs it's especially helpful to execute a different function 117 | for each HTTP method. With the :class:`flask.views.MethodView` you can 118 | easily do that. Each HTTP method maps to a function with the same name 119 | (just in lowercase):: 120 | 121 | from flask.views import MethodView 122 | 123 | class UserAPI(MethodView): 124 | 125 | def get(self): 126 | users = User.query.all() 127 | ... 128 | 129 | def post(self): 130 | user = User.from_form_data(request.form) 131 | ... 132 | 133 | app.add_url_rule('/users/', view_func=UserAPI.as_view('users')) 134 | 135 | That way you also don't have to provide the 136 | :attr:`~flask.views.View.methods` attribute. It's automatically set based 137 | on the methods defined in the class. 138 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.7 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from setuptools import setup 4 | import os 5 | 6 | def read_file(*path): 7 | base_dir = os.path.dirname(__file__) 8 | file_path = (base_dir, ) + tuple(path) 9 | return file(os.path.join(*file_path)).read() 10 | 11 | setup( 12 | name = "flask-cn", 13 | url = "http://flask.flyzen.com", 14 | license="BSD", 15 | author = "Young King", 16 | author_email = "yanckin@gmail.com", 17 | description = "Flask documents (chinese translation)", 18 | long_description = ( 19 | read_file("README") + "\n\n"), 20 | version = "0.7", 21 | packages = [], 22 | include_package_data = True, 23 | zip_safe=False, 24 | install_requires=['Flask==0.7',], 25 | classifiers = [ 26 | "Programming Language :: Python", 27 | "Operating System :: OS Independent", 28 | "License :: OSI Approved :: BSD License", 29 | "Topic :: Software Development :: Libraries", 30 | "Topic :: Utilities", 31 | ], 32 | ) 33 | --------------------------------------------------------------------------------