Moved website into separate branch
2
.gitignore
vendored
|
|
@ -3,5 +3,5 @@
|
|||
*.pyo
|
||||
env
|
||||
dist
|
||||
website/_mailinglist/*
|
||||
_mailinglist/*
|
||||
*.egg-info
|
||||
|
|
|
|||
14
AUTHORS
|
|
@ -1,14 +0,0 @@
|
|||
Flask is written and maintained by Armin Ronacher and
|
||||
various contributors:
|
||||
|
||||
Development Lead
|
||||
````````````````
|
||||
|
||||
- Armin Ronacher <armin.ronacher@active-4.com>
|
||||
|
||||
Patches and Suggestions
|
||||
```````````````````````
|
||||
|
||||
- Chris Edgemon
|
||||
- Chris Grindstaff
|
||||
- Florent Xicluna
|
||||
21
CHANGES
|
|
@ -1,21 +0,0 @@
|
|||
Flask Changelog
|
||||
===============
|
||||
|
||||
Here you can see the full list of changes between each Flask release.
|
||||
|
||||
Version 0.2
|
||||
-----------
|
||||
|
||||
[unreleased; current development version]
|
||||
|
||||
- various bugfixes
|
||||
- integrated JSON support
|
||||
- added :func:`~flask.get_template_attribute` helper function.
|
||||
- :meth:`~flask.Flask.add_url_rule` can now also register a
|
||||
view function.
|
||||
- server listens on 127.0.0.1 by default now to fix issues with chrome.
|
||||
|
||||
Version 0.1
|
||||
-----------
|
||||
|
||||
First public preview release.
|
||||
32
LICENSE
|
|
@ -1,32 +0,0 @@
|
|||
Copyright (c) 2010 by Armin Ronacher and contributors. See AUTHORS
|
||||
for more details.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
17
Makefile
|
|
@ -1,17 +0,0 @@
|
|||
.PHONY: clean-pyc test
|
||||
|
||||
all: clean-pyc test
|
||||
|
||||
test:
|
||||
python tests/flask_tests.py
|
||||
|
||||
clean-pyc:
|
||||
find . -name '*.pyc' -exec rm -f {} +
|
||||
find . -name '*.pyo' -exec rm -f {} +
|
||||
find . -name '*~' -exec rm -f {} +
|
||||
|
||||
upload-website:
|
||||
scp -r website/* pocoo.org:/var/www/flask.pocoo.org/
|
||||
|
||||
upload-docs:
|
||||
$(MAKE) -C docs dirhtml && scp -r docs/_build/dirhtml/* pocoo.org:/var/www/flask.pocoo.org/docs/
|
||||
31
README
|
|
@ -1,31 +0,0 @@
|
|||
|
||||
// Flask //
|
||||
|
||||
because sometimes a pocket knife is not enough
|
||||
|
||||
|
||||
~ What is Flask?
|
||||
|
||||
Flask is a microframework for Python based on Werkzeug
|
||||
and Jinja2. It's intended for small scale applications
|
||||
and was developped with best intentions in mind.
|
||||
|
||||
~ Is it ready?
|
||||
|
||||
A preview release is out now, and I'm hoping for some
|
||||
input about what you want from a microframework and
|
||||
how it should look like. Consider the API to slightly
|
||||
improve over time.
|
||||
|
||||
~ What do I need?
|
||||
|
||||
Jinja 2.4 and Werkzeug 0.6.1. `easy_install` will
|
||||
install them for you if you do `easy_install Flask==dev`.
|
||||
I encourage you to use a virtualenv. Check the docs for
|
||||
complete installation and usage instructions.
|
||||
|
||||
~ Where are the docs?
|
||||
|
||||
Go to http://flask.pocoo.org/ for a prebuilt version of
|
||||
the current documentation. Otherwise build them yourself
|
||||
from the sphinx sources in the docs folder.
|
||||
|
Before Width: | Height: | Size: 90 KiB |
1
docs/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
_build
|
||||
118
docs/Makefile
|
|
@ -1,118 +0,0 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp epub latex changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) _build/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Flask"
|
||||
@echo "# ln -s _build/devhelp $$HOME/.local/share/devhelp/Flask"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
|
||||
latexpdf: latex
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
make -C _build/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in _build/latex."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
BIN
docs/_static/debugger.png
vendored
|
Before Width: | Height: | Size: 121 KiB |
BIN
docs/_static/flask.png
vendored
|
Before Width: | Height: | Size: 9.7 KiB |
BIN
docs/_static/flaskr.png
vendored
|
Before Width: | Height: | Size: 52 KiB |
BIN
docs/_static/logo-full.png
vendored
|
Before Width: | Height: | Size: 23 KiB |
13
docs/_templates/sidebarintro.html
vendored
|
|
@ -1,13 +0,0 @@
|
|||
<h3>About Flask</h3>
|
||||
<p>
|
||||
Flask is a micro webdevelopment framework for Python. You are currently
|
||||
looking at the documentation of the development version. Things are
|
||||
not stable yet, but if you have some feedback,
|
||||
<a href="mailto:armin.ronacher@active-4.com">let me know</a>.
|
||||
</p>
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="http://flask.pocoo.org/">The Flask Website</a></li>
|
||||
<li><a href="http://pypi.python.org/pypi/Flask">Flask @ PyPI</a></li>
|
||||
<li><a href="http://github.com/mitsuhiko/flask">Flask @ github</a></li>
|
||||
</ul>
|
||||
3
docs/_templates/sidebarlogo.html
vendored
|
|
@ -1,3 +0,0 @@
|
|||
<p class="logo"><a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/flask.png', 1) }}" alt="Logo"/>
|
||||
</a></p>
|
||||
344
docs/_themes/flasky/static/flasky.css_t
vendored
|
|
@ -1,344 +0,0 @@
|
|||
/*
|
||||
* flasky.css_t
|
||||
* ~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- flasky theme based on nature theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 17px;
|
||||
background-color: #ddd;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.document {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 230px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 30px 30px;
|
||||
min-height: 34em;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin-top: -70px;
|
||||
text-align: right;
|
||||
color: #888;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.related {
|
||||
line-height: 32px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
padding: 0 0 0 10px;
|
||||
}
|
||||
|
||||
div.related a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper p.logo {
|
||||
padding: 20px 0 10px 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
color: #222;
|
||||
font-size: 24px;
|
||||
font-weight: normal;
|
||||
margin: 20px 0 5px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #555;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #004B6B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #6D4100;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body {
|
||||
padding-bottom: 40px; /* saved for footer */
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.body h1 { margin-top: 0; padding-top: 20px; font-size: 240%; }
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: white;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight{
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
background-color: #ffe4e4;
|
||||
border: 1px solid #f66;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #eee;
|
||||
padding: 7px 30px;
|
||||
margin: 15px -30px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
dl pre {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
dl dl pre {
|
||||
margin-left: -90px;
|
||||
padding-left: 90px;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
||||
3
docs/_themes/flasky/theme.conf
vendored
|
|
@ -1,3 +0,0 @@
|
|||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = flasky.css
|
||||
262
docs/api.rst
|
|
@ -1,262 +0,0 @@
|
|||
.. _api:
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
.. module:: flask
|
||||
|
||||
This part of the documentation covers all the interfaces of Flask. For
|
||||
parts where Flask depends on external libraries, we document the most
|
||||
important right here and provide links to the canonical documentation.
|
||||
|
||||
|
||||
Application Object
|
||||
------------------
|
||||
|
||||
.. autoclass:: Flask
|
||||
:members:
|
||||
|
||||
Incoming Request Data
|
||||
---------------------
|
||||
|
||||
.. autoclass:: Request
|
||||
|
||||
.. class:: request
|
||||
|
||||
To access incoming request data, you can use the global `request`
|
||||
object. Flask parses incoming request data for you and gives you
|
||||
access to it through that global object. Internally Flask makes
|
||||
sure that you always get the correct data for the active thread if you
|
||||
are in a multithreaded environment.
|
||||
|
||||
The request object is an instance of a :class:`~werkzeug.Request`
|
||||
subclass and provides all of the attributes Werkzeug defines. This
|
||||
just shows a quick overview of the most important ones.
|
||||
|
||||
.. attribute:: form
|
||||
|
||||
A :class:`~werkzeug.MultiDict` with the parsed form data from `POST`
|
||||
or `PUT` requests. Please keep in mind that file uploads will not
|
||||
end up here, but instead in the :attr:`files` attribute.
|
||||
|
||||
.. attribute:: args
|
||||
|
||||
A :class:`~werkzeug.MultiDict` with the parsed contents of the query
|
||||
string. (The part in the URL after the question mark).
|
||||
|
||||
.. attribute:: values
|
||||
|
||||
A :class:`~werkzeug.CombinedMultiDict` with the contents of both
|
||||
:attr:`form` and :attr:`args`.
|
||||
|
||||
.. attribute:: cookies
|
||||
|
||||
A :class:`dict` with the contents of all cookies transmitted with
|
||||
the request.
|
||||
|
||||
.. attribute:: stream
|
||||
|
||||
If the incoming form data was not encoded with a known mimetype
|
||||
the data is stored unmodified in this stream for consumption. Most
|
||||
of the time it is a better idea to use :attr:`data` which will give
|
||||
you that data as a string. The stream only returns the data once.
|
||||
|
||||
.. attribute:: data
|
||||
|
||||
Contains the incoming request data as string in case it came with
|
||||
a mimetype Flask does not handle.
|
||||
|
||||
.. attribute:: files
|
||||
|
||||
A :class:`~werkzeug.MultiDict` with files uploaded as part of a
|
||||
`POST` or `PUT` request. Each file is stored as
|
||||
:class:`~werkzeug.FileStorage` object. It basically behaves like a
|
||||
standard file object you know from Python, with the difference that
|
||||
it also has a :meth:`~werkzeug.FileStorage.save` function that can
|
||||
store the file on the filesystem.
|
||||
|
||||
.. attribute:: environ
|
||||
|
||||
The underlying WSGI environment.
|
||||
|
||||
.. attribute:: method
|
||||
|
||||
The current request method (``POST``, ``GET`` etc.)
|
||||
|
||||
.. attribute:: path
|
||||
.. attribute:: script_root
|
||||
.. attribute:: url
|
||||
.. attribute:: base_url
|
||||
.. attribute:: url_root
|
||||
|
||||
Provides different ways to look at the current URL. Imagine your
|
||||
application is listening on the following URL::
|
||||
|
||||
http://www.example.com/myapplication
|
||||
|
||||
And a user requests the following URL::
|
||||
|
||||
http://www.example.com/myapplication/page.html?x=y
|
||||
|
||||
In this case the values of the above mentioned attributes would be
|
||||
the following:
|
||||
|
||||
============= ======================================================
|
||||
`path` ``/page.html``
|
||||
`script_root` ``/myapplication``
|
||||
`url` ``http://www.example.com/myapplication/page.html``
|
||||
`base_url` ``http://www.example.com/myapplication/page.html?x=y``
|
||||
`url_root` ``http://www.example.com/myapplication/``
|
||||
============= ======================================================
|
||||
|
||||
.. attribute:: is_xhr
|
||||
|
||||
`True` if the request was triggered via a JavaScript
|
||||
`XMLHttpRequest`. This only works with libraries that support the
|
||||
``X-Requested-With`` header and set it to `XMLHttpRequest`.
|
||||
Libraries that do that are prototype, jQuery and Mochikit and
|
||||
probably some more.
|
||||
|
||||
.. attribute:: json
|
||||
|
||||
Contains the parsed body of the JSON request if the mimetype of
|
||||
the incoming data was `application/json`. This requires Python 2.6
|
||||
or an installed version of simplejson.
|
||||
|
||||
Response Objects
|
||||
----------------
|
||||
|
||||
.. autoclass:: flask.Response
|
||||
:members: set_cookie, data, mimetype
|
||||
|
||||
.. attribute:: headers
|
||||
|
||||
A :class:`Headers` object representing the response headers.
|
||||
|
||||
.. attribute:: status_code
|
||||
|
||||
The response status as integer.
|
||||
|
||||
|
||||
Sessions
|
||||
--------
|
||||
|
||||
If you have the :attr:`Flask.secret_key` set you can use sessions in Flask
|
||||
applications. A session basically makes it possible to remember
|
||||
information from one request to another. The way Flask does this is by
|
||||
using a signed cookie. So the user can look at the session contents, but
|
||||
not modify it unless he knows the secret key, so make sure to set that to
|
||||
something complex and unguessable.
|
||||
|
||||
To access the current session you can use the :class:`session` object:
|
||||
|
||||
.. class:: session
|
||||
|
||||
The session object works pretty much like an ordinary dict, with the
|
||||
difference that it keeps track on modifications.
|
||||
|
||||
The following attributes are interesting:
|
||||
|
||||
.. attribute:: new
|
||||
|
||||
`True` if the session is new, `False` otherwise.
|
||||
|
||||
.. attribute:: modified
|
||||
|
||||
`True` if the session object detected a modification. Be advised
|
||||
that modifications on mutable structures are not picked up
|
||||
automatically, in that situation you have to explicitly set the
|
||||
attribute to `True` yourself. Here an example::
|
||||
|
||||
# this change is not picked up because a mutable object (here
|
||||
# a list) is changed.
|
||||
session['objects'].append(42)
|
||||
# so mark it as modified yourself
|
||||
session.modified = True
|
||||
|
||||
|
||||
Application Globals
|
||||
-------------------
|
||||
|
||||
To share data that is valid for one request only from one function to
|
||||
another, a global variable is not good enough because it would break in
|
||||
threaded environments. Flask provides you with a special object that
|
||||
ensures it is only valid for the active request and that will return
|
||||
different values for each request. In a nutshell: it does the right
|
||||
thing, like it does for :class:`request` and :class:`session`.
|
||||
|
||||
.. data:: g
|
||||
|
||||
Just store on this whatever you want. For example a database
|
||||
connection or the user that is currently logged in.
|
||||
|
||||
|
||||
Useful Functions and Classes
|
||||
----------------------------
|
||||
|
||||
.. autofunction:: url_for
|
||||
|
||||
.. function:: abort(code)
|
||||
|
||||
Raises an :exc:`~werkzeug.exception.HTTPException` for the given
|
||||
status code. For example to abort request handling with a page not
|
||||
found exception, you would call ``abort(404)``.
|
||||
|
||||
:param code: the HTTP error code.
|
||||
|
||||
.. autofunction:: redirect
|
||||
|
||||
.. autofunction:: escape
|
||||
|
||||
.. autoclass:: Markup
|
||||
:members: escape, unescape, striptags
|
||||
|
||||
Message Flashing
|
||||
----------------
|
||||
|
||||
.. autofunction:: flash
|
||||
|
||||
.. autofunction:: get_flashed_messages
|
||||
|
||||
Returning JSON
|
||||
--------------
|
||||
|
||||
.. autofunction:: jsonify
|
||||
|
||||
.. data:: json
|
||||
|
||||
If JSON support is picked up, this will be the module that Flask is
|
||||
using to parse and serialize JSON. So instead of doing this yourself::
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
You can instead just do this::
|
||||
|
||||
from flask import json
|
||||
|
||||
For usage examples, read the :mod:`json` documentation.
|
||||
|
||||
The :func:`~json.dumps` function of this json module is also available
|
||||
as filter called ``|tojson`` in Jinja2. Note that inside `script`
|
||||
tags no escaping must take place, so make sure to disable escaping
|
||||
with ``|safe`` if you intend to use it inside `script` tags:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<script type=text/javascript>
|
||||
doSomethingWith({{ user.username|tojson|safe }});
|
||||
</script>
|
||||
|
||||
Note that the ``|tojson`` filter escapes forward slashes properly.
|
||||
|
||||
Template Rendering
|
||||
------------------
|
||||
|
||||
.. autofunction:: render_template
|
||||
|
||||
.. autofunction:: render_template_string
|
||||
|
||||
.. autofunction:: get_template_attribute
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
.. _becomingbig:
|
||||
|
||||
Becoming Big
|
||||
============
|
||||
|
||||
Your application is becoming more and more complex? Flask is really not
|
||||
designed for large scale applications and does not attempt to do so, but
|
||||
that does not mean you picked the wrong tool in the first place.
|
||||
|
||||
Flask is powered by Werkzeug and Jinja2, two libraries that are in use at
|
||||
a number of large websites out there and all Flask does is bring those
|
||||
two together. Being a microframework, Flask is literally a single file.
|
||||
What that means for large applications is that it's probably a good idea
|
||||
to take the code from Flask and put it into a new module within the
|
||||
applications and expand on that.
|
||||
|
||||
What Could Be Improved?
|
||||
-----------------------
|
||||
|
||||
For instance it makes a lot of sense to change the way endpoints (the
|
||||
names of the functions / URL rules) are handled to also take the module
|
||||
name into account. Right now the function name is the URL name, but
|
||||
imagine you have a large application consisting of multiple components.
|
||||
In that case, it makes a lot of sense to use dotted names for the URL
|
||||
endpoints.
|
||||
|
||||
Here are some suggestions for how Flask can be modified to better
|
||||
accomodate large-scale applications:
|
||||
|
||||
- implement dotted names for URL endpoints
|
||||
- get rid of the decorator function registering which causes a lot
|
||||
of troubles for applications that have circular dependencies. It
|
||||
also requires that the whole application is imported when the system
|
||||
initializes or certain URLs will not be available right away. A
|
||||
better solution would be to have one module with all URLs in there and
|
||||
specifing the target functions explicitly or by name and importing
|
||||
them when needed.
|
||||
- switch to explicit request object passing. This requires more typing
|
||||
(because you now have something to pass around) but it makes it a
|
||||
whole lot easier to debug hairy situations and to test the code.
|
||||
- integrate the `Babel`_ i18n package or `SQLAlchemy`_ directly into the
|
||||
core framework.
|
||||
|
||||
.. _Babel: http://babel.edgewall.org/
|
||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
||||
|
||||
Why does Flask not do all that by Default?
|
||||
------------------------------------------
|
||||
|
||||
There is a huge difference between a small application that only has to
|
||||
handle a couple of requests per second and with an overall code complexity
|
||||
of less than 4000 lines of code and something of larger scale. At some
|
||||
point it becomes important to integrate external systems, different
|
||||
storage backends and more.
|
||||
|
||||
If Flask was designed with all these contingencies in mind, it would be a
|
||||
much more complex framework and harder to get started with.
|
||||
|
|
@ -1 +0,0 @@
|
|||
.. include:: ../CHANGES
|
||||
247
docs/conf.py
|
|
@ -1,247 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Flask documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Apr 6 15:24:58 2010.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.append(os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Flask'
|
||||
copyright = u'2010, Armin Ronacher'
|
||||
|
||||
import pkg_resources
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
release = __import__('pkg_resources').get_distribution('Flask').version
|
||||
version = '.'.join(release.split('.')[:2])
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'flaskext.FlaskyStyle'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
html_theme = 'flasky'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = ['_themes']
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar. Do not set, template magic!
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
html_sidebars = {
|
||||
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
|
||||
'**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
|
||||
'sourcelink.html', 'searchbox.html']
|
||||
}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_use_modindex = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Flaskdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Flask.tex', u'Flask Documentation',
|
||||
u'Armin Ronacher', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_use_modindex = True
|
||||
|
||||
|
||||
# -- Options for Epub output ---------------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
#epub_title = ''
|
||||
#epub_author = ''
|
||||
#epub_publisher = ''
|
||||
#epub_copyright = ''
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or en if the language is not set.
|
||||
#epub_language = ''
|
||||
|
||||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||
#epub_scheme = ''
|
||||
|
||||
# The unique identifier of the text. This can be a ISBN number
|
||||
# or the project homepage.
|
||||
#epub_identifier = ''
|
||||
|
||||
# A unique identification for the text.
|
||||
#epub_uid = ''
|
||||
|
||||
# HTML files that should be inserted before the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#epub_pre_files = []
|
||||
|
||||
# HTML files shat should be inserted after the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
#epub_post_files = []
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
#epub_exclude_files = []
|
||||
|
||||
# The depth of the table of contents in toc.ncx.
|
||||
#epub_tocdepth = 3
|
||||
|
||||
intersphinx_mapping = {
|
||||
'http://docs.python.org/dev': None,
|
||||
'http://werkzeug.pocoo.org/documentation/dev/': None,
|
||||
'http://www.sqlalchemy.org/docs/': None,
|
||||
'http://wtforms.simplecodes.com/docs/0.5/': None
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
CGI
|
||||
===
|
||||
|
||||
If all other deployment methods do not work, CGI will work for sure. CGI
|
||||
is supported by all major servers but usually has a less-than-optimal
|
||||
performance.
|
||||
|
||||
This is also the way you can use a Flask application on Google's
|
||||
`AppEngine`_, there however the execution does happen in a CGI-like
|
||||
environment. The application's performance is unaffected because of that.
|
||||
|
||||
.. _AppEngine: http://code.google.com/appengine/
|
||||
|
||||
Creating a `.cgi` file
|
||||
----------------------
|
||||
|
||||
First you need to create the CGI application file. Let's call it
|
||||
`yourapplication.cgi`::
|
||||
|
||||
#!/usr/bin/python
|
||||
from wsgiref.handlers import CGIHandler
|
||||
from yourapplication import app
|
||||
|
||||
CGIHandler().run(app)
|
||||
|
||||
If you're running Python 2.4 you will need the :mod:`wsgiref` package. Python
|
||||
2.5 and higher ship this as part of the standard library.
|
||||
|
||||
Server Setup
|
||||
------------
|
||||
|
||||
Usually there are two ways to configure the server. Either just copy the
|
||||
`.cgi` into a `cgi-bin` (and use `mod_rerwite` or something similar to
|
||||
rewrite the URL) or let the server point to the file directly.
|
||||
|
||||
In Apache for example you can put a like like this into the config:
|
||||
|
||||
.. sourcecode:: apache
|
||||
|
||||
ScriptAlias /app /path/to/the/application.cgi
|
||||
|
||||
For more information consult the documentation of your webserver.
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
FastCGI
|
||||
=======
|
||||
|
||||
A very popular deployment setup on servers like `lighttpd`_ and `nginx`_
|
||||
is FastCGI. To use your WSGI application with any of them you will need
|
||||
a FastCGI server first.
|
||||
|
||||
The most popular one is `flup`_ which we will use for this guide. Make
|
||||
sure to have it installed.
|
||||
|
||||
Creating a `.fcgi` file
|
||||
-----------------------
|
||||
|
||||
First you need to create the FastCGI server file. Let's call it
|
||||
`yourapplication.fcgi`::
|
||||
|
||||
#!/usr/bin/python
|
||||
from flup.server.fcgi import WSGIServer
|
||||
from yourapplication import app
|
||||
|
||||
WSGIServer(app).run()
|
||||
|
||||
This is enough for Apache to work, however lighttpd and nginx need a
|
||||
socket to communicate with the FastCGI server. For that to work you
|
||||
need to pass the path to the socket to the
|
||||
:class:`~flup.server.fcgi.WSGIServer`::
|
||||
|
||||
WSGIServer(application, bindAddress='/path/to/fcgi.sock').run()
|
||||
|
||||
The path has to be the exact same path you define in the server
|
||||
config.
|
||||
|
||||
Save the `yourapplication.fcgi` file somewhere you will find it again.
|
||||
It makes sense to have that in `/var/www/yourapplication` or something
|
||||
similar.
|
||||
|
||||
Make sure to set the executable bit on that file so that the servers
|
||||
can execute it::
|
||||
|
||||
# chmod +x /var/www/yourapplication/yourapplication.fcgi
|
||||
|
||||
Configuring lighttpd
|
||||
--------------------
|
||||
|
||||
A basic FastCGI configuration for lighttpd looks like that::
|
||||
|
||||
fastcgi.server = ("/yourapplication" =>
|
||||
"yourapplication" => (
|
||||
"socket" => "/tmp/yourapplication-fcgi.sock",
|
||||
"bin-path" => "/var/www/yourapplication/yourapplication.fcgi",
|
||||
"check-local" => "disable"
|
||||
)
|
||||
)
|
||||
|
||||
This configuration binds the application to `/yourapplication`. If you
|
||||
want the application to work in the URL root you have to work around a
|
||||
lighttpd bug with the :class:`~werkzeug.contrib.fixers.LighttpdCGIRootFix`
|
||||
middleware.
|
||||
|
||||
Make sure to apply it only if you are mounting the application the URL
|
||||
root.
|
||||
|
||||
Configuring nginx
|
||||
-----------------
|
||||
|
||||
Installing FastCGI applications on nginx is a bit tricky because by default
|
||||
some FastCGI parameters are not properly forwarded.
|
||||
|
||||
A basic FastCGI configuration for nginx looks like this::
|
||||
|
||||
location /yourapplication/ {
|
||||
include fastcgi_params;
|
||||
if ($uri ~ ^/yourapplication/(.*)?) {
|
||||
set $path_url $1;
|
||||
}
|
||||
fastcgi_param PATH_INFO $path_url;
|
||||
fastcgi_param SCRIPT_NAME /yourapplication;
|
||||
fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
|
||||
}
|
||||
|
||||
This configuration binds the application to `/yourapplication`. If you want
|
||||
to have it in the URL root it's a bit easier because you don't have to figure
|
||||
out how to calculate `PATH_INFO` and `SCRIPT_NAME`::
|
||||
|
||||
location /yourapplication/ {
|
||||
include fastcgi_params;
|
||||
fastcgi_param PATH_INFO $fastcgi_script_name;
|
||||
fastcgi_param SCRIPT_NAME "";
|
||||
fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
|
||||
}
|
||||
|
||||
Since Nginx doesn't load FastCGI apps, you have to do it by yourself. You
|
||||
can either write an `init.d` script for that or execute it inside a screen
|
||||
session::
|
||||
|
||||
$ screen
|
||||
$ /var/www/yourapplication/yourapplication.fcgi
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
FastCGI deployments tend to be hard to debug on most webservers. Very often the
|
||||
only thing the server log tells you is something along the lines of "premature
|
||||
end of headers". In order to debug the application the only thing that can
|
||||
really give you ideas why it breaks is switching to the correct user and
|
||||
executing the application by hand.
|
||||
|
||||
This example assumes your application is called `application.fcgi` and that your
|
||||
webserver user is `www-data`::
|
||||
|
||||
$ su www-data
|
||||
$ cd /var/www/yourapplication
|
||||
$ python application.fcgi
|
||||
Traceback (most recent call last):
|
||||
File "yourapplication.fcg", line 4, in <module>
|
||||
ImportError: No module named yourapplication
|
||||
|
||||
In this case the error seems to be "yourapplication" not being on the python
|
||||
path. Common problems are:
|
||||
|
||||
- relative paths being used. Don't rely on the current working directory
|
||||
- the code depending on environment variables that are not set by the
|
||||
web server.
|
||||
- different python interpreters being used.
|
||||
|
||||
.. _lighttpd: http://www.lighttpd.net/
|
||||
.. _nginx: http://nginx.net/
|
||||
.. _flup: http://trac.saddi.com/flup
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
Deployment Options
|
||||
==================
|
||||
|
||||
Depending on what you have available there are multiple ways to run Flask
|
||||
applications. A very common method is to use the builtin server during
|
||||
development and maybe behind a proxy for simple applications, but there
|
||||
are more options available.
|
||||
|
||||
If you have a different WSGI server look up the server documentation about
|
||||
how to use a WSGI app with it. Just remember that your application object
|
||||
is the actual WSGI application.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
mod_wsgi
|
||||
cgi
|
||||
fastcgi
|
||||
others
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
mod_wsgi (Apache)
|
||||
=================
|
||||
|
||||
If you are using the `Apache`_ webserver you should consider using `mod_wsgi`_.
|
||||
|
||||
.. _Apache: http://httpd.apache.org/
|
||||
|
||||
Installing `mod_wsgi`
|
||||
---------------------
|
||||
|
||||
If you don't have `mod_wsgi` installed yet you have to either install it using
|
||||
a package manager or compile it yourself.
|
||||
|
||||
The mod_wsgi `installation instructions`_ cover source installations on UNIX
|
||||
systems.
|
||||
|
||||
If you are using ubuntu / debian you can apt-get it and activate it as follows::
|
||||
|
||||
# apt-get install libapache2-mod-wsgi
|
||||
|
||||
On FreeBSD install `mod_wsgi` by compiling the `www/mod_wsgi` port or by using
|
||||
pkg_add::
|
||||
|
||||
# pkg_add -r mod_wsgi
|
||||
|
||||
If you are using pkgsrc you can install `mod_wsgi` by compiling the
|
||||
`www/ap2-wsgi` package.
|
||||
|
||||
If you encounter segfaulting child processes after the first apache reload you
|
||||
can safely ignore them. Just restart the server.
|
||||
|
||||
Creating a `.wsgi` file
|
||||
-----------------------
|
||||
|
||||
To run your application you need a `yourapplication.wsgi` file. This file
|
||||
contains the code `mod_wsgi` is executing on startup to get the application
|
||||
object. The object called `application` in that file is then used as
|
||||
application.
|
||||
|
||||
For most applications the following file should be sufficient::
|
||||
|
||||
from yourapplication import app as application
|
||||
|
||||
If you don't have a factory function for application creation but a singleton
|
||||
instance you can directly import that one as `application`.
|
||||
|
||||
Store that file somewhere that you will find it again (e.g.:
|
||||
`/var/www/yourapplication`) and make sure that `yourapplication` and all
|
||||
the libraries that are in use are on the python load path. If you don't
|
||||
want to install it system wide consider using a `virtual python`_ instance.
|
||||
|
||||
Configuring Apache
|
||||
------------------
|
||||
|
||||
The last thing you have to do is to create an Apache configuration file for
|
||||
your application. In this example we are telling `mod_wsgi` to execute the
|
||||
application under a different user for security reasons:
|
||||
|
||||
.. sourcecode:: apache
|
||||
|
||||
<VirtualHost *>
|
||||
ServerName example.com
|
||||
|
||||
WSGIDaemonProcess yourapplication user=user1 group=group1 threads=5
|
||||
WSGIScriptAlias / /var/www/yourapplication/yourapplication.wsgi
|
||||
|
||||
<Directory /var/www/yourapplication>
|
||||
WSGIProcessGroup yourapplication
|
||||
WSGIApplicationGroup %{GLOBAL}
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
||||
For more information consult the `mod_wsgi wiki`_.
|
||||
|
||||
.. _mod_wsgi: http://code.google.com/p/modwsgi/
|
||||
.. _installation instructions: http://code.google.com/p/modwsgi/wiki/QuickInstallationGuide
|
||||
.. _virtual python: http://pypi.python.org/pypi/virtualenv
|
||||
.. _mod_wsgi wiki: http://code.google.com/p/modwsgi/wiki/
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
Other Servers
|
||||
=============
|
||||
|
||||
There are popular servers written in Python that allow the execution of
|
||||
WSGI applications as well. Keep in mind though that some of these servers
|
||||
were written for very specific applications and might not work as well for
|
||||
standard WSGI application such as Flask powered ones.
|
||||
|
||||
|
||||
Tornado
|
||||
--------
|
||||
|
||||
`Tornado`_ is an open source version of the scalable, non-blocking web
|
||||
server and tools that power `FriendFeed`_. Because it is non-blocking and
|
||||
uses epoll, it can handle thousands of simultaneous standing connections,
|
||||
which means it is ideal for real-time web services. Integrating this
|
||||
service with Flask is a trivial task::
|
||||
|
||||
from tornado.wsgi import WSGIContainer
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
from yourapplication import app
|
||||
|
||||
http_server = HTTPServer(WSGIContainer(app))
|
||||
http_server.listen(5000)
|
||||
IOLoop.instance().start()
|
||||
|
||||
|
||||
.. _Tornado: http://www.tornadoweb.org/
|
||||
.. _FriendFeed: http://friendfeed.com/
|
||||
|
||||
|
||||
Gevent
|
||||
-------
|
||||
|
||||
`Gevent`_ is a coroutine-based Python networking library that uses
|
||||
`greenlet`_ to provide a high-level synchronous API on top of `libevent`_
|
||||
event loop::
|
||||
|
||||
from gevent.wsgi import WSGIServer
|
||||
from yourapplication import app
|
||||
|
||||
http_server = WSGIServer(('', 5000), app)
|
||||
http_server.serve_forever()
|
||||
|
||||
.. _Gevent: http://www.gevent.org/
|
||||
.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html
|
||||
.. _libevent: http://monkey.org/~provos/libevent/
|
||||
147
docs/design.rst
|
|
@ -1,147 +0,0 @@
|
|||
Design Decisions in Flask
|
||||
=========================
|
||||
|
||||
If you are curious why Flask does certain things the way it does and not
|
||||
differently, this section is for you. This should give you an idea about
|
||||
some of the design decisions that may appear arbitrary and surprising at
|
||||
first, especially in direct comparison with other frameworks.
|
||||
|
||||
|
||||
The Explicit Application Object
|
||||
-------------------------------
|
||||
|
||||
A Python web application based on WSGI has to have one central callable
|
||||
object that implements the actual application. In Flask this is an
|
||||
instance of the :class:`~flask.Flask` class. Each Flask application has
|
||||
to create an instance of this class itself and pass it the name of the
|
||||
module, but why can't Flask do that itself?
|
||||
|
||||
Without such an explicit application object the following code::
|
||||
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return 'Hello World!'
|
||||
|
||||
Would look like this instead::
|
||||
|
||||
from hypothetical_flask import route
|
||||
|
||||
@route('/')
|
||||
def index():
|
||||
return 'Hello World!'
|
||||
|
||||
There are three major reasons for this. The most important one is that
|
||||
implicit application objects require that there may only be one class at
|
||||
the time. There are ways to fake multiple application with a single
|
||||
application object, like maintaining a stack of applications, but this
|
||||
causes some problems I won't outline here in detail. Now the question is:
|
||||
when does a microframework need more than one application at the same
|
||||
time? A good example for this is unittesting. When you want to test
|
||||
something it can be very helpful to create a minimal application to test
|
||||
specific behavior. When the application object is deleted everything it
|
||||
allocated will be freed again.
|
||||
|
||||
Another thing that becomes possible when you have an explicit object laying
|
||||
around in your code is that you can subclass the base class
|
||||
(:class:`~flask.Flask`) to alter specific behaviour. This would not be
|
||||
possible without hacks if the object were created ahead of time for you
|
||||
based on a class that is not exposed to you.
|
||||
|
||||
But there is another very important reason why Flask depends on an
|
||||
explicit instanciation of that class: the package name. Whenever you
|
||||
create a Flask instance you usually pass it `__name__` as package name.
|
||||
Flask depends on that information to properly load resources relative
|
||||
to your module. With Python's outstanding support for reflection it can
|
||||
then access the package to figure out where the templates and static files
|
||||
are stored (see :meth:`~flask.Flask.open_resource`). Now obviously there
|
||||
are frameworks around that do not need any configuration and will still be
|
||||
able to load templates relative to your application module. But they have
|
||||
to use the current working directory for that, which is a very unreliable
|
||||
way to determine where the application is. The current working directory
|
||||
is process-wide and if you are running multiple applications in one
|
||||
process (which could happen in a webserver without you knowing) the paths
|
||||
will be off. Worse: many webservers do not set the working directory to
|
||||
the directory of your application but to the document root which does not
|
||||
have to be the same folder.
|
||||
|
||||
The third reason is "explicit is better than implicit". That object is
|
||||
your WSGI application, you don't have to remember anything else. If you
|
||||
want to apply a WSGI middleware, just wrap it and you're done (though
|
||||
there are better ways to do that so that you do not lose the reference
|
||||
to the application object :meth:`~flask.Flask.wsgi_app`).
|
||||
|
||||
One Template Engine
|
||||
-------------------
|
||||
|
||||
Flask decides on one template engine: Jinja2. Why doesn't Flask have a
|
||||
pluggable template engine interface? You can obviously use a different
|
||||
template engine, but Flask will still configure Jinja2 for you. While
|
||||
that limitation that Jinja2 is *always* configured will probably go away,
|
||||
the decision to bundle one template engine and use that will not.
|
||||
|
||||
Template engines are like programming languages and each of those engines
|
||||
has a certain understanding about how things work. On the surface they
|
||||
all work the same: you tell the engine to evaluate a template with a set
|
||||
of variables and take the return value as string.
|
||||
|
||||
But that's about where similarities end. Jinja2 for example has an
|
||||
extensive filter system, a certain way to do template inheritance, support
|
||||
for reusable blocks (macros) that can be used from inside templates and
|
||||
also from Python code, uses unicode for all operations, supports
|
||||
iterative template rendering, configurable syntax and more. On the other
|
||||
hand an engine like Genshi is based on XML stream evaluation, template
|
||||
inheritance by taking the availability of XPath into account and more.
|
||||
Mako on the other hand treats templates similar to Python modules.
|
||||
|
||||
When it comes to connecting a template engine with an application or
|
||||
framework there is more than just rendering templates. For instance,
|
||||
Flask uses Jinja2's extensive autoescaping support. Also it provides
|
||||
ways to access macros from Jinja2 templates.
|
||||
|
||||
A template abstraction layer that would not take the unique features of
|
||||
the template engines away is a science on its own and a too large
|
||||
undertaking for a microframework like Flask.
|
||||
|
||||
|
||||
Micro with Dependencies
|
||||
-----------------------
|
||||
|
||||
Why does Flask call itself a microframework and yet it depends on two
|
||||
libraries (namely Werkzeug and Jinja2). Why shouldn't it? If we look
|
||||
over to the Ruby side of web development there we have a protocol very
|
||||
similar to WSGI. Just that it's called Rack there, but besides that it
|
||||
looks very much like a WSGI rendition for Ruby. But nearly all
|
||||
applications in Ruby land do not work with Rack directly, but on top of a
|
||||
library with the same name. This Rack library has two equivalents in
|
||||
Python: WebOb (formerly Paste) and Werkzeug. Paste is still around but
|
||||
from my understanding it's sort of deprecated in favour of WebOb. The
|
||||
development of WebOb and Werkzeug started side by side with similar ideas
|
||||
in mind: be a good implementation of WSGI for other applications to take
|
||||
advantage.
|
||||
|
||||
Flask is a framework that takes advantage of the work already done by
|
||||
Werkzeug to properly interface WSGI (which can be a complex task at
|
||||
times). Thanks to recent developments in the Python package
|
||||
infrastructure, packages with depencencies are no longer an issue and
|
||||
there are very few reasons against having libraries that depend on others.
|
||||
|
||||
|
||||
Thread Locals
|
||||
-------------
|
||||
|
||||
Flask uses thread local objects (context local objects in fact, they
|
||||
support greenlet contexts as well) for request, session and an extra
|
||||
object you can put your own things on (:data:`~flask.g`). Why is that and
|
||||
isn't that a bad idea?
|
||||
|
||||
Yes it is usually not such a bright idea to use thread locals. They cause
|
||||
troubles for servers that are not based on the concept of threads and make
|
||||
large applications harder to maintain. However Flask is just not designed
|
||||
for large applications or asyncronous servers. Flask wants to make it
|
||||
quick and easy to write a traditional web application.
|
||||
|
||||
Also see the :ref:`becomingbig` section of the documentation for some
|
||||
inspiration for larger applications based on Flask.
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
# flasky extensions. flasky pygments style based on tango style
|
||||
from pygments.style import Style
|
||||
from pygments.token import Keyword, Name, Comment, String, Error, \
|
||||
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
|
||||
|
||||
|
||||
class FlaskyStyle(Style):
|
||||
background_color = "#f8f8f8"
|
||||
default_style = ""
|
||||
|
||||
styles = {
|
||||
# No corresponding class for the following:
|
||||
#Text: "", # class: ''
|
||||
Whitespace: "underline #f8f8f8", # class: 'w'
|
||||
Error: "#a40000 border:#ef2929", # class: 'err'
|
||||
Other: "#000000", # class 'x'
|
||||
|
||||
Comment: "italic #8f5902", # class: 'c'
|
||||
Comment.Preproc: "noitalic", # class: 'cp'
|
||||
|
||||
Keyword: "bold #004461", # class: 'k'
|
||||
Keyword.Constant: "bold #004461", # class: 'kc'
|
||||
Keyword.Declaration: "bold #004461", # class: 'kd'
|
||||
Keyword.Namespace: "bold #004461", # class: 'kn'
|
||||
Keyword.Pseudo: "bold #004461", # class: 'kp'
|
||||
Keyword.Reserved: "bold #004461", # class: 'kr'
|
||||
Keyword.Type: "bold #004461", # class: 'kt'
|
||||
|
||||
Operator: "#582800", # class: 'o'
|
||||
Operator.Word: "bold #004461", # class: 'ow' - like keywords
|
||||
|
||||
Punctuation: "bold #000000", # class: 'p'
|
||||
|
||||
# because special names such as Name.Class, Name.Function, etc.
|
||||
# are not recognized as such later in the parsing, we choose them
|
||||
# to look the same as ordinary variables.
|
||||
Name: "#000000", # class: 'n'
|
||||
Name.Attribute: "#c4a000", # class: 'na' - to be revised
|
||||
Name.Builtin: "#004461", # class: 'nb'
|
||||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
|
||||
Name.Class: "#000000", # class: 'nc' - to be revised
|
||||
Name.Constant: "#000000", # class: 'no' - to be revised
|
||||
Name.Decorator: "#888", # class: 'nd' - to be revised
|
||||
Name.Entity: "#ce5c00", # class: 'ni'
|
||||
Name.Exception: "bold #cc0000", # class: 'ne'
|
||||
Name.Function: "#000000", # class: 'nf'
|
||||
Name.Property: "#000000", # class: 'py'
|
||||
Name.Label: "#f57900", # class: 'nl'
|
||||
Name.Namespace: "#000000", # class: 'nn' - to be revised
|
||||
Name.Other: "#000000", # class: 'nx'
|
||||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
|
||||
Name.Variable: "#000000", # class: 'nv' - to be revised
|
||||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
|
||||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
|
||||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
|
||||
|
||||
Number: "#990000", # class: 'm'
|
||||
|
||||
Literal: "#000000", # class: 'l'
|
||||
Literal.Date: "#000000", # class: 'ld'
|
||||
|
||||
String: "#4e9a06", # class: 's'
|
||||
String.Backtick: "#4e9a06", # class: 'sb'
|
||||
String.Char: "#4e9a06", # class: 'sc'
|
||||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
|
||||
String.Double: "#4e9a06", # class: 's2'
|
||||
String.Escape: "#4e9a06", # class: 'se'
|
||||
String.Heredoc: "#4e9a06", # class: 'sh'
|
||||
String.Interpol: "#4e9a06", # class: 'si'
|
||||
String.Other: "#4e9a06", # class: 'sx'
|
||||
String.Regex: "#4e9a06", # class: 'sr'
|
||||
String.Single: "#4e9a06", # class: 's1'
|
||||
String.Symbol: "#4e9a06", # class: 'ss'
|
||||
|
||||
Generic: "#000000", # class: 'g'
|
||||
Generic.Deleted: "#a40000", # class: 'gd'
|
||||
Generic.Emph: "italic #000000", # class: 'ge'
|
||||
Generic.Error: "#ef2929", # class: 'gr'
|
||||
Generic.Heading: "bold #000080", # class: 'gh'
|
||||
Generic.Inserted: "#00A000", # class: 'gi'
|
||||
Generic.Output: "#888", # class: 'go'
|
||||
Generic.Prompt: "#745334", # class: 'gp'
|
||||
Generic.Strong: "bold #000000", # class: 'gs'
|
||||
Generic.Subheading: "bold #800080", # class: 'gu'
|
||||
Generic.Traceback: "bold #a40000", # class: 'gt'
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
Foreword
|
||||
========
|
||||
|
||||
Read this before you get started with Flask. This hopefully answers some
|
||||
questions about the intention of the project, what it aims at and when you
|
||||
should or should not be using it.
|
||||
|
||||
What does Micro Mean?
|
||||
---------------------
|
||||
|
||||
The micro in microframework for me means on the one hand being small in
|
||||
size and complexity but on the other hand also that the complexity of the
|
||||
applications that are written with these frameworks do not exceed a
|
||||
certain size. A microframework like Flask sacrifices a few things in
|
||||
order to be approachable and to be as concise as possible.
|
||||
|
||||
For example Flask uses thread local objects internally so that you don't
|
||||
have to pass objects around from function to function within a request in
|
||||
order to stay threadsafe. While this is a really easy approach and saves
|
||||
you a lot of time, it also does not scale well to large applications.
|
||||
It's especially painful for more complex unittests and when you suddenly
|
||||
have to deal with code being executed outside of the context of a request
|
||||
(for example if you have cronjobs).
|
||||
|
||||
Flask provides some tools to deal with the downsides of this approach but
|
||||
the core problem of this approach obviously stays. It is also based on
|
||||
convention over configuration which means that a lot of things are
|
||||
preconfigured in Flask and will work well for smaller applications but not
|
||||
so much for larger ones (where and how it looks for templates, static
|
||||
files etc.)
|
||||
|
||||
But don't worry if your application suddenly grows larger than it was
|
||||
initially and you're afraid Flask might not grow with it. Even with
|
||||
larger frameworks you sooner or later will find out that you need
|
||||
something the framework just cannot do for you without modification.
|
||||
If you are ever in that situation, check out the :ref:`becomingbig`
|
||||
chapter.
|
||||
|
||||
A Framework and An Example
|
||||
--------------------------
|
||||
|
||||
Flask is not only a microframework, it is also an example. Based on
|
||||
Flask, there will be a series of blog posts that explain how to create a
|
||||
framework. Flask itself is just one way to implement a framework on top
|
||||
of existing libraries. Unlike many other microframeworks Flask does not
|
||||
try to implement anything on its own, it reuses existing code.
|
||||
|
||||
Web Development is Dangerous
|
||||
----------------------------
|
||||
|
||||
I'm not even joking. Well, maybe a little. If you write a web
|
||||
application you are probably allowing users to register and leave their
|
||||
data on your server. The users are entrusting you with data. And even if
|
||||
you are the only user that might leave data in your application, you still
|
||||
want that data to be stored in a secure manner.
|
||||
|
||||
Unfortunately there are many ways security of a web application can be
|
||||
compromised. Flask protects you against one of the most common security
|
||||
problems of modern web applications: cross site scripting (XSS). Unless
|
||||
you deliberately mark insecure HTML as secure Flask (and the underlying
|
||||
Jinja2 template engine) have you covered. But there are many more ways to
|
||||
cause security problems.
|
||||
|
||||
Whenever something is dangerous where you have to watch out, the
|
||||
documentation will tell you so. Some of the security concerns of web
|
||||
development are far more complex than one might think and often we all end
|
||||
up in situations where we think "well, this is just far fetched, how could
|
||||
that possibly be exploited" and then an intelligent guy comes along and
|
||||
figures a way out to exploit that application. And don't think, your
|
||||
application is not important enough for hackers to take notice. Depending
|
||||
ont he kind of attack, chances are there are automated botnets out there
|
||||
trying to figure out how to fill your database with viagra adverisments.
|
||||
|
||||
So always keep that in mind when doing web development.
|
||||
|
||||
Target Audience
|
||||
---------------
|
||||
|
||||
Is Flask for you? Is your application small-ish (less than 4000 lines of
|
||||
Python code) and does not depend on too complex database structures, Flask
|
||||
is the Framework for you. It was designed from the ground up to be easy
|
||||
to use, based on established principles, good intentions and on top of two
|
||||
established libraries in widespread usage.
|
||||
|
||||
Flask serves two purposes: it's an example of how to create a minimal and
|
||||
opinionated framework on top of Werkzeug to show how this can be done, and
|
||||
to provide people with a simple tool to prototype larger applications or
|
||||
to implement small and medium sized applications.
|
||||
|
||||
If you suddenly discover that your application grows larger than
|
||||
originally intended, head over to the :ref:`becomingbig` section to see
|
||||
some possible solutions for larger applications.
|
||||
|
||||
Satisfied? Then head over to the :ref:`installation`.
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
Welcome to Flask
|
||||
================
|
||||
|
||||
.. image:: _static/logo-full.png
|
||||
:alt: The Flask Logo with Subtitle
|
||||
:class: floatingflask
|
||||
|
||||
Welcome to Flask's documentation. This documentation is divided in
|
||||
different parts. I would suggest to get started with the
|
||||
:ref:`installation` and then heading over to the :ref:`quickstart`.
|
||||
Besides the quickstart there is also a more detailed :ref:`tutorial` that
|
||||
shows how to create a complete (albeit small) application with Flask. If
|
||||
you rather want to dive into all the internal parts of Flask, check out
|
||||
the :ref:`api` documentation. Common patterns are described in the
|
||||
:ref:`patterns` section.
|
||||
|
||||
Flask also depends on two external libraries: the `Jinja2`_ template
|
||||
engine and the `Werkzeug`_ WSGI toolkit. both of which are not documented
|
||||
here. If you want to dive into their documentation check out the
|
||||
following links:
|
||||
|
||||
- `Jinja2 Documentation <http://jinja.pocoo.org/2/documentation/>`_
|
||||
- `Werkzeug Documentation <http://werkzeug.pocoo.org/documentation/>`_
|
||||
|
||||
.. _Jinja2: http://jinja.pocoo.org/2/
|
||||
.. _Werkzeug: http://werkzeug.pocoo.org/
|
||||
|
||||
User's Guide
|
||||
------------
|
||||
|
||||
This part of the documentation is written text and should give you an idea
|
||||
how to work with Flask. It's a series of step-by-step instructions for
|
||||
web development.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
foreword
|
||||
installation
|
||||
quickstart
|
||||
tutorial/index
|
||||
testing
|
||||
patterns/index
|
||||
deploying/index
|
||||
becomingbig
|
||||
|
||||
Additional Notes
|
||||
----------------
|
||||
|
||||
Design notes, legal information and changelog are here for the interested:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
design
|
||||
license
|
||||
changelog
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
If you are looking for information on a specific function, class or
|
||||
method, this part of the documentation is for you:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
.. _installation:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Flask is a microframework and yet it depends on external libraries. There
|
||||
are various ways how you can install that library and this explains each
|
||||
way and why there are multiple ways.
|
||||
|
||||
Flask depends on two external libraries: `Werkzeug
|
||||
<http://werkzeug.pocoo.org/>`_ and `Jinja2 <http://jinja.pocoo.org/2/>`_.
|
||||
The first on is responsible for interfacing WSGI the latter to render
|
||||
templates. Now you are maybe asking, what is WSGI? WSGI is a standard
|
||||
in Python that is basically responsible for ensuring that your application
|
||||
is behaving in a specific way that you can run it on different
|
||||
environments (for example on a local development server, on an Apache2, on
|
||||
lighttpd, on Google's App Engine or whatever you have in mind).
|
||||
|
||||
So how do you get all that on your computer in no time? The most kick-ass
|
||||
method is virtualenv, so let's look at that first.
|
||||
|
||||
virtualenv
|
||||
----------
|
||||
|
||||
Virtualenv is what you want to use during development and in production if
|
||||
you have shell access. So first: what does virtualenv do? If you are
|
||||
like me and you like Python, chances are you want to use it for another
|
||||
project as well. Now the more projects you have, the more likely it is
|
||||
that you will be working with different versions of Python itself or a
|
||||
library involved. Because let's face it: quite often libraries break
|
||||
backwards compatibility and it's unlikely that your application will
|
||||
not have any dependencies, that just won't happen. So virtualenv for the
|
||||
rescue!
|
||||
|
||||
It basically makes it possible to have multiple side-by-side
|
||||
"installations" of Python, each for your own project. It's not actually
|
||||
an installation but a clever way to keep things separated.
|
||||
|
||||
So let's see how that works!
|
||||
|
||||
If you are on OS X or Linux chances are that one of the following two
|
||||
commands will for for you::
|
||||
|
||||
$ sudo easy_install virtualenv
|
||||
|
||||
or even better::
|
||||
|
||||
$ sudo pip install virtualenv
|
||||
|
||||
Changes are you have virtualenv installed on your system then. Maybe it's
|
||||
even in your package manager (on ubuntu try ``sudo apt-get install
|
||||
python-virtualenv``).
|
||||
|
||||
If you are on Windows and missing the `easy_install` command you have to
|
||||
install it first. Check the :ref:`windows-easy-install` section for more
|
||||
information about how to do that. Once you have it installed, run the
|
||||
same commands as above, but without the `sudo` part.
|
||||
|
||||
So now that you have virtualenv running just fire up a shell and create
|
||||
your own environment. I usually create a folder and a `env` folder
|
||||
within::
|
||||
|
||||
$ mkdir myproject
|
||||
$ cd myproject
|
||||
$ virtualenv env
|
||||
New python executable in env/bin/python
|
||||
Installing setuptools............done.
|
||||
|
||||
Now you only have to activate it, whenever you work with it. On OS X and
|
||||
Linux do the following::
|
||||
|
||||
$ source env/bin/activate
|
||||
|
||||
If you are a Windows user, the following command is for you::
|
||||
|
||||
$ env\scripts\activate
|
||||
|
||||
Either way, you should now be using your virtualenv (see how the prompt of
|
||||
your shell has changed to show the virtualenv).
|
||||
|
||||
Now you can just enter the following command to get Flask activated in
|
||||
your virtualenv::
|
||||
|
||||
$ easy_install Flask
|
||||
|
||||
A few seconds later you are good to go.
|
||||
|
||||
|
||||
System Wide Installation
|
||||
------------------------
|
||||
|
||||
This is possible as well, but I would not recommend it. Just run
|
||||
`easy_install` with root rights::
|
||||
|
||||
sudo easy_install Flask
|
||||
|
||||
(Run it in an Admin shell on Windows systems and without the `sudo`).
|
||||
|
||||
|
||||
Leaving on the Edge
|
||||
-------------------
|
||||
|
||||
You want to work with the latest version of Flask, there are two ways: you
|
||||
can either let `easy_install` pull in the development version or tell it
|
||||
to operate on a git checkout. Either way it's recommended to do that in a
|
||||
virtualenv.
|
||||
|
||||
Get the git checkout in a new virtualenv and run in develop mode::
|
||||
|
||||
$ git clone http://github.com/mitsuhiko/flask.git
|
||||
Initialized empty Git repository in ~/dev/flask/.git/
|
||||
$ cd flask
|
||||
$ virtualenv env
|
||||
$ source env/bin/activate
|
||||
New python executable in env/bin/python
|
||||
Installing setuptools............done.
|
||||
$ python setup.py develop
|
||||
...
|
||||
Finished processing dependencies for Flask
|
||||
|
||||
This will pull in the depdenencies and activate the git head as current
|
||||
version. Then you just have to ``git pull origin`` to get the latest
|
||||
version.
|
||||
|
||||
To just get the development version without git, do this instead::
|
||||
|
||||
$ mkdir flask
|
||||
$ cd flask
|
||||
$ virtualenv env
|
||||
$ source env/bin/activate
|
||||
New python executable in env/bin/python
|
||||
Installing setuptools............done.
|
||||
$ easy_install Flask==dev
|
||||
...
|
||||
Finished processing dependencies for Flask==dev
|
||||
|
||||
.. _windows-easy-install:
|
||||
|
||||
`easy_install` on Windows
|
||||
-------------------------
|
||||
|
||||
On Windows installation of `easy_install` is a little bit tricker because
|
||||
on Windows slightly different rules apply, but it's not a biggy. The
|
||||
easiest way to accomplish that is downloading the `ez_setup.py`_ file and
|
||||
running it. (Double clicking should do the trick)
|
||||
|
||||
Once you have done that it's important to add the `easy_install` command
|
||||
and other Python scripts to the path. To do that you have to add the
|
||||
Python installation's Script folder to the `PATH` variable.
|
||||
|
||||
To do that, click right on your "Computer" desktop icon and click
|
||||
"Properties". On Windows Vista and Windows 7 then click on "Advanced System
|
||||
settings", on Windows XP click on the "Advanced" tab instead. Then click
|
||||
on the "Environment variables" button and double click on the "Path"
|
||||
variable in the "System variables" section.
|
||||
|
||||
There append the path of your Python interpreter's Script folder to the
|
||||
end of the last (make sure you delimit it from existing values with a
|
||||
semicolon). Assuming you are using Python 2.6 on the default path, add
|
||||
the following value::
|
||||
|
||||
;C:\Python26\Scripts
|
||||
|
||||
Then you are done. To check if it worked, open the cmd and execute
|
||||
"easy_install". If you have UAC enabled it should prompt you for admin
|
||||
privileges.
|
||||
|
||||
|
||||
.. _ez_setup.py: http://peak.telecommunity.com/dist/ez_setup.py
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
License
|
||||
=======
|
||||
|
||||
Flask is licensed under a three clause `BSD License`_. It basically
|
||||
means: do whatever you want with it as long as the copyright in Flask
|
||||
sticks around, the conditions are not modified and the disclaimer is
|
||||
present. Furthermore you must not use the names of the authors to promote
|
||||
derivates of the software without written consent.
|
||||
|
||||
.. _BSD License:
|
||||
http://en.wikipedia.org/wiki/BSD_licenses#3-clause_license_.28.22New_BSD_License.22.29
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
.. include:: ../AUTHORS
|
||||
|
||||
License Text
|
||||
------------
|
||||
|
||||
.. include:: ../LICENSE
|
||||
139
docs/make.bat
|
|
@ -1,139 +0,0 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Flask.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flask.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% _build/devhelp
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
.. _message-flashing-pattern:
|
||||
|
||||
Message Flashing
|
||||
================
|
||||
|
||||
Good applications and user interfaces are all about feedback. If the user
|
||||
does not get enough feedback he will probably end up hating the
|
||||
application. Flask provides a really simple way to give feedback to a
|
||||
user with the flashing system. The flashing system basically makes it
|
||||
possible to record a message at the end of a request and access it next
|
||||
request and only next request. This is usually combined with a layout
|
||||
template that does this.
|
||||
|
||||
So here a full example::
|
||||
|
||||
from flask import flash, redirect, url_for, render_template
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
if request.form['username'] != 'admin' or \
|
||||
request.form['password'] != 'secret':
|
||||
error = 'Invalid credentials'
|
||||
else:
|
||||
flash('You were sucessfully logged in')
|
||||
return redirect(url_for('index'))
|
||||
return render_template('login.html', error=error)
|
||||
|
||||
And here the ``layout.html`` template which does the magic:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<!doctype html>
|
||||
<title>My Application</title>
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<ul class=flashes>
|
||||
{% for message in messages %}
|
||||
<li>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block body %}{% endblock %}
|
||||
|
||||
And here the index.html template:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<h1>Overview</h1>
|
||||
<p>Do you want to <a href="{{ url_for('login') }}">log in?</a>
|
||||
{% endblock %}
|
||||
|
||||
And of course the login template:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<h1>Login</h1>
|
||||
{% if error %}
|
||||
<p class=error><strong>Error:</strong> {{ error }}
|
||||
{% endif %}
|
||||
<form action="" method=post>
|
||||
<dl>
|
||||
<dt>Username:
|
||||
<dd><input type=text name=username value="{{
|
||||
request.form.username }}">
|
||||
<dt>Password:
|
||||
<dd><input type=password name=password>
|
||||
</dl>
|
||||
<p><input type=submit value=Login>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
.. _patterns:
|
||||
|
||||
Patterns for Flask
|
||||
==================
|
||||
|
||||
Certain things are common enough that the changes are high you will find
|
||||
them in most web applications. For example quite a lot of applications
|
||||
are using relational databases and user authentication. In that case,
|
||||
changes are they will open a database connection at the beginning of the
|
||||
request and get the information of the currently logged in user. At the
|
||||
end of the request, the database connection is closed again.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
packages
|
||||
sqlite3
|
||||
sqlalchemy
|
||||
wtforms
|
||||
templateinheritance
|
||||
flashing
|
||||
jquery
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
AJAX with jQuery
|
||||
================
|
||||
|
||||
`jQuery`_ is a small JavaScript library commonly used to simplify working
|
||||
with the DOM and JavaScript in general. It is the perfect tool to make
|
||||
web applications more dynamic by exchanging JSON between server and
|
||||
client.
|
||||
|
||||
JSON itself is a very lightweight transport format, very similar to how
|
||||
Python primitives (numbers, strings, dicts and lists) look like which is
|
||||
widely supported and very easy to parse. It became popular a few years
|
||||
ago and quickly replaced XML as transport format in web applications.
|
||||
|
||||
If you have Python 2.6 JSON will work out of the box, in Python 2.5 you
|
||||
will have to install the `simplejson`_ library from PyPI.
|
||||
|
||||
.. _jQuery: http://jquery.com/
|
||||
.. _simplejson: http://pypi.python.org/pypi/simplejson
|
||||
|
||||
Loading jQuery
|
||||
--------------
|
||||
|
||||
In order to use jQuery, you have to download it first and place it in the
|
||||
static folder of your application and then ensure it's loaded. Ideally
|
||||
you have a layout template that is used for all pages where you just have
|
||||
to add a script statement to your `head` to load jQuery:
|
||||
|
||||
.. sourcecode:: html
|
||||
|
||||
<script type=text/javascript src="{{
|
||||
url_for('static', filename='jquery.js') }}"></script>
|
||||
|
||||
Another method is using Google's `AJAX Libraries API
|
||||
<http://code.google.com/apis/ajaxlibs/documentation/>`_ to load jQuery:
|
||||
|
||||
.. sourcecode:: html
|
||||
|
||||
<script type=text/javascript
|
||||
src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||
|
||||
In this case you don't have to put jQuery into your static folder, it will
|
||||
instead be loaded from Google directly. This has the advantage that your
|
||||
website will probably load faster for users if they were to at least one
|
||||
other website before using the same jQuery version from Google because it
|
||||
will already be in the browser cache. Downside is that if you don't have
|
||||
network connectivity during development jQuery will not load.
|
||||
|
||||
Where is My Site?
|
||||
-----------------
|
||||
|
||||
Do you know where your application is? If you are developing the answer
|
||||
is quite simple: it's on localhost port something and directly on the root
|
||||
of that server. But what if you later decide to move your application to
|
||||
a different location? For example to ``http://example.com/myapp``? On
|
||||
the server side this never was a problem because we were using the handy
|
||||
:func:`~flask.url_for` function that did could answer that question for
|
||||
us, but if we are using jQuery we should better not hardcode the path to
|
||||
the application but make that dynamic, so how can we do that?
|
||||
|
||||
A simple method would be to add a script tag to our page that sets a
|
||||
global variable to the prefix to the root of the application. Something
|
||||
like this:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<script type=text/javascript>
|
||||
$SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
|
||||
</script>
|
||||
|
||||
The ``|safe`` is necessary so that Jinja does not escape the JSON encoded
|
||||
string with HTML rules. Usually this would be necessary, but we are
|
||||
inside a `script` block here where different rules apply.
|
||||
|
||||
.. admonition:: Information for Pros
|
||||
|
||||
In HTML the `script` tag is declared `CDATA` which means that entities
|
||||
will not be parsed. Everything until ``</script>`` is handled as script.
|
||||
This also means that there must never be any ``</`` between the script
|
||||
tags. ``|tojson`` is kindly enough to do the right thing here and
|
||||
escape slashes for you (``{{ "</script>"|tojson|safe }`` is rendered as
|
||||
``"<\/script>"``).
|
||||
|
||||
|
||||
JSON View Functions
|
||||
-------------------
|
||||
|
||||
Now let's create a server side function that accepts two URL arguments of
|
||||
numbers which should be added together and then sent back to the
|
||||
application in a JSON object. This is a really ridiculous example and is
|
||||
something you usually would do on the client side alone, but a simple
|
||||
example that shows how you would use jQuery and Flask nonetheless::
|
||||
|
||||
from flask import Flask, jsonify, render_template, request
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/_add_numbers')
|
||||
def add_numbers():
|
||||
a = request.args.get('a', 0, type=int)
|
||||
b = request.args.get('b', 0, type=int)
|
||||
return jsonify(result=a + b)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
As you can see I also added an `index` method here that renders a
|
||||
template. This template will load jQuery as above and have a little form
|
||||
we can add two numbers and a link to trigger the function on the server
|
||||
side.
|
||||
|
||||
Note that we are using the :meth:`~werkzeug.MultiDict.get` method here
|
||||
which will never fail. If the key is missing a default value (here ``0``)
|
||||
is returned. Furthermore it can convert values to a specific type (like
|
||||
in our case `int`). This is especially handy for code that is
|
||||
triggered by a script (APIs, JavaScript etc.) because you don't need
|
||||
special error reporting in that case.
|
||||
|
||||
The HTML
|
||||
--------
|
||||
|
||||
You index.html template either has to extend a `layout.html` template with
|
||||
jQuery loaded and the `$SCRIPT_ROOT` variable set, or do that on the top.
|
||||
Here the HTML code needed for our little application (`index.html`).
|
||||
Notice that we also drop the script directly into the HTML here. It is
|
||||
usually a better idea to have that in a separate script file:
|
||||
|
||||
.. sourcecode:: html
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$('a#calculate').bind('click', function() {
|
||||
$.getJSON($SCRIPT_ROOT + '/_add_numbers', {
|
||||
a: $('input[name="a"]').val(),
|
||||
b: $('input[name="b"]').val()
|
||||
}, function(data) {
|
||||
$("#result").text(data.result);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<h1>jQuery Example</h1>
|
||||
<p><input type=text size=5 name=a> +
|
||||
<input type=text size=5 name=b> =
|
||||
<span id=result>?</span>
|
||||
<p><a href=# id=calculate>calculate server side</a>
|
||||
|
||||
I won't got into detail here about how jQuery works, just a very quick
|
||||
explanation of the little bit of code above:
|
||||
|
||||
1. ``$(function() { ... })`` specifies code that should run once the
|
||||
browser is done loading the basic parts of the page.
|
||||
2. ``#('selector')`` selects an element and lets you operate on it.
|
||||
3. ``element.bind('event', func)`` specifies a function that should run
|
||||
when the user clicked on the element. If that function returns
|
||||
`false`, the default behaviour will not kick in (in this case, navigate
|
||||
to the `#` URL).
|
||||
4. ``$.getJSON(url, data, func)`` sends a `GET` request to `url` and will
|
||||
send the contents of the `data` object as query parameters. Once the
|
||||
data arrived, it will call the given function with the return value as
|
||||
argument. Note that we can use the `$SCRIPT_ROOT` variable here that
|
||||
we set earlier.
|
||||
|
||||
If you don't get the whole picture, download the `sourcecode
|
||||
for this example
|
||||
<http://github.com/mitsuhiko/flask/tree/master/examples/jqueryexample>`_
|
||||
from github.
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
.. _larger-applications:
|
||||
|
||||
Larger Applications
|
||||
===================
|
||||
|
||||
For larger applications it's a good idea to use a package instead of a
|
||||
module. That is quite simple. Imagine a small application looks like
|
||||
this::
|
||||
|
||||
/yourapplication
|
||||
/yourapplication.py
|
||||
/static
|
||||
/style.css
|
||||
/templates
|
||||
layout.html
|
||||
index.html
|
||||
login.html
|
||||
...
|
||||
|
||||
To convert that into a larger one, just create a new folder
|
||||
`yourapplication` inside the existing one and move everything below it.
|
||||
Then rename `yourapplication.py` to `__init__.py`. (Make sure to delete
|
||||
all `.pyc` files first, otherwise things would most likely break)
|
||||
|
||||
You should then end up with something like that::
|
||||
|
||||
/yourapplication
|
||||
/yourapplication
|
||||
/__init__.py
|
||||
/static
|
||||
/style.css
|
||||
/templates
|
||||
layout.html
|
||||
index.html
|
||||
login.html
|
||||
...
|
||||
|
||||
But how do you run your application now? The naive ``python
|
||||
yourapplication/__init__.py`` will not work. Let's just say that Python
|
||||
does not want modules in packages to be the startup file. But that is not
|
||||
a big problem, just add a new file called `runserver.py` next to the inner
|
||||
`yourapplication` folder with the following contents::
|
||||
|
||||
from yourapplication import app
|
||||
app.run(debug=True)
|
||||
|
||||
What did we gain from this? Now we can restructure the application a bit
|
||||
into multiple modules. The only thing you have to remember is the
|
||||
following quick checklist:
|
||||
|
||||
1. the `Flask` application object creation has to be in the
|
||||
`__init__.py` file. That way each module can import it safely and the
|
||||
`__name__` variable will resolve to the correct package.
|
||||
2. all the view functions (the ones with a :meth:`~flask.Flask.route`
|
||||
decorator on top) have to be imported when in the `__init__.py` file.
|
||||
Not the object itself, but the module it is in. Do the importing at
|
||||
the *bottom* of the file.
|
||||
|
||||
Here an example `__init__.py`::
|
||||
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
import yourapplication.views
|
||||
|
||||
And this is what `views.py` would look like::
|
||||
|
||||
from yourapplication import app
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return 'Hello World!'
|
||||
|
||||
.. admonition:: Circular Imports
|
||||
|
||||
Every Python programmer hates them, and yet we just added some:
|
||||
circular imports (That's when two modules depend on each other. In this
|
||||
case `views.py` depends on `__init__.py`). Be advised that this is a
|
||||
bad idea in general but here it is actually fine. The reason for this is
|
||||
that we are not actually using the views in `__init__.py` and just
|
||||
ensuring the module is imported and we are doing that at the bottom of
|
||||
the file.
|
||||
|
||||
There are still some problems with that approach but if you want to use
|
||||
decorators there is no way around that. Check out the
|
||||
:ref:`becomingbig` section for some inspiration how to deal with that.
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
.. _sqlalchemy-pattern:
|
||||
|
||||
SQLAlchemy in Flask
|
||||
===================
|
||||
|
||||
Many people prefer `SQLAlchemy`_ for database access. In this case it's
|
||||
encouraged to use a package instead of a module for your flask application
|
||||
and drop the models into a separate module (:ref:`larger-applications`).
|
||||
While that is not necessary, it makes a lot of sense.
|
||||
|
||||
There are three very common ways to use SQLAlchemy. I will outline each
|
||||
of them here:
|
||||
|
||||
Declarative
|
||||
-----------
|
||||
|
||||
The declarative extension in SQLAlchemy is the most recent method of using
|
||||
SQLAlchemy. It allows you to define tables and models in one go, similar
|
||||
to how Django works. In addition to the following text I recommend the
|
||||
official documentation on the `declarative`_ extension.
|
||||
|
||||
Here the example `database.py` module for your application::
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
engine = create_engine('sqlite:////tmp/test.db')
|
||||
db_session = scoped_session(sessionmaker(autocommit=False,
|
||||
autoflush=False,
|
||||
bind=engine))
|
||||
Base = declarative_base()
|
||||
Base.query = db_session.query_property()
|
||||
|
||||
def init_db():
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
To define your models, just subclass the `Base` class that was created by
|
||||
the code above. If you are wondering why we don't have to care about
|
||||
threads here (like we did in the SQLite3 example above with the
|
||||
:data:`~flask.g` object): that's because SQLAlchemy does that for us
|
||||
already with the :class:`~sqlalchemy.orm.scoped_session`.
|
||||
|
||||
To use SQLAlchemy in a declarative way with your application, you just
|
||||
have to put the following code into your application module. Flask will
|
||||
automatically remove database sessions at the end of the request for you::
|
||||
|
||||
from yourapplication.database import db_session
|
||||
|
||||
@app.after_request
|
||||
def shutdown_session(response):
|
||||
db_session.remove()
|
||||
return response
|
||||
|
||||
Here is an example model (put this into `models.py`, e.g.)::
|
||||
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from yourapplication.database import Base
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50), unique=True)
|
||||
email = Column(String(120), unique=True)
|
||||
|
||||
def __init__(self, name=None, email=None):
|
||||
self.name = name
|
||||
self.email = email
|
||||
|
||||
def __repr__(self):
|
||||
return '<User %r>' % (self.name, self.email)
|
||||
|
||||
You can insert entries into the database like this:
|
||||
|
||||
>>> from yourapplication.database import db_session
|
||||
>>> from yourapplication.models import User
|
||||
>>> u = User('admin', 'admin@localhost')
|
||||
>>> db_session.add(u)
|
||||
>>> db_session.commit()
|
||||
|
||||
Querying is simple as well:
|
||||
|
||||
>>> User.query.all()
|
||||
[<User u'admin'>]
|
||||
>>> User.query.filter(User.name == 'admin').first()
|
||||
<User u'admin'>
|
||||
|
||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
||||
.. _declarative:
|
||||
http://www.sqlalchemy.org/docs/reference/ext/declarative.html
|
||||
|
||||
Manual Object Relational Mapping
|
||||
--------------------------------
|
||||
|
||||
Manual object relational mapping has a few upsides and a few downsides
|
||||
versus the declarative approach from above. The main difference is that
|
||||
you define tables and classes separately and map them together. It's more
|
||||
flexible but a little more to type. In general it works like the
|
||||
declarative approach, so make sure to also split up your application into
|
||||
multiple modules in a package.
|
||||
|
||||
Here is an example `database.py` module for your application::
|
||||
|
||||
from sqlalchemy import create_engine, MetaData
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
|
||||
engine = create_engine('sqlite:////tmp/test.db')
|
||||
metadata = MetaData()
|
||||
db_session = scoped_session(sessionmaker(autocommit=False,
|
||||
autoflush=False,
|
||||
bind=engine))
|
||||
def init_db():
|
||||
metadata.create_all(bind=engine)
|
||||
|
||||
As for the declarative approach you need to close the session after
|
||||
each request. Put this into your application module::
|
||||
|
||||
from yourapplication.database import db_session
|
||||
|
||||
@app.after_request
|
||||
def shutdown_session(response):
|
||||
db_session.remove()
|
||||
return response
|
||||
|
||||
Here is an example table and model (put this into `models.py`)::
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, String
|
||||
from sqlalchemy.orm import mapper
|
||||
from yourapplication.database import metadata, db_session
|
||||
|
||||
class User(object):
|
||||
query = db_session.query_property()
|
||||
|
||||
def __init__(self, name=None, email=None):
|
||||
self.name = name
|
||||
self.email = email
|
||||
|
||||
def __repr__(self):
|
||||
return '<User %r>' % (self.name, self.email)
|
||||
|
||||
users = Table('users', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', String(50), unique=True),
|
||||
Column('email', String(120), unique=True)
|
||||
)
|
||||
mapper(User, users)
|
||||
|
||||
Querying and inserting works exactly the same as in the example above.
|
||||
|
||||
|
||||
SQL Abstraction Layer
|
||||
---------------------
|
||||
|
||||
If you just want to use the database system (and SQL) abstraction layer
|
||||
you basically only need the engine::
|
||||
|
||||
from sqlalchemy import create_engine, MetaData
|
||||
|
||||
engine = create_engine('sqlite:////tmp/test.db')
|
||||
metadata = MetaData(bind=engine)
|
||||
|
||||
Then you can either declare the tables in your code like in the examples
|
||||
above, or automatically load them::
|
||||
|
||||
users = Table('users', metadata, autoload=True)
|
||||
|
||||
To insert data you can use the `insert` method. We have to get a
|
||||
connection first so that we can use a transaction:
|
||||
|
||||
>>> con = engine.connect()
|
||||
>>> con.execute(users.insert(name='admin', email='admin@localhost'))
|
||||
|
||||
SQLAlchemy will automatically commit for us.
|
||||
|
||||
To query your database, you use the engine directly or use a connection:
|
||||
|
||||
>>> users.select(users.c.id == 1).execute().first()
|
||||
(1, u'admin', u'admin@localhost')
|
||||
|
||||
These results are also dict-like tuples:
|
||||
|
||||
>>> r = users.select(users.c.id == 1).execute().first()
|
||||
>>> r['name']
|
||||
u'admin'
|
||||
|
||||
You can also pass strings of SQL statements to the
|
||||
:meth:`~sqlalchemy.engine.base.Connection.execute` method:
|
||||
|
||||
>>> engine.execute('select * from users where id = :1', [1]).first()
|
||||
(1, u'admin', u'admin@localhost')
|
||||
|
||||
For more information about SQLAlchemy, head over to the
|
||||
`website <http://sqlalchemy.org/>`_.
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
.. _sqlite3:
|
||||
|
||||
Using SQLite 3 with Flask
|
||||
=========================
|
||||
|
||||
In Flask you can implement opening of database connections at the beginning
|
||||
of the request and closing at the end with the
|
||||
:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.after_request`
|
||||
decorators in combination with the special :class:`~flask.g` object.
|
||||
|
||||
So here a simple example of how you can use SQLite 3 with Flask::
|
||||
|
||||
import sqlite3
|
||||
from flask import g
|
||||
|
||||
DATABASE = '/path/to/database.db'
|
||||
|
||||
def connect_db():
|
||||
return sqlite3.connect(DATABASE)
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
g.db = connect_db()
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
g.db.close()
|
||||
return response
|
||||
|
||||
.. _easy-querying:
|
||||
|
||||
Easy Querying
|
||||
-------------
|
||||
|
||||
Now in each request handling function you can access `g.db` to get the
|
||||
current open database connection. To simplify working with SQLite a
|
||||
helper function can be useful::
|
||||
|
||||
def query_db(query, args=(), one=False):
|
||||
cur = g.db.execute(query, args)
|
||||
rv = [dict((cur.description[idx][0], value)
|
||||
for idx, value in enumerate(row)) for row in cur.fetchall()]
|
||||
return (rv[0] if rv else None) if one else rv
|
||||
|
||||
This handy little function makes working with the database much more
|
||||
pleasant than it is by just using the raw cursor and connection objects.
|
||||
|
||||
Here is how you can use it::
|
||||
|
||||
for user in query_db('select * from users'):
|
||||
print user['username'], 'has the id', user['user_id']
|
||||
|
||||
Or if you just want a single result::
|
||||
|
||||
user = query_db('select * from users where username = ?',
|
||||
[the_username], one=True)
|
||||
if user is None:
|
||||
print 'No such user'
|
||||
else:
|
||||
print the_username, 'has the id', user['user_id']
|
||||
|
||||
To pass variable parts to the SQL statement, use a question mark in the
|
||||
statement and pass in the arguments as a list. Never directly add them to
|
||||
the SQL statement with string formattings because this makes it possible
|
||||
to attack the application using `SQL Injections
|
||||
<http://en.wikipedia.org/wiki/SQL_injection>`_.
|
||||
|
||||
Initial Schemas
|
||||
---------------
|
||||
|
||||
Relational databases need schemas, so applications often ship a
|
||||
`schema.sql` file that creates the database. It's a good idea to provide
|
||||
a function that creates the database based on that schema. This function
|
||||
can do that for you::
|
||||
|
||||
from contextlib import closing
|
||||
|
||||
def init_db():
|
||||
with closing(connect_db()) as db:
|
||||
with app.open_resource('schema.sql') as f:
|
||||
db.cursor().executescript(f.read())
|
||||
db.commit()
|
||||
|
||||
You can then create such a database from the python shell:
|
||||
|
||||
>>> from yourapplication import init_db
|
||||
>>> init_db()
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
.. _template-inheritance:
|
||||
|
||||
Template Inheritance
|
||||
====================
|
||||
|
||||
The most powerful part of Jinja is template inheritance. Template inheritance
|
||||
allows you to build a base "skeleton" template that contains all the common
|
||||
elements of your site and defines **blocks** that child templates can override.
|
||||
|
||||
Sounds complicated but is very basic. It's easiest to understand it by starting
|
||||
with an example.
|
||||
|
||||
|
||||
Base Template
|
||||
-------------
|
||||
|
||||
This template, which we'll call ``layout.html``, defines a simple HTML skeleton
|
||||
document that you might use for a simple two-column page. It's the job of
|
||||
"child" templates to fill the empty blocks with content:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
<title>{% block title %}{% endblock %} - My Webpage</title>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">{% block content %}{% endblock %}</div>
|
||||
<div id="footer">
|
||||
{% block footer %}
|
||||
© Copyright 2010 by <a href="http://domain.invalid/">you</a>.
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
In this example, the ``{% block %}`` tags define four blocks that child templates
|
||||
can fill in. All the `block` tag does is to tell the template engine that a
|
||||
child template may override those portions of the template.
|
||||
|
||||
Child Template
|
||||
--------------
|
||||
|
||||
A child template might look like this:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Index{% endblock %}
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<style type="text/css">
|
||||
.important { color: #336699; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Index</h1>
|
||||
<p class="important">
|
||||
Welcome on my awesome homepage.
|
||||
{% endblock %}
|
||||
|
||||
The ``{% extends %}`` tag is the key here. It tells the template engine that
|
||||
this template "extends" another template. When the template system evaluates
|
||||
this template, first it locates the parent. The extends tag must be the
|
||||
first tag in the template. To render the contents of a block defined in
|
||||
the parent template, use ``{{ super() }}``.
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
Form Validation with WTForms
|
||||
============================
|
||||
|
||||
When you have to work with form data submitted by a browser view code
|
||||
quickly becomes very hard to read. There are libraries out there designed
|
||||
to make this process easier to manage. One of them is `WTForms`_ which we
|
||||
will handle here. If you find yourself in the situation of having many
|
||||
forms, you might want to give it a try.
|
||||
|
||||
When you are working with WTForms you have to define your forms as classes
|
||||
first. I recommend breaking up the application into multiple modules
|
||||
(:ref:`larger-applications`) for that and adding a separate module for the
|
||||
forms.
|
||||
|
||||
The Forms
|
||||
---------
|
||||
|
||||
This is an example form for a typical registration page::
|
||||
|
||||
from wtforms import Form, BooleanField, TextField, validators
|
||||
|
||||
class RegistrationForm(Form):
|
||||
username = TextField('Username', [validators.Length(min=4, max=25)])
|
||||
email = TextField('Email Address', [validators.Length(min=6, max=35)])
|
||||
password = PasswordField('New Password', [validators.Required()])
|
||||
confirm = PasswordField('Repeat Password', [validators.EqualTo(
|
||||
'confirm', message='Passwords must match')])
|
||||
accept_tos = BooleanField('I accept the TOS', [validators.Required()])
|
||||
|
||||
In the View
|
||||
-----------
|
||||
|
||||
In the view function, the usage of this form looks like this::
|
||||
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
form = RegistrationForm(request.form)
|
||||
if request.method == 'POST' and form.validate():
|
||||
user = User(form.username.data, form.email.data,
|
||||
form.password.data)
|
||||
db_session.add(user)
|
||||
flash('Thanks for registering')
|
||||
redirect(url_for('login'))
|
||||
return render_template('register.html', form=form)
|
||||
|
||||
Notice that we are implying that the view is using SQLAlchemy here
|
||||
(:ref:`sqlalchemy-pattern`) but this is no requirement of course. Adapt
|
||||
the code as necessary.
|
||||
|
||||
Things to remember:
|
||||
|
||||
1. create the form from the request :attr:`~flask.request.form` value if
|
||||
the data is submitted via the HTTP `POST` method and
|
||||
:attr:`~flask.request.args` if the data is submitted as `GET`.
|
||||
2. to validate the data, call the :func:`~wtforms.form.Form.validate`
|
||||
method which will return `True` if the data validates, `False`
|
||||
otherwise.
|
||||
3. to access individual values from the form, access `form.<NAME>.data`.
|
||||
|
||||
Forms in Templates
|
||||
------------------
|
||||
|
||||
Now to the template side. When you pass the form to the templates you can
|
||||
easily render them there. Look at the following example template to see
|
||||
how easy this is. WTForms does half the form generation for us already.
|
||||
To make it even nicer, we can write a macro that renders a field with
|
||||
label and a list of errors if there are any.
|
||||
|
||||
Here an example `_formhelpers.html` template with such a macro:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% macro render_field(field) %}
|
||||
<dt>{{ field.label }}
|
||||
<dd>{{ field(**kwargs)|safe }}
|
||||
{% if field.errors %}
|
||||
<ul class="errors">
|
||||
{% for error in field.errors %}<li>{{ error }}{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endmacro %}
|
||||
|
||||
This macro accepts a couple of keyword arguments that are forwarded to
|
||||
WTForm's field function that renders the field for us. They keyword
|
||||
arguments will be inserted as HTML attributes. So for example you can
|
||||
call ``render_field(form.username, class='username')`` to add a class to
|
||||
the input element. Note that WTForms returns standard Python unicode
|
||||
strings, so we have to tell Jinja2 that this data is already HTML escaped
|
||||
with the `|safe` filter.
|
||||
|
||||
Here the `register.html` template for the function we used above which
|
||||
takes advantage of the `_formhelpers.html` template:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% from "_formhelpers.html" import render_field %}
|
||||
<form method="post" action="/register">
|
||||
<dl>
|
||||
{{ render_field(form.username) }}
|
||||
{{ render_field(form.email) }}
|
||||
{{ render_field(form.password) }}
|
||||
{{ render_field(form.confirm) }}
|
||||
{{ render_field(form.accept_tos) }}
|
||||
</dl>
|
||||
<p><input type=submit value=Register>
|
||||
</form>
|
||||
|
||||
For more information about WTForms, head over to the `WTForms
|
||||
website`_.
|
||||
|
||||
.. _WTForms: http://wtforms.simplecodes.com/
|
||||
.. _WTForms website: http://wtforms.simplecodes.com/
|
||||
|
|
@ -1,618 +0,0 @@
|
|||
.. _quickstart:
|
||||
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
Eager to get started? This page gives a good introduction in how to gets
|
||||
started with Flask. This assumes you already have Flask installed. If
|
||||
you do not, head over to the :ref:`installation` section.
|
||||
|
||||
|
||||
A Minimal Application
|
||||
---------------------
|
||||
|
||||
A minimal Flask application looks something like that::
|
||||
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def hello_world():
|
||||
return "Hello World!"
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
|
||||
Just save it as `hello.py` or something similar and run it with your
|
||||
Python interpreter. Make sure to not call your application `flask.py`
|
||||
because this would conflict with Flask itself.
|
||||
|
||||
::
|
||||
|
||||
$ python hello.py
|
||||
* Running on http://127.0.0.1:5000/
|
||||
|
||||
Head over to `http://127.0.0.1:5000/ <http://127.0.0.1:5000/>`_, you should
|
||||
see your hello world greeting.
|
||||
|
||||
So what did that code do?
|
||||
|
||||
1. first we imported the :class:`~flask.Flask` class. An instance of this
|
||||
class will be our WSGI application.
|
||||
2. next we create an instance of it. We pass it the name of the module /
|
||||
package. This is needed so that Flask knows where it should look for
|
||||
templates, static files and so on.
|
||||
3. Then we use the :meth:`~flask.Flask.route` decorator to tell Flask
|
||||
what URL should trigger our function.
|
||||
4. The function then has a name which is also used to generate URLs to
|
||||
that particular function, and returns the message we want to display in
|
||||
the user's browser.
|
||||
5. Finally we use the :meth:`~flask.Flask.run` function to run the
|
||||
local server with our application. The ``if __name__ == '__main__':``
|
||||
makes sure the server only runs if the script is executed directly from
|
||||
the Python interpreter and not used as imported module.
|
||||
|
||||
To stop the server, hit control-C.
|
||||
|
||||
|
||||
Debug Mode
|
||||
----------
|
||||
|
||||
Now that :meth:`~flask.Flask.run` method is nice to start a local
|
||||
development server, but you would have to restart it manually after each
|
||||
change you do to code. That is not very nice and Flask can do better. If
|
||||
you enable the debug support the server will reload itself on code changes
|
||||
and also provide you with a helpful debugger if things go wrong.
|
||||
|
||||
There are two ways to enable debugging. Either set that flag on the
|
||||
applciation object::
|
||||
|
||||
app.debug = True
|
||||
app.run()
|
||||
|
||||
Or pass it to run::
|
||||
|
||||
app.run(debug=True)
|
||||
|
||||
Both will have exactly the same effect.
|
||||
|
||||
.. admonition:: Attention
|
||||
|
||||
The interactive debugger however does not work in forking environments
|
||||
which makes it nearly impossible to use on production servers but the
|
||||
debugger still allows the execution of arbitrary code which makes it a
|
||||
major security risk and **must never be used on production machines**
|
||||
because of that.
|
||||
|
||||
Screenshot of the debugger in action:
|
||||
|
||||
.. image:: _static/debugger.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: screenshot of debugger in action
|
||||
|
||||
|
||||
Routing
|
||||
-------
|
||||
|
||||
As you have seen above, the :meth:`~flask.Flask.route` decorator is used
|
||||
to bind a function to a URL. But there is more to it! You can make
|
||||
certain parts of the URL dynamic and attach multiple rules to a function.
|
||||
|
||||
Here some examples::
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return 'Index Page'
|
||||
|
||||
@app.route('/hello')
|
||||
def hello():
|
||||
return 'Hello World'
|
||||
|
||||
|
||||
Variable Rules
|
||||
``````````````
|
||||
|
||||
Modern web applications have beautiful URLs. This helps people remember
|
||||
the URLs which is especially handy for applications that are used from
|
||||
mobile devices with slower network connections. If the user can directly
|
||||
go to the desired page without having to hit the index page it is more
|
||||
likely he will like the page and come back next time.
|
||||
|
||||
To add variable parts to a URL you can mark these special sections as
|
||||
``<variable_name>``. Such a part is then passed as keyword argument to
|
||||
your function. Optionally a converter can be specifed by specifying a
|
||||
rule with ``<converter:variable_name>``. Here some nice examples::
|
||||
|
||||
@app.route('/user/<username>')
|
||||
def show_user_profile(username):
|
||||
# show the user profile for that user
|
||||
pass
|
||||
|
||||
@app.route('/post/<int:post_id>')
|
||||
def show_post(post_id):
|
||||
# show the post with the given id, the id is an integer
|
||||
pass
|
||||
|
||||
The following converters exist:
|
||||
|
||||
=========== ===========================================
|
||||
`int` accepts integers
|
||||
`float` like `int` but for floating point values
|
||||
`path` like the default but also accepts slashes
|
||||
=========== ===========================================
|
||||
|
||||
URL Building
|
||||
````````````
|
||||
|
||||
If it can match URLs, can it also generate them? Of course you can. To
|
||||
build a URL to a specific function you can use the :func:`~flask.url_for`
|
||||
function. It accepts the name of the function as first argument and a
|
||||
number of keyword arguments, each corresponding to the variable part of
|
||||
the URL rule. Here some examples:
|
||||
|
||||
>>> from flask import Flask, url_for
|
||||
>>> app = Flask(__name__)
|
||||
>>> @app.route('/')
|
||||
... def index(): pass
|
||||
...
|
||||
>>> @app.route('/login')
|
||||
... def login(): pass
|
||||
...
|
||||
>>> @app.route('/user/<username>')
|
||||
... def profile(username): pass
|
||||
...
|
||||
>>> with app.test_request_context():
|
||||
... print url_for('index')
|
||||
... print url_for('login')
|
||||
... print url_for('profile', username='John Doe')
|
||||
...
|
||||
/
|
||||
/login
|
||||
/user/John%20Doe
|
||||
|
||||
(This also uses the :meth:`~flask.Flask.test_request_context` method
|
||||
explained below. It basically tells flask to think we are handling a
|
||||
request even though we are not, we are in an interactive Python shell.
|
||||
Have a look at the explanation below. :ref:`context-locals`).
|
||||
|
||||
Why would you want to build URLs instead of hardcoding them in your
|
||||
templates? There are three good reasons for this:
|
||||
|
||||
1. reversing is often more descriptive than hardcoding the URLs. Also and
|
||||
more importantly you can change URLs in one go without having to change
|
||||
the URLs all over the place.
|
||||
2. URL building will handle escaping of special characters and unicode
|
||||
data transparently for you, you don't have to deal with that.
|
||||
3. If your application is placed outside the URL root (so say in
|
||||
``/myapplication`` instead of ``/``), :func:`~flask.url_for` will
|
||||
handle that properly for you.
|
||||
|
||||
|
||||
HTTP Methods
|
||||
````````````
|
||||
|
||||
HTTP (the protocol web applications are speaking) knows different methods
|
||||
to access URLs. By default a route only answers to `GET` requests, but
|
||||
that can be changed by providing the `methods` argument to the
|
||||
:meth:`~flask.Flask.route` decorator. Here some examples::
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
do_the_login()
|
||||
else:
|
||||
show_the_login_form()
|
||||
|
||||
If `GET` is present, `HEAD` will be added automatically for you. You
|
||||
don't have to deal with that. It will also make sure that `HEAD` requests
|
||||
are handled like the `HTTP RFC`_ (the document describing the HTTP
|
||||
protocol) demands, so you can completely ignore that part of the HTTP
|
||||
specification.
|
||||
|
||||
You have no idea what an HTTP method is? Worry not, here quick
|
||||
introduction in HTTP methods and why they matter:
|
||||
|
||||
The HTTP method (also often called "the verb") tells the server what the
|
||||
clients wants to *do* with the requested page. The following methods are
|
||||
very common:
|
||||
|
||||
`GET`
|
||||
The Browser tells the server: just *get* me the information stored on
|
||||
that page and send them to me. This is probably the most common
|
||||
method.
|
||||
|
||||
`HEAD`
|
||||
The Browser tells the server: get me the information, but I am only
|
||||
interested in the *headers*, not the content of the page. An
|
||||
application is supposed to handle that as if a `GET` request was
|
||||
received but not deliver the actual contents. In Flask you don't have
|
||||
to deal with that at all, the underlying Werkzeug library handles that
|
||||
for you.
|
||||
|
||||
`POST`
|
||||
The browser tells the server that it wants to *post* some new
|
||||
information to that URL and that the server must ensure the data is
|
||||
stored and only stored once. This is how HTML forms are usually
|
||||
transmitting data to the server.
|
||||
|
||||
`PUT`
|
||||
Similar to `POST` but the server might trigger the store procedure
|
||||
multiple times by overwriting the old values more than once. Now you
|
||||
might be asking why this is any useful, but there are some good
|
||||
reasons to do that. Consider the connection is lost during
|
||||
transmission, in that situation a system between the browser and the
|
||||
server might sent the request safely a second time without breaking
|
||||
things. With `POST` that would not be possible because it must only
|
||||
be triggered once.
|
||||
|
||||
`DELETE`
|
||||
Remove the information that the given location.
|
||||
|
||||
Now the interesting part is that in HTML4 and XHTML1, the only methods a
|
||||
form might submit to the server are `GET` and `POST`. But with JavaScript
|
||||
and future HTML standards you can use other methods as well. Furthermore
|
||||
HTTP became quite popular lately and there are more things than browsers
|
||||
that are speaking HTTP. (Your revision control system for instance might
|
||||
speak HTTP)
|
||||
|
||||
.. _HTTP RFC: http://www.ietf.org/rfc/rfc2068.txt
|
||||
|
||||
Static Files
|
||||
------------
|
||||
|
||||
Dynamic web applications need static files as well. That's usually where
|
||||
the CSS and JavaScript files are coming from. Ideally your web server is
|
||||
configured to serve them for you, but during development Flask can do that
|
||||
as well. Just create a folder called `static` in your package or next to
|
||||
your module and it will be available at `/static` on the application.
|
||||
|
||||
To generate URLs to that part of the URL, use the special ``'static'`` URL
|
||||
name::
|
||||
|
||||
url_for('static', filename='style.css')
|
||||
|
||||
The file has to be stored on the filesystem as ``static/style.css``.
|
||||
|
||||
Rendering Templates
|
||||
-------------------
|
||||
|
||||
Generating HTML from within Python is not fun, and actually pretty
|
||||
cumbersome because you have to do the HTML escaping on your own to keep
|
||||
the application secure. Because of that Flask configures the `Jinja2
|
||||
<http://jinja.pocoo.org/2/>`_ template engine for you automatically.
|
||||
|
||||
To render a template you can use the :func:`~flask.render_template`
|
||||
method. All you have to do is to provide the name of the template and the
|
||||
variables you want to pass to the template engine as keyword arguments.
|
||||
Here a simple example of how to render a template::
|
||||
|
||||
from flask import render_template
|
||||
|
||||
@app.route('/hello/')
|
||||
@app.route('/hello/<name>')
|
||||
def hello(name=None):
|
||||
return render_template('hello.html', name=name)
|
||||
|
||||
Flask will look for templates in the `templates` folder. So if your
|
||||
application is a module, that folder is next to that module, if it's a
|
||||
pacakge it's actually inside your package:
|
||||
|
||||
**Case 1**: a module::
|
||||
|
||||
/application.py
|
||||
/templates
|
||||
/hello.html
|
||||
|
||||
**Case 2**: a package::
|
||||
|
||||
/application
|
||||
/__init__.py
|
||||
/templates
|
||||
/hello.html
|
||||
|
||||
For templates you can use the full power of Jinja2 templates. Head over
|
||||
to the `Jinja2 Template Documentation
|
||||
<http://jinja.pocoo.org/2/documentation/templates>`_ for more information.
|
||||
|
||||
Here an example template:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<!doctype html>
|
||||
<title>Hello from Flask</title>
|
||||
{% if name %}
|
||||
<h1>Hello {{ name }}!</h1>
|
||||
{% else %}
|
||||
<h1>Hello World!</h1>
|
||||
{% endif %}
|
||||
|
||||
Inside templates you also have access to the :class:`~flask.request`,
|
||||
:class:`~flask.session` and :class:`~flask.g` [#]_ objects
|
||||
as well as the :func:`~flask.get_flashed_messages` function.
|
||||
|
||||
Templates are especially useful if inheritance is used. If you want to
|
||||
know how that works, head over to the :ref:`template-inheritance` pattern
|
||||
documentation. Basically template inheritance makes it possible to keep
|
||||
certain elements on each page (like header, navigation and footer).
|
||||
|
||||
Automatic escaping is enabled, so if name contains HTML it will be escaped
|
||||
automatically. If you can trust a variable and you know that it will be
|
||||
safe HTML (because for example it came from a module that converts wiki
|
||||
markup to HTML) you can mark it as safe by using the
|
||||
:class:`~jinja2.Markup` class or by using the ``|safe`` filter in the
|
||||
template. Head over to the Jinja 2 documentation for more examples.
|
||||
|
||||
Here a basic introduction in how the :class:`~jinja2.Markup` class works:
|
||||
|
||||
>>> from flask import Markup
|
||||
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
|
||||
Markup(u'<strong>Hello <blink>hacker</blink>!</strong>')
|
||||
>>> Markup.escape('<blink>hacker</blink>')
|
||||
Markup(u'<blink>hacker</blink>')
|
||||
>>> Markup('<em>Marked up</em> » HTML').striptags()
|
||||
u'Marked up \xbb HTML'
|
||||
|
||||
.. [#] Unsure what that :class:`~flask.g` object is? It's something you
|
||||
can store information on yourself, check the documentation of that
|
||||
object (:class:`~flask.g`) and the :ref:`sqlite3` for more
|
||||
information.
|
||||
|
||||
|
||||
Accessing Request Data
|
||||
----------------------
|
||||
|
||||
For web applications it's crucial to react to the data a client sent to
|
||||
the server. In Flask this information is provided by the global
|
||||
:class:`~flask.request` object. If you have some experience with Python
|
||||
you might be wondering how that object can be global and how Flask
|
||||
manages to still be threadsafe. The answer are context locals:
|
||||
|
||||
|
||||
.. _context-locals:
|
||||
|
||||
Context Locals
|
||||
``````````````
|
||||
|
||||
.. admonition:: Insider Information
|
||||
|
||||
If you want to understand how that works and how you can implement
|
||||
tests with context locals, read this section, otherwise just skip it.
|
||||
|
||||
Certain objects in Flask are global objects, but not just a standard
|
||||
global object, but actually a proxy to an object that is local to a
|
||||
specific context. What a mouthful. But that is actually quite easy to
|
||||
understand.
|
||||
|
||||
Imagine the context being the handling thread. A request comes in and the
|
||||
webserver decides to spawn a new thread (or something else, the
|
||||
underlying object is capable of dealing with other concurrency systems
|
||||
than threads as well). When Flask starts its internal request handling it
|
||||
figures out that the current thread is the active context and binds the
|
||||
current application and the WSGI environments to that context (thread).
|
||||
It does that in an intelligent way that one application can invoke another
|
||||
application without breaking.
|
||||
|
||||
So what does this mean to you? Basically you can completely ignore that
|
||||
this is the case unless you are unittesting or something different. You
|
||||
will notice that code that depends on a request object will suddenly break
|
||||
because there is no request object. The solution is creating a request
|
||||
object yourself and binding it to the context. The easiest solution for
|
||||
unittesting is by using the :meth:`~flask.Flask.test_request_context`
|
||||
context manager. In combination with the `with` statement it will bind a
|
||||
test request so that you can interact with it. Here an example::
|
||||
|
||||
from flask import request
|
||||
|
||||
with app.test_request_context('/hello', method='POST'):
|
||||
# now you can do something with the request until the
|
||||
# end of the with block, such as basic assertions:
|
||||
assert request.path == '/hello'
|
||||
assert request.method == 'POST'
|
||||
|
||||
The other possibility is passing a whole WSGI environment to the
|
||||
:meth:`~flask.Flask.request_context` method::
|
||||
|
||||
from flask import request
|
||||
|
||||
with app.request_context(environ):
|
||||
assert request.method == 'POST'
|
||||
|
||||
The Request Object
|
||||
``````````````````
|
||||
|
||||
The request object is documented in the API section and we will not cover
|
||||
it here in detail (see :class:`~flask.request`), but just mention some of
|
||||
the most common operations. First of all you have to import it from the
|
||||
the `flask` module::
|
||||
|
||||
from flask import request
|
||||
|
||||
The current request method is available by using the
|
||||
:attr:`~flask.request.method` attribute. To access form data (data
|
||||
transmitted in a `POST` or `PUT` request) you can use the
|
||||
:attr:`~flask.request.form` attribute. Here a full example of the two
|
||||
attributes mentioned above::
|
||||
|
||||
@app.route('/login', method=['POST', 'GET'])
|
||||
def login():
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
if valid_login(request.form['username'],
|
||||
request.form['password']):
|
||||
return log_the_user_in(request.form['username'])
|
||||
else:
|
||||
error = 'Invalid username/password'
|
||||
# this is executed if the request method was GET or the
|
||||
# credentials were invalid
|
||||
|
||||
What happens if the key does not exist in the `form` attribute? In that
|
||||
case a special :exc:`KeyError` is raised. You can catch it like a
|
||||
standard :exc:`KeyError` but if you don't do that, a HTTP 400 Bad Request
|
||||
error page is shown instead. So for many situations you don't have to
|
||||
deal with that problem.
|
||||
|
||||
To access parameters submitted in the URL (``?key=value``) you can use the
|
||||
:attr:`~flask.request.args` attribute::
|
||||
|
||||
searchword = request.args.get('q', '')
|
||||
|
||||
We recommend accessing URL parameters with `get` or by catching the
|
||||
`KeyError` because users might change the URL and presenting them a 400
|
||||
bad request page in that case is a bit user unfriendly.
|
||||
|
||||
For a full list of methods and attribtues on that object, head over to the
|
||||
:class:`~flask.request` documentation.
|
||||
|
||||
|
||||
File Uploads
|
||||
````````````
|
||||
|
||||
Obviously you can handle uploaded files with Flask just as easy. Just
|
||||
make sure not to forget to set the ``enctype="multipart/form-data"``
|
||||
attribtue on your HTML form, otherwise the browser will not transmit your
|
||||
files at all.
|
||||
|
||||
Uploaded files are stored in memory or at a temporary location on the
|
||||
filesystem. You can access those files by looking at the
|
||||
:attr:`~flask.request.files` attribute on the request object. Each
|
||||
uploaded file is stored in that dictionary. It behaves just like a
|
||||
standard Python :class:`file` object, but it also has a
|
||||
:meth:`~werkzeug.FileStorage.save` method that allows you to store that
|
||||
file on the filesystem of the server. Here a simple example how that
|
||||
works::
|
||||
|
||||
from flask import request
|
||||
|
||||
@app.route('/upload', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
f = request.files['the_file']
|
||||
f.save('/var/www/uploads/uploaded_file.txt')
|
||||
...
|
||||
|
||||
If you want to know how the file was named on the client before it was
|
||||
uploaded to your application, you can access the
|
||||
:attr:`~werkzeug.FileStorage.filename` attribute. However please keep in
|
||||
mind that this value can be forged so never ever trust that value. If you
|
||||
want to use the filename of the client to store the file on the server,
|
||||
pass it through the :func:`~werkzeug.secure_filename` function that
|
||||
Werkzeug provides for you::
|
||||
|
||||
from flask import request
|
||||
from werkzeug import secure_filename
|
||||
|
||||
@app.route('/upload', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
f= request.files['the_file']
|
||||
f.save('/var/www/uploads/' + secure_filename(f.filename))
|
||||
...
|
||||
|
||||
Cookies
|
||||
```````
|
||||
|
||||
To access cookies you can use the :attr:`~flask.request.cookies`
|
||||
attribute. Again this is a dictionary with all the cookies the client
|
||||
transmits. If you want to use sessions, do not use the cookies directly
|
||||
but instead use the :ref:`sessions` in Flask that add some security on top
|
||||
of cookies for you.
|
||||
|
||||
|
||||
Redirects and Errors
|
||||
--------------------
|
||||
|
||||
To redirect a user to somewhere else you can use the
|
||||
:func:`~flask.redirect` function, to abort a request early with an error
|
||||
code the :func:`~flask.abort` function. Here an example how this works::
|
||||
|
||||
from flask import abort, redirect, url_for
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/login')
|
||||
def login():
|
||||
abort(401)
|
||||
this_is_never_executed()
|
||||
|
||||
This is a rather pointless example because a user will be redirected from
|
||||
the index to a page he cannot access (401 means access denied) but it
|
||||
shows how that works.
|
||||
|
||||
By default a black and white error page is shown for each error code. If
|
||||
you want to customize the error page, you can use the
|
||||
:meth:`~flask.Flask.errorhandler` decorator::
|
||||
|
||||
from flask import render_template
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(error):
|
||||
return render_template('page_not_found.html'), 404
|
||||
|
||||
Note the ``404`` after the :func:`~flask.render_template` call. This
|
||||
tells Flask that the status code of that page should be 404 which means
|
||||
not found. By default 200 is assumed which translats to: all went well.
|
||||
|
||||
.. _sessions:
|
||||
|
||||
Sessions
|
||||
--------
|
||||
|
||||
Besides the request object there is also a second object called
|
||||
:class:`~flask.session` that allows you to store information specific to a
|
||||
user from one request to the next. This is implemented on top of cookies
|
||||
for you and signes the cookies cryptographically. What this means is that
|
||||
the user could look at the contents of your cookie but not modify it,
|
||||
unless he knows the secret key used for signing.
|
||||
|
||||
In order to use sessions you have to set a secret key. Here is how
|
||||
sessions work::
|
||||
|
||||
from flask import session, redirect, url_for, escape
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
if 'username' in session:
|
||||
return 'Logged in as %s' % escape(session['username'])
|
||||
return 'You are not logged in'
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
session['username'] = request.form['username']
|
||||
return redirect(url_for('index'))
|
||||
return '''
|
||||
<form action="" method="post">
|
||||
<p><input type=text name=username>
|
||||
<p><input type=submit value=Login>
|
||||
</form>
|
||||
'''
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
# remove the username from the session if its there
|
||||
session.pop('username', None)
|
||||
|
||||
# set the secret key. keep this really secret:
|
||||
app.secret_key = 'the secret key'
|
||||
|
||||
The here mentioned :func:`~flask.escape` does escaping for you if you are
|
||||
not using the template engine (like in this example).
|
||||
|
||||
Message Flashing
|
||||
----------------
|
||||
|
||||
Good applications and user interfaces are all about feedback. If the user
|
||||
does not get enough feedback he will probably end up hating the
|
||||
application. Flask provides a really simple way to give feedback to a
|
||||
user with the flashing system. The flashing system basically makes it
|
||||
possible to record a message at the end of a request and access it next
|
||||
request and only next request. This is usually combined with a layout
|
||||
template that does this.
|
||||
|
||||
To flash a message use the :func:`~flask.flash` method, to get hold of the
|
||||
messages you can use :func:`~flask.get_flashed_messages` which is also
|
||||
available in the templates. Check out the :ref:`message-flashing-pattern`
|
||||
for a full example.
|
||||
197
docs/testing.rst
|
|
@ -1,197 +0,0 @@
|
|||
.. _testing:
|
||||
|
||||
Testing Flask Applications
|
||||
==========================
|
||||
|
||||
**Something that is untested is broken.**
|
||||
|
||||
Not sure where that is coming from, and it's not entirely correct, but
|
||||
also not that far from the truth. Untested applications make it hard to
|
||||
improve existing code and developers of untested applications tend to
|
||||
become pretty paranoid. If an application however has automated tests, you
|
||||
can safely change things and you will instantly know if your change broke
|
||||
something.
|
||||
|
||||
Flask gives you a couple of ways to test applications. It mainly does
|
||||
that by exposing the Werkzeug test :class:`~werkzeug.Client` class to your
|
||||
code and handling the context locals for you. You can then use that with
|
||||
your favourite testing solution. In this documentation we will use the
|
||||
:mod:`unittest` package that comes preinstalled with each Python
|
||||
installation.
|
||||
|
||||
The Application
|
||||
---------------
|
||||
|
||||
First we need an application to test for functionality. For the testing
|
||||
we will use the application from the :ref:`tutorial`. If you don't have
|
||||
that application yet, get the sources from `the examples`_.
|
||||
|
||||
.. _the examples:
|
||||
http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/
|
||||
|
||||
The Testing Skeleton
|
||||
--------------------
|
||||
|
||||
In order to test that, we add a second module (
|
||||
`flaskr_tests.py`) and create a unittest skeleton there::
|
||||
|
||||
import os
|
||||
import flaskr
|
||||
import unittest
|
||||
import tempfile
|
||||
|
||||
class FlaskrTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.db_fd, flaskr.DATABASE = tempfile.mkstemp()
|
||||
self.app = flaskr.app.test_client()
|
||||
flaskr.init_db()
|
||||
|
||||
def tearDown(self):
|
||||
os.close(self.db_fd)
|
||||
os.unlink(flaskr.DATABASE)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
The code in the :meth:`~unittest.TestCase.setUp` method creates a new test
|
||||
client and initializes a new database. That function is called before
|
||||
each individual test function. To delete the database after the test, we
|
||||
close the file and remove it from the filesystem in the
|
||||
:meth:`~unittest.TestCase.tearDown` method. What the test client does is
|
||||
give us a simple interface to the application. We can trigger test
|
||||
requests to the application and the client will also keep track of cookies
|
||||
for us.
|
||||
|
||||
Because SQLite3 is filesystem-based we can easily use the tempfile module
|
||||
to create a temporary database and initialize it. The
|
||||
:func:`~tempfile.mkstemp` function does two things for us: it returns a
|
||||
low-level file handle and a random file name, the latter we use as
|
||||
database name. We just have to keep the `db_fd` around so that we can use
|
||||
the :func:`os.close` function to close the file.
|
||||
|
||||
If we now run that testsuite, we should see the following output::
|
||||
|
||||
$ python flaskr_tests.py
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 0 tests in 0.000s
|
||||
|
||||
OK
|
||||
|
||||
Even though it did not run any tests, we already know that our flaskr
|
||||
application is syntactically valid, otherwise the import would have died
|
||||
with an exception.
|
||||
|
||||
The First Test
|
||||
--------------
|
||||
|
||||
Now we can add the first test. Let's check that the application shows
|
||||
"No entries here so far" if we access the root of the application (``/``).
|
||||
For that we modify our created test case class so that it looks like
|
||||
this::
|
||||
|
||||
class FlaskrTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.db_fd, flaskr.DATABASE = tempfile.mkstemp()
|
||||
self.app = flaskr.app.test_client()
|
||||
flaskr.init_db()
|
||||
|
||||
def tearDown(self):
|
||||
os.close(self.db_fd)
|
||||
os.unlink(flaskr.DATABASE)
|
||||
|
||||
def test_empty_db(self):
|
||||
rv = self.app.get('/')
|
||||
assert 'No entries here so far' in rv.data
|
||||
|
||||
Test functions begin with the word `test`. Every function named like that
|
||||
will be picked up automatically. By using `self.app.get` we can send an
|
||||
HTTP `GET` request to the application with the given path. The return
|
||||
value will be a :class:`~flask.Flask.response_class` object. We can now
|
||||
use the :attr:`~werkzeug.BaseResponse.data` attribute to inspect the
|
||||
return value (as string) from the application. In this case, we ensure
|
||||
that ``'No entries here so far'`` is part of the output.
|
||||
|
||||
Run it again and you should see one passing test::
|
||||
|
||||
$ python flaskr_tests.py
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.034s
|
||||
|
||||
OK
|
||||
|
||||
Of course you can submit forms with the test client as well, which we will
|
||||
use now to log our user in.
|
||||
|
||||
Logging In and Out
|
||||
------------------
|
||||
|
||||
The majority of the functionality of our application is only available for
|
||||
the administration user. So we need a way to log our test client in to the
|
||||
application and out of it again. For that we fire some requests to the
|
||||
login and logout pages with the required form data (username and
|
||||
password). Because the login and logout pages redirect, we tell the
|
||||
client to `follow_redirects`.
|
||||
|
||||
Add the following two methods to your `FlaskrTestCase` class::
|
||||
|
||||
def login(self, username, password):
|
||||
return self.app.post('/login', data=dict(
|
||||
username=username,
|
||||
password=password
|
||||
), follow_redirects=True)
|
||||
|
||||
def logout(self):
|
||||
return self.app.get('/logout', follow_redirects=True)
|
||||
|
||||
Now we can easily test if logging in and out works and that it fails with
|
||||
invalid credentials. Add this new test to the class::
|
||||
|
||||
def test_login_logout(self):
|
||||
rv = self.login(flaskr.USERNAME, flaskr.PASSWORD)
|
||||
assert 'You were logged in' in rv.data
|
||||
rv = self.logout()
|
||||
assert 'You were logged out' in rv.data
|
||||
rv = self.login(flaskr.USERNAME + 'x', flaskr.PASSWORD)
|
||||
assert 'Invalid username' in rv.data
|
||||
rv = self.login(flaskr.USERNAME, flaskr.PASSWORD + 'x')
|
||||
assert 'Invalid password' in rv.data
|
||||
|
||||
Test Adding Messages
|
||||
--------------------
|
||||
|
||||
Now we can also test that adding messages works. Add a new test method
|
||||
like this::
|
||||
|
||||
def test_messages(self):
|
||||
self.login(flaskr.USERNAME, flaskr.PASSWORD)
|
||||
rv = self.app.post('/add', data=dict(
|
||||
title='<Hello>',
|
||||
text='<strong>HTML</strong> allowed here'
|
||||
), follow_redirects=True)
|
||||
assert 'No entries here so far' not in rv.data
|
||||
assert '<Hello>' in rv.data
|
||||
assert '<strong>HTML</strong> allowed here' in rv.data
|
||||
|
||||
Here we check that HTML is allowed in the text but not in the title,
|
||||
which is the intended behavior.
|
||||
|
||||
Running that should now give us three passing tests::
|
||||
|
||||
$ python flaskr_tests.py
|
||||
...
|
||||
----------------------------------------------------------------------
|
||||
Ran 3 tests in 0.332s
|
||||
|
||||
OK
|
||||
|
||||
For more complex tests with headers and status codes, check out the
|
||||
`MiniTwit Example`_ from the sources. That one contains a larger test
|
||||
suite.
|
||||
|
||||
|
||||
.. _MiniTwit Example:
|
||||
http://github.com/mitsuhiko/flask/tree/master/examples/minitwit/
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
Step 7: Adding Style
|
||||
====================
|
||||
|
||||
Now that everything else works, it's time to add some style to the
|
||||
application. Just create a stylesheet called `style.css` in the `static`
|
||||
folder we created before:
|
||||
|
||||
.. sourcecode:: css
|
||||
|
||||
body { font-family: sans-serif; background: #eee; }
|
||||
a, h1, h2 { color: #377BA8; }
|
||||
h1, h2 { font-family: 'Georgia', serif; margin: 0; }
|
||||
h1 { border-bottom: 2px solid #eee; }
|
||||
h2 { font-size: 1.2em; }
|
||||
|
||||
.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
|
||||
padding: 0.8em; background: white; }
|
||||
.entries { list-style: none; margin: 0; padding: 0; }
|
||||
.entries li { margin: 0.8em 1.2em; }
|
||||
.entries li h2 { margin-left: -1em; }
|
||||
.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
|
||||
.add-entry dl { font-weight: bold; }
|
||||
.metanav { text-align: right; font-size: 0.8em; padding: 0.3em;
|
||||
margin-bottom: 1em; background: #fafafa; }
|
||||
.flash { background: #CEE5F5; padding: 0.5em;
|
||||
border: 1px solid #AACBE2; }
|
||||
.error { background: #F0D6D6; padding: 0.5em; }
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
Step 4: Request Database Connections
|
||||
------------------------------------
|
||||
|
||||
Now we know how we can open database connections and use them for scripts,
|
||||
but how can we elegantly do that for requests? We will need the database
|
||||
connection in all our functions so it makes sense to initialize them
|
||||
before each request and shut them down afterwards.
|
||||
|
||||
Flask allows us to do that with the :meth:`~flask.Flask.before_request` and
|
||||
:meth:`~flask.Flask.after_request` decorators::
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
g.db = connect_db()
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
g.db.close()
|
||||
return response
|
||||
|
||||
Functions marked with :meth:`~flask.Flask.before_request` are called before
|
||||
a request and passed no arguments, functions marked with
|
||||
:meth:`~flask.Flask.after_request` are called after a request and
|
||||
passed the response that will be sent to the client. They have to return
|
||||
that response object or a different one. In this case we just return it
|
||||
unchanged.
|
||||
|
||||
We store our current database connection on the special :data:`~flask.g`
|
||||
object that flask provides for us. This object stores information for one
|
||||
request only and is available from within each function. Never store such
|
||||
things on other objects because this would not work with threaded
|
||||
environments. That special :data:`~flask.g` object does some magic behind
|
||||
the scenes to ensure it does the right thing.
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
Step 3: Creating The Database
|
||||
=============================
|
||||
|
||||
Flaskr is a database powered application as outlined earlier, and more
|
||||
precisely, an application powered by a relational database system. Such
|
||||
systems need a schema that tells them how to store that information. So
|
||||
before starting the server for the first time it's important to create
|
||||
that schema.
|
||||
|
||||
Such a schema can be created by piping the `schema.sql` file into the
|
||||
`sqlite3` command as follows::
|
||||
|
||||
sqlite3 /tmp/flaskr.db < schema.sql
|
||||
|
||||
The downside of this is that it requires the sqlite3 command to be
|
||||
installed which is not necessarily the case on every system. Also one has
|
||||
to provide the path to the database there which leaves some place for
|
||||
errors. It's a good idea to add a function that initializes the database
|
||||
for you to the application.
|
||||
|
||||
If you want to do that, you first have to import the
|
||||
:func:`contextlib.closing` function from the contextlib package. If you
|
||||
want to use Python 2.5 it's also necessary to enable the `with` statement
|
||||
first (`__future__` imports must be the very first import)::
|
||||
|
||||
from __future__ import with_statement
|
||||
from contextlib import closing
|
||||
|
||||
Next we can create a function called `init_db` that initializes the
|
||||
database. For this we can use the `connect_db` function we defined
|
||||
earlier. Just add that function below the `connect_db` function::
|
||||
|
||||
def init_db():
|
||||
with closing(connect_db()) as db:
|
||||
with app.open_resource('schema.sql') as f:
|
||||
db.cursor().executescript(f.read())
|
||||
db.commit()
|
||||
|
||||
The :func:`~contextlib.closing` helper function allows us to keep a
|
||||
connection open for the duration of the `with` block. The
|
||||
:func:`~flask.Flask.open_resource` method of the application object
|
||||
supports that functionality out of the box, so it can be used in the
|
||||
`with` block directly. This function opens a file from the resource
|
||||
location (your `flaskr` folder) and allows you to read from it. We are
|
||||
using this here to execute a script on the database connection.
|
||||
|
||||
When we connect to a database we get a connection object (here called
|
||||
`db`) that can give us a cursor. On that cursor there is a method to
|
||||
execute a complete script. Finally we only have to commit the changes.
|
||||
SQLite 3 and other transactional databases will not commit unless you
|
||||
explicitly tell it to.
|
||||
|
||||
Now it is possible to create a database by starting up a Python shell and
|
||||
importing and calling that function::
|
||||
|
||||
>>> from flaskr import init_db
|
||||
>>> init_db()
|
||||
|
||||
.. admonition:: Troubleshooting
|
||||
|
||||
If you get an exception later that a table cannot be found check that
|
||||
you did call the `init_db` function and that your table names are
|
||||
correct (singular vs. plural for example).
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
Step 0: Creating The Folders
|
||||
============================
|
||||
|
||||
Before we get started, let's create the folders needed for this
|
||||
application::
|
||||
|
||||
/flaskr
|
||||
/static
|
||||
/templates
|
||||
|
||||
The `flaskr` folder is not a python package, but just something where we
|
||||
drop our files. Directly into this folder we will then put our database
|
||||
schema as well as main module in the following steps. The files inside
|
||||
the `static` folder are available to users of the application via `HTTP`.
|
||||
This is the place where css and javascript files go. Inside the
|
||||
`templates` folder Flask will look for `Jinja2`_ templates. Drop all the
|
||||
templates there.
|
||||
|
||||
.. _Jinja2: http://jinja.pocoo.org/2/
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
.. _tutorial:
|
||||
|
||||
Tutorial
|
||||
========
|
||||
|
||||
You want to develop an application with Python and Flask? Here you have
|
||||
the chance to learn that by example. In this tutorial we will create a
|
||||
simple microblog application. It only supports one user that can create
|
||||
text-only entries and there are no feeds or comments, but it still
|
||||
features everything you need to get started. We will use Flask and SQLite
|
||||
as database which comes out of the box with Python, so there is nothing
|
||||
else you need.
|
||||
|
||||
If you want the full sourcecode in advance or for comparison, check out
|
||||
the `example source`_.
|
||||
|
||||
.. _example source:
|
||||
http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
introduction
|
||||
folders
|
||||
schema
|
||||
setup
|
||||
dbinit
|
||||
dbcon
|
||||
views
|
||||
templates
|
||||
css
|
||||
testing
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
Introducing Flaskr
|
||||
==================
|
||||
|
||||
We will call our blogging application flaskr here, feel free to chose a
|
||||
less web-2.0-ish name ;) Basically we want it to do the following things:
|
||||
|
||||
1. let the user sign in and out with credentials specified in the
|
||||
configuration. Only one user is supported.
|
||||
2. when the user is logged in he or she can add new entries to the page
|
||||
consisting of a text-only title and some HTML for the text. This HTML
|
||||
is not sanitized because we trust the user here.
|
||||
3. the page shows all entries so far in reverse order (newest on top) and
|
||||
the user can add new ones from there if logged in.
|
||||
|
||||
We will be using SQlite3 directly for that application because it's good
|
||||
enough for an application of that size. For larger applications however
|
||||
it makes a lot of sense to use `SQLAlchemy`_ that handles database
|
||||
connections in a more intelligent way, allows you to target different
|
||||
relational databases at once and more. You might also want to consider
|
||||
one of the popular NoSQL databases if your data is more suited for those.
|
||||
|
||||
Here a screenshot from the final application:
|
||||
|
||||
.. image:: ../_static/flaskr.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: screenshot of the final application
|
||||
|
||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
Step 1: Database Schema
|
||||
=======================
|
||||
|
||||
First we want to create the database schema. For this application only a
|
||||
single table is needed and we only want to support SQLite so that is quite
|
||||
easy. Just put the following contents into a file named `schema.sql` in
|
||||
the just created `flaskr` folder:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
drop table if exists entries;
|
||||
create table entries (
|
||||
id integer primary key autoincrement,
|
||||
title string not null,
|
||||
text string not null
|
||||
);
|
||||
|
||||
This schema consists of a single table called `entries` and each row in
|
||||
this table has an `id`, a `title` and a `text`. The `id` is an
|
||||
automatically incrementing integer and a primary key, the other two are
|
||||
strings that must not be null.
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
Step 2: Application Setup Code
|
||||
==============================
|
||||
|
||||
Now that we have the schema in place we can create the application module.
|
||||
Let's call it `flaskr.py` inside the `flaskr` folder. For starters we
|
||||
will add the imports we will need as well as the config section. For
|
||||
small applications it's a possibility to drop the configuration directly
|
||||
into the module which we will be doing here. However a cleaner solution
|
||||
would be to create a separate `.ini` or `.py` file and load that or import
|
||||
the values from there.
|
||||
|
||||
::
|
||||
|
||||
# all the imports
|
||||
import sqlite3
|
||||
from flask import Flask, request, session, g, redirect, url_for, \
|
||||
abort, render_template, flash
|
||||
|
||||
# configuration
|
||||
DATABASE = '/tmp/flaskr.db'
|
||||
DEBUG = True
|
||||
SECRET_KEY = 'development key'
|
||||
USERNAME = 'admin'
|
||||
PASSWORD = 'default'
|
||||
|
||||
Next we can create our actual application and initialize it with the
|
||||
config::
|
||||
|
||||
# create our little application :)
|
||||
app = Flask(__name__)
|
||||
app.secret_key = SECRET_KEY
|
||||
app.debug = DEBUG
|
||||
|
||||
The `secret_key` is needed to keep the client-side sessions secure.
|
||||
Choose that key wisely and as hard to guess and complex as possible. The
|
||||
debug flag enables or disables the interactive debugger. Never leave
|
||||
debug mode activated in a production system because it will allow users to
|
||||
executed code on the server!
|
||||
|
||||
We also add a method to easily connect to the database specified. That
|
||||
can be used to open a connection on request and also from the interactive
|
||||
Python shell or a script. This will come in handy later
|
||||
|
||||
::
|
||||
|
||||
def connect_db():
|
||||
return sqlite3.connect(DATABASE)
|
||||
|
||||
Finally we just add a line to the bottom of the file that fires up the
|
||||
server if we run that file as standalone application::
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
|
||||
With that out of the way you should be able to start up the application
|
||||
without problems. When you head over to the server you will get an 404
|
||||
page not found error because we don't have any views yet. But we will
|
||||
focus on that a little later. First we should get the database working.
|
||||
|
||||
.. admonition:: Troubleshooting
|
||||
|
||||
If you notice later that the browser cannot connect to the server
|
||||
during development, you might want to try this line instead::
|
||||
|
||||
app.run(host='127.0.0.1')
|
||||
|
||||
In a nutshell: Werkzeug starts up as IPv6 on many operating systems by
|
||||
default and not every browser is happy with that. This forces IPv4
|
||||
usage.
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
Step 6: The Templates
|
||||
=====================
|
||||
|
||||
Now we should start working on the templates. If we request the URLs now
|
||||
we would only get an exception that Flask cannot find the templates. The
|
||||
templates are using `Jinja2`_ syntax and have autoescaping enabled by
|
||||
default. This means that unless you mark a value in the code with
|
||||
:class:`~flask.Markup` or with the ``|safe`` filter in the template,
|
||||
Jinja2 will ensure that special characters such as ``<`` or ``>`` are
|
||||
escaped with their XML equivalents.
|
||||
|
||||
We are also using template inheritance which makes it possible to reuse
|
||||
the layout of the website in all pages.
|
||||
|
||||
Put the following templates into the `templates` folder:
|
||||
|
||||
.. _Jinja2: http://jinja.pocoo.org/2/documentation/templates
|
||||
|
||||
layout.html
|
||||
-----------
|
||||
|
||||
This template contains the HTML skeleton, the header and a link to log in
|
||||
(or log out if the user was already logged in). It also displays the
|
||||
flashed messages if there are any. The ``{% block body %}`` block can be
|
||||
replaced by a block of the same name (``body``) in a child template.
|
||||
|
||||
The :class:`~flask.session` dict is available in the template as well and
|
||||
you can use that to check if the user is logged in or not. Note that in
|
||||
Jinja you can access missing attributes and items of objects / dicts which
|
||||
makes the following code work, even if there is no ``'logged_in'`` key in
|
||||
the session:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<!doctype html>
|
||||
<title>Flaskr</title>
|
||||
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
|
||||
<div class=page>
|
||||
<h1>Flaskr</h1>
|
||||
<div class=metanav>
|
||||
{% if not session.logged_in %}
|
||||
<a href="{{ url_for('login') }}">log in</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('logout') }}">log out</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class=flash>{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
|
||||
show_entries.html
|
||||
-----------------
|
||||
|
||||
This template extends the `layout.html` template from above to display the
|
||||
messages. Note that the `for` loop iterates over the messages we passed
|
||||
in with the :func:`~flask.render_template` function. We also tell the
|
||||
form to submit to your `add_entry` function and use `POST` as `HTTP`
|
||||
method:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
{% if session.logged_in %}
|
||||
<form action="{{ url_for('add_entry') }}" method=post class=add-entry>
|
||||
<dl>
|
||||
<dt>Title:
|
||||
<dd><input type=text size=30 name=title>
|
||||
<dt>Text:
|
||||
<dd><textarea name=text rows=5 cols=40></textarea>
|
||||
<dd><input type=submit value=Share>
|
||||
</dl>
|
||||
</form>
|
||||
{% endif %}
|
||||
<ul class=entries>
|
||||
{% for entry in entries %}
|
||||
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
|
||||
{% else %}
|
||||
<li><em>Unbelievable. No entries here so far</em>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
login.html
|
||||
----------
|
||||
|
||||
Finally the login template which basically just displays a form to allow
|
||||
the user to login:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<h2>Login</h2>
|
||||
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
|
||||
<form action="{{ url_for('login') }}" method=post>
|
||||
<dl>
|
||||
<dt>Username:
|
||||
<dd><input type=text name=username>
|
||||
<dt>Password:
|
||||
<dd><input type=password name=password>
|
||||
<dd><input type=submit value=Login>
|
||||
</dl>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
Bonus: Testing the Application
|
||||
===============================
|
||||
|
||||
Now that you have finished the application and everything works as
|
||||
expected, it's probably not the best idea to add automated tests to
|
||||
simplify modifications in the future. The application above is used as a
|
||||
basic example of how to perform unittesting in the :ref:`testing` section
|
||||
of the documentation. Go there to see how easy it is to test Flask
|
||||
applications.
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
Step 5: The View Functions
|
||||
==========================
|
||||
|
||||
Now that the database connections are working we can start writing the
|
||||
view functions. We will need four of them:
|
||||
|
||||
Show Entries
|
||||
------------
|
||||
|
||||
This view shows all the entries stored in the database. It listens on the
|
||||
root of the application and will select title and text from the database.
|
||||
The one with the highest id (the newest entry) on top. The rows returned
|
||||
from the cursor are tuples with the columns ordered like specified in the
|
||||
select statement. This is good enough for small applications like here,
|
||||
but you might want to convert them into a dict. If you are interested how
|
||||
to do that, check out the :ref:`easy-querying` example.
|
||||
|
||||
The view function will pass the entries as dicts to the
|
||||
`show_entries.html` template and return the rendered one::
|
||||
|
||||
@app.route('/')
|
||||
def show_entries():
|
||||
cur = g.db.execute('select title, text from entries order by id desc')
|
||||
entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()]
|
||||
return render_template('show_entries.html', entries=entries)
|
||||
|
||||
Add New Entry
|
||||
-------------
|
||||
|
||||
This view lets the user add new entries if he's logged in. This only
|
||||
responds to `POST` requests, the actual form is shown on the
|
||||
`show_entries` page. If everything worked out well we will
|
||||
:func:`~flask.flash` an information message to the next request and
|
||||
redirect back to the `show_entries` page::
|
||||
|
||||
@app.route('/add', methods=['POST'])
|
||||
def add_entry():
|
||||
if not session.get('logged_in'):
|
||||
abort(401)
|
||||
g.db.execute('insert into entries (title, text) values (?, ?)',
|
||||
[request.form['title'], request.form['text']])
|
||||
g.db.commit()
|
||||
flash('New entry was successfully posted')
|
||||
return redirect(url_for('show_entries'))
|
||||
|
||||
Note that we check that the user is logged in here (the `logged_in` key is
|
||||
present in the session and `True`).
|
||||
|
||||
Login and Logout
|
||||
----------------
|
||||
|
||||
These functions are used to sign the user in and out. Login checks the
|
||||
username and password against the ones from the configuration and sets the
|
||||
`logged_in` key in the session. If the user logged in successfully that
|
||||
key is set to `True` and the user is redirected back to the `show_entries`
|
||||
page. In that case also a message is flashed that informs the user he or
|
||||
she was logged in successfully. If an error occoured the template is
|
||||
notified about that and the user asked again::
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
if request.form['username'] != USERNAME:
|
||||
error = 'Invalid username'
|
||||
elif request.form['password'] != PASSWORD:
|
||||
error = 'Invalid password'
|
||||
else:
|
||||
session['logged_in'] = True
|
||||
flash('You were logged in')
|
||||
return redirect(url_for('show_entries'))
|
||||
return render_template('login.html', error=error)
|
||||
|
||||
The logout function on the other hand removes that key from the session
|
||||
again. We use a neat trick here: if you use the :meth:`~dict.pop` method
|
||||
of the dict and pass a second parameter to it (the default) the method
|
||||
will delete the key from the dictionary if present or do nothing when that
|
||||
key was not in there. This is helpful because we don't have to check in
|
||||
that case if the user was logged in.
|
||||
|
||||
::
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('logged_in', None)
|
||||
flash('You were logged out')
|
||||
return redirect(url_for('show_entries'))
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
|
||||
/ Flaskr /
|
||||
|
||||
a minimal blog application
|
||||
|
||||
|
||||
~ What is Flaskr?
|
||||
|
||||
A sqlite powered thumble blog application
|
||||
|
||||
~ How do I use it?
|
||||
|
||||
1. edit the configuration in the flaskr.py file
|
||||
|
||||
2. fire up a python shell and run this:
|
||||
|
||||
>>> from flaskr import init_db; init_db()
|
||||
|
||||
3. now you can run the flaskr.py file with your
|
||||
python interpreter and the application will
|
||||
greet you on http://localhost:5000/
|
||||
|
||||
~ Is it tested?
|
||||
|
||||
You betcha. Run the `flaskr_tests.py` file to see
|
||||
the tests pass.
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Flaskr
|
||||
~~~~~~
|
||||
|
||||
A microblog example application written as Flask tutorial with
|
||||
Flask and sqlite3.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
import sqlite3
|
||||
from contextlib import closing
|
||||
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
||||
render_template, flash
|
||||
|
||||
# configuration
|
||||
DATABASE = '/tmp/flaskr.db'
|
||||
DEBUG = True
|
||||
SECRET_KEY = 'development key'
|
||||
USERNAME = 'admin'
|
||||
PASSWORD = 'default'
|
||||
|
||||
# create our little application :)
|
||||
app = Flask(__name__)
|
||||
app.secret_key = SECRET_KEY
|
||||
app.debug = DEBUG
|
||||
|
||||
|
||||
def connect_db():
|
||||
"""Returns a new connection to the database."""
|
||||
return sqlite3.connect(DATABASE)
|
||||
|
||||
|
||||
def init_db():
|
||||
"""Creates the database tables."""
|
||||
with closing(connect_db()) as db:
|
||||
with app.open_resource('schema.sql') as f:
|
||||
db.cursor().executescript(f.read())
|
||||
db.commit()
|
||||
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
"""Make sure we are connected to the database each request."""
|
||||
g.db = connect_db()
|
||||
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
"""Closes the database again at the end of the request."""
|
||||
g.db.close()
|
||||
return response
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def show_entries():
|
||||
cur = g.db.execute('select title, text from entries order by id desc')
|
||||
entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()]
|
||||
return render_template('show_entries.html', entries=entries)
|
||||
|
||||
|
||||
@app.route('/add', methods=['POST'])
|
||||
def add_entry():
|
||||
if not session.get('logged_in'):
|
||||
abort(401)
|
||||
g.db.execute('insert into entries (title, text) values (?, ?)',
|
||||
[request.form['title'], request.form['text']])
|
||||
g.db.commit()
|
||||
flash('New entry was successfully posted')
|
||||
return redirect(url_for('show_entries'))
|
||||
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
if request.form['username'] != USERNAME:
|
||||
error = 'Invalid username'
|
||||
elif request.form['password'] != PASSWORD:
|
||||
error = 'Invalid password'
|
||||
else:
|
||||
session['logged_in'] = True
|
||||
flash('You were logged in')
|
||||
return redirect(url_for('show_entries'))
|
||||
return render_template('login.html', error=error)
|
||||
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('logged_in', None)
|
||||
flash('You were logged out')
|
||||
return redirect(url_for('show_entries'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Flaskr Tests
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Tests the Flaskr application.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import flaskr
|
||||
import unittest
|
||||
import tempfile
|
||||
|
||||
|
||||
class FlaskrTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Before each test, set up a blank database"""
|
||||
self.db_fd, flaskr.DATABASE = tempfile.mkstemp()
|
||||
self.app = flaskr.app.test_client()
|
||||
flaskr.init_db()
|
||||
|
||||
def tearDown(self):
|
||||
"""Get rid of the database again after each test."""
|
||||
os.close(self.db_fd)
|
||||
os.unlink(flaskr.DATABASE)
|
||||
|
||||
def login(self, username, password):
|
||||
return self.app.post('/login', data=dict(
|
||||
username=username,
|
||||
password=password
|
||||
), follow_redirects=True)
|
||||
|
||||
def logout(self):
|
||||
return self.app.get('/logout', follow_redirects=True)
|
||||
|
||||
# testing functions
|
||||
|
||||
def test_empty_db(self):
|
||||
"""Start with a blank database."""
|
||||
rv = self.app.get('/')
|
||||
assert 'No entries here so far' in rv.data
|
||||
|
||||
def test_login_logout(self):
|
||||
"""Make sure login and logout works"""
|
||||
rv = self.login(flaskr.USERNAME, flaskr.PASSWORD)
|
||||
assert 'You were logged in' in rv.data
|
||||
rv = self.logout()
|
||||
assert 'You were logged out' in rv.data
|
||||
rv = self.login(flaskr.USERNAME + 'x', flaskr.PASSWORD)
|
||||
assert 'Invalid username' in rv.data
|
||||
rv = self.login(flaskr.USERNAME, flaskr.PASSWORD + 'x')
|
||||
assert 'Invalid password' in rv.data
|
||||
|
||||
def test_messages(self):
|
||||
"""Test that messages work"""
|
||||
self.login(flaskr.USERNAME, flaskr.PASSWORD)
|
||||
rv = self.app.post('/add', data=dict(
|
||||
title='<Hello>',
|
||||
text='<strong>HTML</strong> allowed here'
|
||||
), follow_redirects=True)
|
||||
assert 'No entries here so far' not in rv.data
|
||||
assert '<Hello>' in rv.data
|
||||
assert '<strong>HTML</strong> allowed here' in rv.data
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
drop table if exists entries;
|
||||
create table entries (
|
||||
id integer primary key autoincrement,
|
||||
title string not null,
|
||||
text string not null
|
||||
);
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
body { font-family: sans-serif; background: #eee; }
|
||||
a, h1, h2 { color: #377BA8; }
|
||||
h1, h2 { font-family: 'Georgia', serif; margin: 0; }
|
||||
h1 { border-bottom: 2px solid #eee; }
|
||||
h2 { font-size: 1.2em; }
|
||||
|
||||
.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;
|
||||
padding: 0.8em; background: white; }
|
||||
.entries { list-style: none; margin: 0; padding: 0; }
|
||||
.entries li { margin: 0.8em 1.2em; }
|
||||
.entries li h2 { margin-left: -1em; }
|
||||
.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
|
||||
.add-entry dl { font-weight: bold; }
|
||||
.metanav { text-align: right; font-size: 0.8em; padding: 0.3em;
|
||||
margin-bottom: 1em; background: #fafafa; }
|
||||
.flash { background: #CEE5F5; padding: 0.5em;
|
||||
border: 1px solid #AACBE2; }
|
||||
.error { background: #F0D6D6; padding: 0.5em; }
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>Flaskr</title>
|
||||
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
|
||||
<div class=page>
|
||||
<h1>Flaskr</h1>
|
||||
<div class=metanav>
|
||||
{% if not session.logged_in %}
|
||||
<a href="{{ url_for('login') }}">log in</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('logout') }}">log out</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class=flash>{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<h2>Login</h2>
|
||||
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
|
||||
<form action="{{ url_for('login') }}" method=post>
|
||||
<dl>
|
||||
<dt>Username:
|
||||
<dd><input type=text name=username>
|
||||
<dt>Password:
|
||||
<dd><input type=password name=password>
|
||||
<dd><input type=submit value=Login>
|
||||
</dl>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
{% if session.logged_in %}
|
||||
<form action="{{ url_for('add_entry') }}" method=post class=add-entry>
|
||||
<dl>
|
||||
<dt>Title:
|
||||
<dd><input type=text size=30 name=title>
|
||||
<dt>Text:
|
||||
<dd><textarea name=text rows=5 cols=40></textarea>
|
||||
<dd><input type=submit value=Share>
|
||||
</dl>
|
||||
</form>
|
||||
{% endif %}
|
||||
<ul class=entries>
|
||||
{% for entry in entries %}
|
||||
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
|
||||
{% else %}
|
||||
<li><em>Unbelievable. No entries here so far</em>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jQuery Example
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
A simple application that shows how Flask and jQuery get along.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from flask import Flask, jsonify, render_template, request
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route('/_add_numbers')
|
||||
def add_numbers():
|
||||
"""Add two numbers server side, ridiculous but well..."""
|
||||
a = request.args.get('a', 0, type=int)
|
||||
b = request.args.get('b', 0, type=int)
|
||||
return jsonify(result=a + b)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$('a#calculate').bind('click', function() {
|
||||
$.getJSON($SCRIPT_ROOT + '/_add_numbers', {
|
||||
a: $('input[name="a"]').val(),
|
||||
b: $('input[name="b"]').val()
|
||||
}, function(data) {
|
||||
$("#result").text(data.result);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<h1>jQuery Example</h1>
|
||||
<p><input type=text size=5 name=a> +
|
||||
<input type=text size=5 name=b> =
|
||||
<span id=result>?</span>
|
||||
<p><a href=# id=calculate>calculate server side</a>
|
||||
{% endblock %}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>jQuery Example</title>
|
||||
<script type=text/javascript
|
||||
src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||
<script type=text/javascript src="{{ url_for('static', filename='app.js')
|
||||
}}"></script>
|
||||
<script type=text/javascript>
|
||||
var $SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
|
||||
</script>
|
||||
{% block body %}{% endblock %}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
|
||||
/ MiniTwit /
|
||||
|
||||
because writing todo lists is not fun
|
||||
|
||||
|
||||
~ What is MiniTwit?
|
||||
|
||||
A SQLite and Flask powered twitter clone
|
||||
|
||||
~ How do I use it?
|
||||
|
||||
1. edit the configuration in the minitwit.py file
|
||||
|
||||
2. fire up a python shell and run this:
|
||||
|
||||
>>> from minitwit import init_db; init_db()
|
||||
|
||||
3. now you can run the minitwit.py file with your
|
||||
python interpreter and the application will
|
||||
greet you on http://localhost:5000/
|
||||
|
||||
~ Is it tested?
|
||||
|
||||
You betcha. Run the `minitwit_tests.py` file to
|
||||
see the tests pass.
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
MiniTwit
|
||||
~~~~~~~~
|
||||
|
||||
A microblogging application written with Flask and sqlite3.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
import time
|
||||
import sqlite3
|
||||
from hashlib import md5
|
||||
from datetime import datetime
|
||||
from contextlib import closing
|
||||
from flask import Flask, request, session, url_for, redirect, \
|
||||
render_template, abort, g, flash
|
||||
from werkzeug import check_password_hash, generate_password_hash
|
||||
|
||||
|
||||
# configuration
|
||||
DATABASE = '/tmp/minitwit.db'
|
||||
PER_PAGE = 30
|
||||
DEBUG = True
|
||||
SECRET_KEY = 'development key'
|
||||
|
||||
# create our little application :)
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def connect_db():
|
||||
"""Returns a new connection to the database."""
|
||||
return sqlite3.connect(DATABASE)
|
||||
|
||||
|
||||
def init_db():
|
||||
"""Creates the database tables."""
|
||||
with closing(connect_db()) as db:
|
||||
with app.open_resource('schema.sql') as f:
|
||||
db.cursor().executescript(f.read())
|
||||
db.commit()
|
||||
|
||||
|
||||
def query_db(query, args=(), one=False):
|
||||
"""Queries the database and returns a list of dictionaries."""
|
||||
cur = g.db.execute(query, args)
|
||||
rv = [dict((cur.description[idx][0], value)
|
||||
for idx, value in enumerate(row)) for row in cur.fetchall()]
|
||||
return (rv[0] if rv else None) if one else rv
|
||||
|
||||
|
||||
def get_user_id(username):
|
||||
"""Convenience method to look up the id for a username."""
|
||||
rv = g.db.execute('select user_id from user where username = ?',
|
||||
[username]).fetchone()
|
||||
return rv[0] if rv else None
|
||||
|
||||
|
||||
def format_datetime(timestamp):
|
||||
"""Format a timestamp for display."""
|
||||
return datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d @ %H:%M')
|
||||
|
||||
|
||||
def gravatar_url(email, size=80):
|
||||
"""Return the gravatar image for the given email address."""
|
||||
return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \
|
||||
(md5(email.strip().lower().encode('utf-8')).hexdigest(), size)
|
||||
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
"""Make sure we are connected to the database each request and look
|
||||
up the current user so that we know he's there.
|
||||
"""
|
||||
g.db = connect_db()
|
||||
g.user = None
|
||||
if 'user_id' in session:
|
||||
g.user = query_db('select * from user where user_id = ?',
|
||||
[session['user_id']], one=True)
|
||||
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
"""Closes the database again at the end of the request."""
|
||||
g.db.close()
|
||||
return response
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def timeline():
|
||||
"""Shows a users timeline or if no user is logged in it will
|
||||
redirect to the public timeline. This timeline shows the user's
|
||||
messages as well as all the messages of followed users.
|
||||
"""
|
||||
if not g.user:
|
||||
return redirect(url_for('public_timeline'))
|
||||
return render_template('timeline.html', messages=query_db('''
|
||||
select message.*, user.* from message, user
|
||||
where message.author_id = user.user_id and (
|
||||
user.user_id = ? or
|
||||
user.user_id in (select whom_id from follower
|
||||
where who_id = ?))
|
||||
order by message.pub_date desc limit ?''',
|
||||
[session['user_id'], session['user_id'], PER_PAGE]))
|
||||
|
||||
|
||||
@app.route('/public')
|
||||
def public_timeline():
|
||||
"""Displays the latest messages of all users."""
|
||||
return render_template('timeline.html', messages=query_db('''
|
||||
select message.*, user.* from message, user
|
||||
where message.author_id = user.user_id
|
||||
order by message.pub_date desc limit ?''', [PER_PAGE]))
|
||||
|
||||
|
||||
@app.route('/<username>')
|
||||
def user_timeline(username):
|
||||
"""Display's a users tweets."""
|
||||
profile_user = query_db('select * from user where username = ?',
|
||||
[username], one=True)
|
||||
if profile_user is None:
|
||||
abort(404)
|
||||
followed = False
|
||||
if g.user:
|
||||
followed = query_db('''select 1 from follower where
|
||||
follower.who_id = ? and follower.whom_id = ?''',
|
||||
[session['user_id'], profile_user['user_id']],
|
||||
one=True) is not None
|
||||
return render_template('timeline.html', messages=query_db('''
|
||||
select message.*, user.* from message, user where
|
||||
user.user_id = message.author_id and user.user_id = ?
|
||||
order by message.pub_date desc limit ?''',
|
||||
[profile_user['user_id'], PER_PAGE]), followed=followed,
|
||||
profile_user=profile_user)
|
||||
|
||||
|
||||
@app.route('/<username>/follow')
|
||||
def follow_user(username):
|
||||
"""Adds the current user as follower of the given user."""
|
||||
if not g.user:
|
||||
abort(401)
|
||||
whom_id = get_user_id(username)
|
||||
if whom_id is None:
|
||||
abort(404)
|
||||
g.db.execute('insert into follower (who_id, whom_id) values (?, ?)',
|
||||
[session['user_id'], whom_id])
|
||||
g.db.commit()
|
||||
flash('You are now following "%s"' % username)
|
||||
return redirect(url_for('user_timeline', username=username))
|
||||
|
||||
|
||||
@app.route('/<username>/unfollow')
|
||||
def unfollow_user(username):
|
||||
"""Removes the current user as follower of the given user."""
|
||||
if not g.user:
|
||||
abort(401)
|
||||
whom_id = get_user_id(username)
|
||||
if whom_id is None:
|
||||
abort(404)
|
||||
g.db.execute('delete from follower where who_id=? and whom_id=?',
|
||||
[session['user_id'], whom_id])
|
||||
g.db.commit()
|
||||
flash('You are no longer following "%s"' % username)
|
||||
return redirect(url_for('user_timeline', username=username))
|
||||
|
||||
|
||||
@app.route('/add_message', methods=['POST'])
|
||||
def add_message():
|
||||
"""Registers a new message for the user."""
|
||||
if 'user_id' not in session:
|
||||
abort(401)
|
||||
if request.form['text']:
|
||||
g.db.execute('''insert into message (author_id, text, pub_date)
|
||||
values (?, ?, ?)''', (session['user_id'], request.form['text'],
|
||||
int(time.time())))
|
||||
g.db.commit()
|
||||
flash('Your message was recorded')
|
||||
return redirect(url_for('timeline'))
|
||||
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
"""Logs the user in."""
|
||||
if g.user:
|
||||
return redirect(url_for('timeline'))
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
user = query_db('''select * from user where
|
||||
username = ?''', [request.form['username']], one=True)
|
||||
if user is None:
|
||||
error = 'Invalid username'
|
||||
elif not check_password_hash(user['pw_hash'],
|
||||
request.form['password']):
|
||||
error = 'Invalid password'
|
||||
else:
|
||||
flash('You were logged in')
|
||||
session['user_id'] = user['user_id']
|
||||
return redirect(url_for('timeline'))
|
||||
return render_template('login.html', error=error)
|
||||
|
||||
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
"""Registers the user."""
|
||||
if g.user:
|
||||
return redirect(url_for('timeline'))
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
if not request.form['username']:
|
||||
error = 'You have to enter a username'
|
||||
elif not request.form['email'] or \
|
||||
'@' not in request.form['email']:
|
||||
error = 'You have to enter a valid email address'
|
||||
elif not request.form['password']:
|
||||
error = 'You have to enter a password'
|
||||
elif request.form['password'] != request.form['password2']:
|
||||
error = 'The two passwords do not match'
|
||||
elif get_user_id(request.form['username']) is not None:
|
||||
error = 'The username is already taken'
|
||||
else:
|
||||
g.db.execute('''insert into user (
|
||||
username, email, pw_hash) values (?, ?, ?)''',
|
||||
[request.form['username'], request.form['email'],
|
||||
generate_password_hash(request.form['password'])])
|
||||
g.db.commit()
|
||||
flash('You were successfully registered and can login now')
|
||||
return redirect(url_for('login'))
|
||||
return render_template('register.html', error=error)
|
||||
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
"""Logs the user out."""
|
||||
flash('You were logged out')
|
||||
session.pop('user_id', None)
|
||||
return redirect(url_for('public_timeline'))
|
||||
|
||||
|
||||
# add some filters to jinja and set the secret key and debug mode
|
||||
# from the configuration.
|
||||
app.jinja_env.filters['datetimeformat'] = format_datetime
|
||||
app.jinja_env.filters['gravatar'] = gravatar_url
|
||||
app.secret_key = SECRET_KEY
|
||||
app.debug = DEBUG
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
MiniTwit Tests
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Tests the MiniTwit application.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import minitwit
|
||||
import unittest
|
||||
import tempfile
|
||||
|
||||
|
||||
class MiniTwitTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Before each test, set up a blank database"""
|
||||
self.db_fd, minitwit.DATABASE = tempfile.mkstemp()
|
||||
self.app = minitwit.app.test_client()
|
||||
minitwit.init_db()
|
||||
|
||||
def tearDown(self):
|
||||
"""Get rid of the database again after each test."""
|
||||
os.close(self.db_fd)
|
||||
os.unlink(minitwit.DATABASE)
|
||||
|
||||
# helper functions
|
||||
|
||||
def register(self, username, password, password2=None, email=None):
|
||||
"""Helper function to register a user"""
|
||||
if password2 is None:
|
||||
password2 = password
|
||||
if email is None:
|
||||
email = username + '@example.com'
|
||||
return self.app.post('/register', data={
|
||||
'username': username,
|
||||
'password': password,
|
||||
'password2': password2,
|
||||
'email': email,
|
||||
}, follow_redirects=True)
|
||||
|
||||
def login(self, username, password):
|
||||
"""Helper function to login"""
|
||||
return self.app.post('/login', data={
|
||||
'username': username,
|
||||
'password': password
|
||||
}, follow_redirects=True)
|
||||
|
||||
def register_and_login(self, username, password):
|
||||
"""Registers and logs in in one go"""
|
||||
self.register(username, password)
|
||||
return self.login(username, password)
|
||||
|
||||
def logout(self):
|
||||
"""Helper function to logout"""
|
||||
return self.app.get('/logout', follow_redirects=True)
|
||||
|
||||
def add_message(self, text):
|
||||
"""Records a message"""
|
||||
rv = self.app.post('/add_message', data={'text': text},
|
||||
follow_redirects=True)
|
||||
if text:
|
||||
assert 'Your message was recorded' in rv.data
|
||||
return rv
|
||||
|
||||
# testing functions
|
||||
|
||||
def test_register(self):
|
||||
"""Make sure registering works"""
|
||||
rv = self.register('user1', 'default')
|
||||
assert 'You were successfully registered ' \
|
||||
'and can login now' in rv.data
|
||||
rv = self.register('user1', 'default')
|
||||
assert 'The username is already taken' in rv.data
|
||||
rv = self.register('', 'default')
|
||||
assert 'You have to enter a username' in rv.data
|
||||
rv = self.register('meh', '')
|
||||
assert 'You have to enter a password' in rv.data
|
||||
rv = self.register('meh', 'x', 'y')
|
||||
assert 'The two passwords do not match' in rv.data
|
||||
rv = self.register('meh', 'foo', email='broken')
|
||||
assert 'You have to enter a valid email address' in rv.data
|
||||
|
||||
def test_login_logout(self):
|
||||
"""Make sure logging in and logging out works"""
|
||||
rv = self.register_and_login('user1', 'default')
|
||||
assert 'You were logged in' in rv.data
|
||||
rv = self.logout()
|
||||
assert 'You were logged out' in rv.data
|
||||
rv = self.login('user1', 'wrongpassword')
|
||||
assert 'Invalid password' in rv.data
|
||||
rv = self.login('user2', 'wrongpassword')
|
||||
assert 'Invalid username' in rv.data
|
||||
|
||||
def test_message_recording(self):
|
||||
"""Check if adding messages works"""
|
||||
self.register_and_login('foo', 'default')
|
||||
self.add_message('test message 1')
|
||||
self.add_message('<test message 2>')
|
||||
rv = self.app.get('/')
|
||||
assert 'test message 1' in rv.data
|
||||
assert '<test message 2>' in rv.data
|
||||
|
||||
def test_timelines(self):
|
||||
"""Make sure that timelines work"""
|
||||
self.register_and_login('foo', 'default')
|
||||
self.add_message('the message by foo')
|
||||
self.logout()
|
||||
self.register_and_login('bar', 'default')
|
||||
self.add_message('the message by bar')
|
||||
rv = self.app.get('/public')
|
||||
assert 'the message by foo' in rv.data
|
||||
assert 'the message by bar' in rv.data
|
||||
|
||||
# bar's timeline should just show bar's message
|
||||
rv = self.app.get('/')
|
||||
assert 'the message by foo' not in rv.data
|
||||
assert 'the message by bar' in rv.data
|
||||
|
||||
# now let's follow foo
|
||||
rv = self.app.get('/foo/follow', follow_redirects=True)
|
||||
assert 'You are now following "foo"' in rv.data
|
||||
|
||||
# we should now see foo's message
|
||||
rv = self.app.get('/')
|
||||
assert 'the message by foo' in rv.data
|
||||
assert 'the message by bar' in rv.data
|
||||
|
||||
# but on the user's page we only want the user's message
|
||||
rv = self.app.get('/bar')
|
||||
assert 'the message by foo' not in rv.data
|
||||
assert 'the message by bar' in rv.data
|
||||
rv = self.app.get('/foo')
|
||||
assert 'the message by foo' in rv.data
|
||||
assert 'the message by bar' not in rv.data
|
||||
|
||||
# now unfollow and check if that worked
|
||||
rv = self.app.get('/foo/unfollow', follow_redirects=True)
|
||||
assert 'You are no longer following "foo"' in rv.data
|
||||
rv = self.app.get('/')
|
||||
assert 'the message by foo' not in rv.data
|
||||
assert 'the message by bar' in rv.data
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
drop table if exists user;
|
||||
create table user (
|
||||
user_id integer primary key autoincrement,
|
||||
username string not null,
|
||||
email string not null,
|
||||
pw_hash string not null
|
||||
);
|
||||
|
||||
drop table if exists follower;
|
||||
create table follower (
|
||||
who_id integer,
|
||||
whom_id integer
|
||||
);
|
||||
|
||||
drop table if exists message;
|
||||
create table message (
|
||||
message_id integer primary key autoincrement,
|
||||
author_id integer not null,
|
||||
text string not null,
|
||||
pub_date integer
|
||||
);
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
body {
|
||||
background: #CAECE9;
|
||||
font-family: 'Trebuchet MS', sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #26776F;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"] {
|
||||
background: white;
|
||||
border: 1px solid #BFE6E2;
|
||||
padding: 2px;
|
||||
font-family: 'Trebuchet MS', sans-serif;
|
||||
font-size: 14px;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
color: #105751;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
background: #105751;
|
||||
border: 1px solid #073B36;
|
||||
padding: 1px 3px;
|
||||
font-family: 'Trebuchet MS', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.page {
|
||||
background: white;
|
||||
border: 1px solid #6ECCC4;
|
||||
width: 700px;
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
div.page h1 {
|
||||
background: #6ECCC4;
|
||||
margin: 0;
|
||||
padding: 10px 14px;
|
||||
color: white;
|
||||
letter-spacing: 1px;
|
||||
text-shadow: 0 0 3px #24776F;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
div.page div.navigation {
|
||||
background: #DEE9E8;
|
||||
padding: 4px 10px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #eee;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
div.page div.navigation a {
|
||||
color: #444;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.page h2 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #105751;
|
||||
text-shadow: 0 1px 2px #ccc;
|
||||
}
|
||||
|
||||
div.page div.body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
div.page div.footer {
|
||||
background: #eee;
|
||||
color: #888;
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
div.page div.followstatus {
|
||||
border: 1px solid #ccc;
|
||||
background: #E3EBEA;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
padding: 3px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
div.page ul.messages {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.page ul.messages li {
|
||||
margin: 10px 0;
|
||||
padding: 5px;
|
||||
background: #F0FAF9;
|
||||
border: 1px solid #DBF3F1;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
div.page ul.messages p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.page ul.messages li img {
|
||||
float: left;
|
||||
padding: 0 10px 0 0;
|
||||
}
|
||||
|
||||
div.page ul.messages li small {
|
||||
font-size: 0.9em;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.page div.twitbox {
|
||||
margin: 10px 0;
|
||||
padding: 5px;
|
||||
background: #F0FAF9;
|
||||
border: 1px solid #94E2DA;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
}
|
||||
|
||||
div.page div.twitbox h3 {
|
||||
margin: 0;
|
||||
font-size: 1em;
|
||||
color: #2C7E76;
|
||||
}
|
||||
|
||||
div.page div.twitbox p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.page div.twitbox input[type="text"] {
|
||||
width: 585px;
|
||||
}
|
||||
|
||||
div.page div.twitbox input[type="submit"] {
|
||||
width: 70px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
ul.flashes {
|
||||
list-style: none;
|
||||
margin: 10px 10px 0 10px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.flashes li {
|
||||
background: #B9F3ED;
|
||||
border: 1px solid #81CEC6;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
padding: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
div.error {
|
||||
margin: 10px 0;
|
||||
background: #FAE4E4;
|
||||
border: 1px solid #DD6F6F;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
padding: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<!doctype html>
|
||||
<title>{% block title %}Welcome{% endblock %} | MiniTwit</title>
|
||||
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
|
||||
<div class=page>
|
||||
<h1>MiniTwit</h1>
|
||||
<div class=navigation>
|
||||
{% if g.user %}
|
||||
<a href="{{ url_for('timeline') }}">my timeline</a> |
|
||||
<a href="{{ url_for('public_timeline') }}">public timeline</a> |
|
||||
<a href="{{ url_for('logout') }}">sign out [{{ g.user.username }}]</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('public_timeline') }}">public timeline</a> |
|
||||
<a href="{{ url_for('register') }}">sign up</a> |
|
||||
<a href="{{ url_for('login') }}">sign in</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% with flashes = get_flashed_messages() %}
|
||||
{% if flashes %}
|
||||
<ul class=flashes>
|
||||
{% for message in flashes %}
|
||||
<li>{{ message }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class=body>
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
<div class=footer>
|
||||
MiniTwit — A Flask Application
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}Sign In{% endblock %}
|
||||
{% block body %}
|
||||
<h2>Sign In</h2>
|
||||
{% if error %}<div class=error><strong>Error:</strong> {{ error }}</div>{% endif %}
|
||||
<form action="" method=post>
|
||||
<dl>
|
||||
<dt>Username:
|
||||
<dd><input type=text name=username size=30 value="{{ request.form.username }}">
|
||||
<dt>Password:
|
||||
<dd><input type=password name=password size=30>
|
||||
</dl>
|
||||
<div class=actions><input type=submit value="Sign In"></div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}Sign Up{% endblock %}
|
||||
{% block body %}
|
||||
<h2>Sign Up</h2>
|
||||
{% if error %}<div class=error><strong>Error:</strong> {{ error }}</div>{% endif %}
|
||||
<form action="" method=post>
|
||||
<dl>
|
||||
<dt>Username:
|
||||
<dd><input type=text name=username size=30 value="{{ request.form.username }}">
|
||||
<dt>E-Mail:
|
||||
<dd><input type=text name=email size=30 value="{{ request.form.email }}">
|
||||
<dt>Password:
|
||||
<dd><input type=password name=password size=30>
|
||||
<dt>Password <small>(repeat)</small>:
|
||||
<dd><input type=password name=password2 size=30>
|
||||
</dl>
|
||||
<div class=actions><input type=submit value="Sign Up"></div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}
|
||||
{% if request.endpoint == 'public_timeline' %}
|
||||
Public Timeline
|
||||
{% elif request.endpoint == 'user_timeline' %}
|
||||
{{ profile_user.username }}'s Timeline
|
||||
{% else %}
|
||||
My Timeline
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<h2>{{ self.title() }}</h2>
|
||||
{% if g.user %}
|
||||
{% if request.endpoint == 'user_timeline' %}
|
||||
<div class=followstatus>
|
||||
{% if g.user.user_id == profile_user.user_id %}
|
||||
This is you!
|
||||
{% elif followed %}
|
||||
You are currently following this user.
|
||||
<a class=unfollow href="{{ url_for('unfollow_user', username=profile_user.username)
|
||||
}}">Unfollow user</a>.
|
||||
{% else %}
|
||||
You are not yet following this user.
|
||||
<a class=follow href="{{ url_for('follow_user', username=profile_user.username)
|
||||
}}">Follow user</a>.
|
||||
{% endif %}
|
||||
</div>
|
||||
{% elif request.endpoint == 'timeline' %}
|
||||
<div class=twitbox>
|
||||
<h3>What's on your mind {{ g.user.username }}?</h3>
|
||||
<form action="{{ url_for('add_message') }}" method=post>
|
||||
<p><input type=text name=text size=60><!--
|
||||
--><input type=submit value="Share">
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<ul class=messages>
|
||||
{% for message in messages %}
|
||||
<li><img src="{{ message.email|gravatar(size=48) }}"><p>
|
||||
<strong><a href="{{ url_for('user_timeline', username=message.username)
|
||||
}}">{{ message.username }}</a></strong>
|
||||
{{ message.text }}
|
||||
<small>— {{ message.pub_date|datetimeformat }}</small>
|
||||
{% else %}
|
||||
<li><em>There's no message so far.</em>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
801
flask.py
|
|
@ -1,801 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask
|
||||
~~~~~
|
||||
|
||||
A microframework based on Werkzeug. It's extensively documented
|
||||
and follows best practice patterns.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
import os
|
||||
import sys
|
||||
|
||||
from jinja2 import Environment, PackageLoader, FileSystemLoader
|
||||
from werkzeug import Request as RequestBase, Response as ResponseBase, \
|
||||
LocalStack, LocalProxy, create_environ, SharedDataMiddleware, \
|
||||
ImmutableDict, cached_property
|
||||
from werkzeug.routing import Map, Rule
|
||||
from werkzeug.exceptions import HTTPException
|
||||
from werkzeug.contrib.securecookie import SecureCookie
|
||||
|
||||
# try to load the best simplejson implementation available. If JSON
|
||||
# is not installed, we add a failing class.
|
||||
json_available = True
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
json_available = False
|
||||
|
||||
# utilities we import from Werkzeug and Jinja2 that are unused
|
||||
# in the module but are exported as public interface.
|
||||
from werkzeug import abort, redirect
|
||||
from jinja2 import Markup, escape
|
||||
|
||||
# use pkg_resource if that works, otherwise fall back to cwd. The
|
||||
# current working directory is generally not reliable with the notable
|
||||
# exception of google appengine.
|
||||
try:
|
||||
import pkg_resources
|
||||
pkg_resources.resource_stream
|
||||
except (ImportError, AttributeError):
|
||||
pkg_resources = None
|
||||
|
||||
|
||||
class Request(RequestBase):
|
||||
"""The request object used by default in flask. Remembers the
|
||||
matched endpoint and view arguments.
|
||||
|
||||
It is what ends up as :class:`~flask.request`. If you want to replace
|
||||
the request object used you can subclass this and set
|
||||
:attr:`~flask.Flask.request_class` to your subclass.
|
||||
"""
|
||||
|
||||
endpoint = view_args = None
|
||||
|
||||
@cached_property
|
||||
def json(self):
|
||||
"""If the mimetype is `application/json` this will contain the
|
||||
parsed JSON data.
|
||||
"""
|
||||
if __debug__:
|
||||
_assert_have_json()
|
||||
if self.mimetype == 'application/json':
|
||||
return json.loads(self.data)
|
||||
|
||||
|
||||
class Response(ResponseBase):
|
||||
"""The response object that is used by default in flask. Works like the
|
||||
response object from Werkzeug but is set to have a HTML mimetype by
|
||||
default. Quite often you don't have to create this object yourself because
|
||||
:meth:`~flask.Flask.make_response` will take care of that for you.
|
||||
|
||||
If you want to replace the response object used you can subclass this and
|
||||
set :attr:`~flask.Flask.request_class` to your subclass.
|
||||
"""
|
||||
default_mimetype = 'text/html'
|
||||
|
||||
|
||||
class _RequestGlobals(object):
|
||||
pass
|
||||
|
||||
|
||||
class _NullSession(SecureCookie):
|
||||
"""Class used to generate nicer error messages if sessions are not
|
||||
available. Will still allow read-only access to the empty session
|
||||
but fail on setting.
|
||||
"""
|
||||
|
||||
def _fail(self, *args, **kwargs):
|
||||
raise RuntimeError('the session is unavailable because no secret '
|
||||
'key was set. Set the secret_key on the '
|
||||
'application to something unique and secret')
|
||||
__setitem__ = __delitem__ = clear = pop = popitem = \
|
||||
update = setdefault = _fail
|
||||
del _fail
|
||||
|
||||
|
||||
class _RequestContext(object):
|
||||
"""The request context contains all request relevant information. It is
|
||||
created at the beginning of the request and pushed to the
|
||||
`_request_ctx_stack` and removed at the end of it. It will create the
|
||||
URL adapter and request object for the WSGI environment provided.
|
||||
"""
|
||||
|
||||
def __init__(self, app, environ):
|
||||
self.app = app
|
||||
self.url_adapter = app.url_map.bind_to_environ(environ)
|
||||
self.request = app.request_class(environ)
|
||||
self.session = app.open_session(self.request)
|
||||
if self.session is None:
|
||||
self.session = _NullSession()
|
||||
self.g = _RequestGlobals()
|
||||
self.flashes = None
|
||||
|
||||
def __enter__(self):
|
||||
_request_ctx_stack.push(self)
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
# do not pop the request stack if we are in debug mode and an
|
||||
# exception happened. This will allow the debugger to still
|
||||
# access the request object in the interactive shell.
|
||||
if tb is None or not self.app.debug:
|
||||
_request_ctx_stack.pop()
|
||||
|
||||
|
||||
def url_for(endpoint, **values):
|
||||
"""Generates a URL to the given endpoint with the method provided.
|
||||
|
||||
:param endpoint: the endpoint of the URL (name of the function)
|
||||
:param values: the variable arguments of the URL rule
|
||||
"""
|
||||
return _request_ctx_stack.top.url_adapter.build(endpoint, values)
|
||||
|
||||
|
||||
def get_template_attribute(template_name, attribute):
|
||||
"""Loads a macro (or variable) a template exports. This can be used to
|
||||
invoke a macro from within Python code. If you for example have a
|
||||
template named `_foo.html` with the following contents:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% macro hello(name) %}Hello {{ name }}!{% endmacro %}
|
||||
|
||||
You can access this from Python code like this::
|
||||
|
||||
hello = get_template_attribute('_foo.html', 'hello')
|
||||
return hello('World')
|
||||
|
||||
.. versionadded:: 0.2
|
||||
|
||||
:param template_name: the name of the template
|
||||
:param attribute: the name of the variable of macro to acccess
|
||||
"""
|
||||
return getattr(current_app.jinja_env.get_template(template_name).module,
|
||||
attribute)
|
||||
|
||||
|
||||
def flash(message):
|
||||
"""Flashes a message to the next request. In order to remove the
|
||||
flashed message from the session and to display it to the user,
|
||||
the template has to call :func:`get_flashed_messages`.
|
||||
|
||||
:param message: the message to be flashed.
|
||||
"""
|
||||
session.setdefault('_flashes', []).append(message)
|
||||
|
||||
|
||||
def get_flashed_messages():
|
||||
"""Pulls all flashed messages from the session and returns them.
|
||||
Further calls in the same request to the function will return
|
||||
the same messages.
|
||||
"""
|
||||
flashes = _request_ctx_stack.top.flashes
|
||||
if flashes is None:
|
||||
_request_ctx_stack.top.flashes = flashes = session.pop('_flashes', [])
|
||||
return flashes
|
||||
|
||||
|
||||
def jsonify(*args, **kwargs):
|
||||
"""Creates a :class:`~flask.Response` with the JSON representation of
|
||||
the given arguments with an `application/json` mimetype. The arguments
|
||||
to this function are the same as to the :class:`dict` constructor.
|
||||
|
||||
Example usage::
|
||||
|
||||
@app.route('/_get_current_user')
|
||||
def get_current_user():
|
||||
return jsonify(username=g.user.username,
|
||||
email=g.user.email,
|
||||
id=g.user.id)
|
||||
|
||||
This will send a JSON response like this to the browser::
|
||||
|
||||
{
|
||||
"username": "admin",
|
||||
"email": "admin@localhost",
|
||||
"id": 42
|
||||
}
|
||||
|
||||
This requires Python 2.6 or an installed version of simplejson.
|
||||
|
||||
.. versionadded:: 0.2
|
||||
"""
|
||||
if __debug__:
|
||||
_assert_have_json()
|
||||
return current_app.response_class(json.dumps(dict(*args, **kwargs),
|
||||
indent=None if request.is_xhr else 2), mimetype='application/json')
|
||||
|
||||
|
||||
def render_template(template_name, **context):
|
||||
"""Renders a template from the template folder with the given
|
||||
context.
|
||||
|
||||
:param template_name: the name of the template to be rendered
|
||||
:param context: the variables that should be available in the
|
||||
context of the template.
|
||||
"""
|
||||
current_app.update_template_context(context)
|
||||
return current_app.jinja_env.get_template(template_name).render(context)
|
||||
|
||||
|
||||
def render_template_string(source, **context):
|
||||
"""Renders a template from the given template source string
|
||||
with the given context.
|
||||
|
||||
:param template_name: the sourcecode of the template to be
|
||||
rendered
|
||||
:param context: the variables that should be available in the
|
||||
context of the template.
|
||||
"""
|
||||
current_app.update_template_context(context)
|
||||
return current_app.jinja_env.from_string(source).render(context)
|
||||
|
||||
|
||||
def _default_template_ctx_processor():
|
||||
"""Default template context processor. Injects `request`,
|
||||
`session` and `g`.
|
||||
"""
|
||||
reqctx = _request_ctx_stack.top
|
||||
return dict(
|
||||
request=reqctx.request,
|
||||
session=reqctx.session,
|
||||
g=reqctx.g
|
||||
)
|
||||
|
||||
|
||||
def _assert_have_json():
|
||||
"""Helper function that fails if JSON is unavailable."""
|
||||
if not json_available:
|
||||
raise RuntimeError('simplejson not installed')
|
||||
|
||||
|
||||
def _get_package_path(name):
|
||||
"""Returns the path to a package or cwd if that cannot be found."""
|
||||
try:
|
||||
return os.path.abspath(os.path.dirname(sys.modules[name].__file__))
|
||||
except (KeyError, AttributeError):
|
||||
return os.getcwd()
|
||||
|
||||
|
||||
# figure out if simplejson escapes slashes. This behaviour was changed
|
||||
# from one version to another without reason.
|
||||
if not json_available or '\\/' not in json.dumps('/'):
|
||||
|
||||
def _tojson_filter(*args, **kwargs):
|
||||
if __debug__:
|
||||
_assert_have_json()
|
||||
return json.dumps(*args, **kwargs).replace('/', '\\/')
|
||||
else:
|
||||
_tojson_filter = json.dumps
|
||||
|
||||
|
||||
class Flask(object):
|
||||
"""The flask object implements a WSGI application and acts as the central
|
||||
object. It is passed the name of the module or package of the
|
||||
application. Once it is created it will act as a central registry for
|
||||
the view functions, the URL rules, template configuration and much more.
|
||||
|
||||
The name of the package is used to resolve resources from inside the
|
||||
package or the folder the module is contained in depending on if the
|
||||
package parameter resolves to an actual python package (a folder with
|
||||
an `__init__.py` file inside) or a standard module (just a `.py` file).
|
||||
|
||||
For more information about resource loading, see :func:`open_resource`.
|
||||
|
||||
Usually you create a :class:`Flask` instance in your main module or
|
||||
in the `__init__.py` file of your package like this::
|
||||
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
"""
|
||||
|
||||
#: the class that is used for request objects. See :class:`~flask.request`
|
||||
#: for more information.
|
||||
request_class = Request
|
||||
|
||||
#: the class that is used for response objects. See
|
||||
#: :class:`~flask.Response` for more information.
|
||||
response_class = Response
|
||||
|
||||
#: path for the static files. If you don't want to use static files
|
||||
#: you can set this value to `None` in which case no URL rule is added
|
||||
#: and the development server will no longer serve any static files.
|
||||
static_path = '/static'
|
||||
|
||||
#: if a secret key is set, cryptographic components can use this to
|
||||
#: sign cookies and other things. Set this to a complex random value
|
||||
#: when you want to use the secure cookie for instance.
|
||||
secret_key = None
|
||||
|
||||
#: The secure cookie uses this for the name of the session cookie
|
||||
session_cookie_name = 'session'
|
||||
|
||||
#: options that are passed directly to the Jinja2 environment
|
||||
jinja_options = ImmutableDict(
|
||||
autoescape=True,
|
||||
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
|
||||
)
|
||||
|
||||
def __init__(self, package_name):
|
||||
#: the debug flag. Set this to `True` to enable debugging of
|
||||
#: the application. In debug mode the debugger will kick in
|
||||
#: when an unhandled exception ocurrs and the integrated server
|
||||
#: will automatically reload the application if changes in the
|
||||
#: code are detected.
|
||||
self.debug = False
|
||||
|
||||
#: the name of the package or module. Do not change this once
|
||||
#: it was set by the constructor.
|
||||
self.package_name = package_name
|
||||
|
||||
#: where is the app root located?
|
||||
self.root_path = _get_package_path(self.package_name)
|
||||
|
||||
#: a dictionary of all view functions registered. The keys will
|
||||
#: be function names which are also used to generate URLs and
|
||||
#: the values are the function objects themselves.
|
||||
#: to register a view function, use the :meth:`route` decorator.
|
||||
self.view_functions = {}
|
||||
|
||||
#: a dictionary of all registered error handlers. The key is
|
||||
#: be the error code as integer, the value the function that
|
||||
#: should handle that error.
|
||||
#: To register a error handler, use the :meth:`errorhandler`
|
||||
#: decorator.
|
||||
self.error_handlers = {}
|
||||
|
||||
#: a list of functions that should be called at the beginning
|
||||
#: of the request before request dispatching kicks in. This
|
||||
#: can for example be used to open database connections or
|
||||
#: getting hold of the currently logged in user.
|
||||
#: To register a function here, use the :meth:`before_request`
|
||||
#: decorator.
|
||||
self.before_request_funcs = []
|
||||
|
||||
#: a list of functions that are called at the end of the
|
||||
#: request. The function is passed the current response
|
||||
#: object and modify it in place or replace it.
|
||||
#: To register a function here use the :meth:`after_request`
|
||||
#: decorator.
|
||||
self.after_request_funcs = []
|
||||
|
||||
#: a list of functions that are called without arguments
|
||||
#: to populate the template context. Each returns a dictionary
|
||||
#: that the template context is updated with.
|
||||
#: To register a function here, use the :meth:`context_processor`
|
||||
#: decorator.
|
||||
self.template_context_processors = [_default_template_ctx_processor]
|
||||
|
||||
#: the :class:`~werkzeug.routing.Map` for this instance. You can use
|
||||
#: this to change the routing converters after the class was created
|
||||
#: but before any routes are connected. Example::
|
||||
#:
|
||||
#: from werkzeug import BaseConverter
|
||||
#:
|
||||
#: class ListConverter(BaseConverter):
|
||||
#: def to_python(self, value):
|
||||
#: return value.split(',')
|
||||
#: def to_url(self, values):
|
||||
#: return ','.join(BaseConverter.to_url(value)
|
||||
#: for value in values)
|
||||
#:
|
||||
#: app = Flask(__name__)
|
||||
#: app.url_map.converters['list'] = ListConverter
|
||||
self.url_map = Map()
|
||||
|
||||
if self.static_path is not None:
|
||||
self.add_url_rule(self.static_path + '/<filename>',
|
||||
build_only=True, endpoint='static')
|
||||
if pkg_resources is not None:
|
||||
target = (self.package_name, 'static')
|
||||
else:
|
||||
target = os.path.join(self.root_path, 'static')
|
||||
self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
|
||||
self.static_path: target
|
||||
})
|
||||
|
||||
#: the Jinja2 environment. It is created from the
|
||||
#: :attr:`jinja_options` and the loader that is returned
|
||||
#: by the :meth:`create_jinja_loader` function.
|
||||
self.jinja_env = Environment(loader=self.create_jinja_loader(),
|
||||
**self.jinja_options)
|
||||
self.jinja_env.globals.update(
|
||||
url_for=url_for,
|
||||
get_flashed_messages=get_flashed_messages
|
||||
)
|
||||
self.jinja_env.filters['tojson'] = _tojson_filter
|
||||
|
||||
def create_jinja_loader(self):
|
||||
"""Creates the Jinja loader. By default just a package loader for
|
||||
the configured package is returned that looks up templates in the
|
||||
`templates` folder. To add other loaders it's possible to
|
||||
override this method.
|
||||
"""
|
||||
if pkg_resources is None:
|
||||
return FileSystemLoader(os.path.join(self.root_path, 'templates'))
|
||||
return PackageLoader(self.package_name)
|
||||
|
||||
def update_template_context(self, context):
|
||||
"""Update the template context with some commonly used variables.
|
||||
This injects request, session and g into the template context.
|
||||
|
||||
:param context: the context as a dictionary that is updated in place
|
||||
to add extra variables.
|
||||
"""
|
||||
for func in self.template_context_processors:
|
||||
context.update(func())
|
||||
|
||||
def run(self, host='127.0.0.1', port=5000, **options):
|
||||
"""Runs the application on a local development server. If the
|
||||
:attr:`debug` flag is set the server will automatically reload
|
||||
for code changes and show a debugger in case an exception happened.
|
||||
|
||||
:param host: the hostname to listen on. set this to ``'0.0.0.0'``
|
||||
to have the server available externally as well.
|
||||
:param port: the port of the webserver
|
||||
:param options: the options to be forwarded to the underlying
|
||||
Werkzeug server. See :func:`werkzeug.run_simple`
|
||||
for more information.
|
||||
"""
|
||||
from werkzeug import run_simple
|
||||
if 'debug' in options:
|
||||
self.debug = options.pop('debug')
|
||||
options.setdefault('use_reloader', self.debug)
|
||||
options.setdefault('use_debugger', self.debug)
|
||||
return run_simple(host, port, self, **options)
|
||||
|
||||
def test_client(self):
|
||||
"""Creates a test client for this application. For information
|
||||
about unit testing head over to :ref:`testing`.
|
||||
"""
|
||||
from werkzeug import Client
|
||||
return Client(self, self.response_class, use_cookies=True)
|
||||
|
||||
def open_resource(self, resource):
|
||||
"""Opens a resource from the application's resource folder. To see
|
||||
how this works, consider the following folder structure::
|
||||
|
||||
/myapplication.py
|
||||
/schemal.sql
|
||||
/static
|
||||
/style.css
|
||||
/template
|
||||
/layout.html
|
||||
/index.html
|
||||
|
||||
If you want to open the `schema.sql` file you would do the
|
||||
following::
|
||||
|
||||
with app.open_resource('schema.sql') as f:
|
||||
contents = f.read()
|
||||
do_something_with(contents)
|
||||
|
||||
:param resource: the name of the resource. To access resources within
|
||||
subfolders use forward slashes as separator.
|
||||
"""
|
||||
if pkg_resources is None:
|
||||
return open(os.path.join(self.root_path, resource), 'rb')
|
||||
return pkg_resources.resource_stream(self.package_name, resource)
|
||||
|
||||
def open_session(self, request):
|
||||
"""Creates or opens a new session. Default implementation stores all
|
||||
session data in a signed cookie. This requires that the
|
||||
:attr:`secret_key` is set.
|
||||
|
||||
:param request: an instance of :attr:`request_class`.
|
||||
"""
|
||||
key = self.secret_key
|
||||
if key is not None:
|
||||
return SecureCookie.load_cookie(request, self.session_cookie_name,
|
||||
secret_key=key)
|
||||
|
||||
def save_session(self, session, response):
|
||||
"""Saves the session if it needs updates. For the default
|
||||
implementation, check :meth:`open_session`.
|
||||
|
||||
:param session: the session to be saved (a
|
||||
:class:`~werkzeug.contrib.securecookie.SecureCookie`
|
||||
object)
|
||||
:param response: an instance of :attr:`response_class`
|
||||
"""
|
||||
session.save_cookie(response, self.session_cookie_name)
|
||||
|
||||
def add_url_rule(self, rule, endpoint, view_func=None, **options):
|
||||
"""Connects a URL rule. Works exactly like the :meth:`route`
|
||||
decorator. If a view_func is provided it will be registered with the
|
||||
endpoint.
|
||||
|
||||
Basically this example::
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
pass
|
||||
|
||||
Is equivalent to the following::
|
||||
|
||||
def index():
|
||||
pass
|
||||
app.add_url_rule('/', 'index', index)
|
||||
|
||||
If the view_func is not provided you will need to connect the endpoint
|
||||
to a view function like so::
|
||||
|
||||
app.view_functions['index'] = index
|
||||
|
||||
.. versionchanged:: 0.2
|
||||
`view_func` parameter added
|
||||
|
||||
:param rule: the URL rule as string
|
||||
:param endpoint: the endpoint for the registered URL rule. Flask
|
||||
itself assumes the name of the view function as
|
||||
endpoint
|
||||
:param view_func: the function to call when serving a request to the
|
||||
provided endpoint
|
||||
:param options: the options to be forwarded to the underlying
|
||||
:class:`~werkzeug.routing.Rule` object
|
||||
"""
|
||||
options['endpoint'] = endpoint
|
||||
options.setdefault('methods', ('GET',))
|
||||
self.url_map.add(Rule(rule, **options))
|
||||
if view_func is not None:
|
||||
self.view_functions[endpoint] = view_func
|
||||
|
||||
def route(self, rule, **options):
|
||||
"""A decorator that is used to register a view function for a
|
||||
given URL rule. Example::
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return 'Hello World'
|
||||
|
||||
Variables parts in the route can be specified with angular
|
||||
brackets (``/user/<username>``). By default a variable part
|
||||
in the URL accepts any string without a slash however a different
|
||||
converter can be specified as well by using ``<converter:name>``.
|
||||
|
||||
Variable parts are passed to the view function as keyword
|
||||
arguments.
|
||||
|
||||
The following converters are possible:
|
||||
|
||||
=========== ===========================================
|
||||
`int` accepts integers
|
||||
`float` like `int` but for floating point values
|
||||
`path` like the default but also accepts slashes
|
||||
=========== ===========================================
|
||||
|
||||
Here some examples::
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
pass
|
||||
|
||||
@app.route('/<username>')
|
||||
def show_user(username):
|
||||
pass
|
||||
|
||||
@app.route('/post/<int:post_id>')
|
||||
def show_post(post_id):
|
||||
pass
|
||||
|
||||
An important detail to keep in mind is how Flask deals with trailing
|
||||
slashes. The idea is to keep each URL unique so the following rules
|
||||
apply:
|
||||
|
||||
1. If a rule ends with a slash and is requested without a slash
|
||||
by the user, the user is automatically redirected to the same
|
||||
page with a trailing slash attached.
|
||||
2. If a rule does not end with a trailing slash and the user request
|
||||
the page with a trailing slash, a 404 not found is raised.
|
||||
|
||||
This is consistent with how web servers deal with static files. This
|
||||
also makes it possible to use relative link targets safely.
|
||||
|
||||
The :meth:`route` decorator accepts a couple of other arguments
|
||||
as well:
|
||||
|
||||
:param rule: the URL rule as string
|
||||
:param methods: a list of methods this rule should be limited
|
||||
to (``GET``, ``POST`` etc.). By default a rule
|
||||
just listens for ``GET`` (and implicitly ``HEAD``).
|
||||
:param subdomain: specifies the rule for the subdoain in case
|
||||
subdomain matching is in use.
|
||||
:param strict_slashes: can be used to disable the strict slashes
|
||||
setting for this rule. See above.
|
||||
:param options: other options to be forwarded to the underlying
|
||||
:class:`~werkzeug.routing.Rule` object.
|
||||
"""
|
||||
def decorator(f):
|
||||
self.add_url_rule(rule, f.__name__, f, **options)
|
||||
return f
|
||||
return decorator
|
||||
|
||||
def errorhandler(self, code):
|
||||
"""A decorator that is used to register a function give a given
|
||||
error code. Example::
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found():
|
||||
return 'This page does not exist', 404
|
||||
|
||||
You can also register a function as error handler without using
|
||||
the :meth:`errorhandler` decorator. The following example is
|
||||
equivalent to the one above::
|
||||
|
||||
def page_not_found():
|
||||
return 'This page does not exist', 404
|
||||
app.error_handlers[404] = page_not_found
|
||||
|
||||
:param code: the code as integer for the handler
|
||||
"""
|
||||
def decorator(f):
|
||||
self.error_handlers[code] = f
|
||||
return f
|
||||
return decorator
|
||||
|
||||
def before_request(self, f):
|
||||
"""Registers a function to run before each request."""
|
||||
self.before_request_funcs.append(f)
|
||||
return f
|
||||
|
||||
def after_request(self, f):
|
||||
"""Register a function to be run after each request."""
|
||||
self.after_request_funcs.append(f)
|
||||
return f
|
||||
|
||||
def context_processor(self, f):
|
||||
"""Registers a template context processor function."""
|
||||
self.template_context_processors.append(f)
|
||||
return f
|
||||
|
||||
def match_request(self):
|
||||
"""Matches the current request against the URL map and also
|
||||
stores the endpoint and view arguments on the request object
|
||||
is successful, otherwise the exception is stored.
|
||||
"""
|
||||
rv = _request_ctx_stack.top.url_adapter.match()
|
||||
request.endpoint, request.view_args = rv
|
||||
return rv
|
||||
|
||||
def dispatch_request(self):
|
||||
"""Does the request dispatching. Matches the URL and returns the
|
||||
return value of the view or error handler. This does not have to
|
||||
be a response object. In order to convert the return value to a
|
||||
proper response object, call :func:`make_response`.
|
||||
"""
|
||||
try:
|
||||
endpoint, values = self.match_request()
|
||||
return self.view_functions[endpoint](**values)
|
||||
except HTTPException, e:
|
||||
handler = self.error_handlers.get(e.code)
|
||||
if handler is None:
|
||||
return e
|
||||
return handler(e)
|
||||
except Exception, e:
|
||||
handler = self.error_handlers.get(500)
|
||||
if self.debug or handler is None:
|
||||
raise
|
||||
return handler(e)
|
||||
|
||||
def make_response(self, rv):
|
||||
"""Converts the return value from a view function to a real
|
||||
response object that is an instance of :attr:`response_class`.
|
||||
|
||||
The following types are allowd for `rv`:
|
||||
|
||||
======================= ===========================================
|
||||
:attr:`response_class` the object is returned unchanged
|
||||
:class:`str` a response object is created with the
|
||||
string as body
|
||||
:class:`unicode` a response object is created with the
|
||||
string encoded to utf-8 as body
|
||||
:class:`tuple` the response object is created with the
|
||||
contents of the tuple as arguments
|
||||
a WSGI function the function is called as WSGI application
|
||||
and buffered as response object
|
||||
======================= ===========================================
|
||||
|
||||
:param rv: the return value from the view function
|
||||
"""
|
||||
if isinstance(rv, self.response_class):
|
||||
return rv
|
||||
if isinstance(rv, basestring):
|
||||
return self.response_class(rv)
|
||||
if isinstance(rv, tuple):
|
||||
return self.response_class(*rv)
|
||||
return self.response_class.force_type(rv, request.environ)
|
||||
|
||||
def preprocess_request(self):
|
||||
"""Called before the actual request dispatching and will
|
||||
call every as :meth:`before_request` decorated function.
|
||||
If any of these function returns a value it's handled as
|
||||
if it was the return value from the view and further
|
||||
request handling is stopped.
|
||||
"""
|
||||
for func in self.before_request_funcs:
|
||||
rv = func()
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
def process_response(self, response):
|
||||
"""Can be overridden in order to modify the response object
|
||||
before it's sent to the WSGI server. By default this will
|
||||
call all the :meth:`after_request` decorated functions.
|
||||
|
||||
:param response: a :attr:`response_class` object.
|
||||
:return: a new response object or the same, has to be an
|
||||
instance of :attr:`response_class`.
|
||||
"""
|
||||
session = _request_ctx_stack.top.session
|
||||
if not isinstance(session, _NullSession):
|
||||
self.save_session(session, response)
|
||||
for handler in self.after_request_funcs:
|
||||
response = handler(response)
|
||||
return response
|
||||
|
||||
def wsgi_app(self, environ, start_response):
|
||||
"""The actual WSGI application. This is not implemented in
|
||||
`__call__` so that middlewares can be applied without losing a
|
||||
reference to the class. So instead of doing this::
|
||||
|
||||
app = MyMiddleware(app)
|
||||
|
||||
It's a better idea to do this instead::
|
||||
|
||||
app.wsgi_app = MyMiddleware(app.wsgi_app)
|
||||
|
||||
Then you still have the original application object around and
|
||||
can continue to call methods on it.
|
||||
|
||||
:param environ: a WSGI environment
|
||||
:param start_response: a callable accepting a status code,
|
||||
a list of headers and an optional
|
||||
exception context to start the response
|
||||
"""
|
||||
with self.request_context(environ):
|
||||
rv = self.preprocess_request()
|
||||
if rv is None:
|
||||
rv = self.dispatch_request()
|
||||
response = self.make_response(rv)
|
||||
response = self.process_response(response)
|
||||
return response(environ, start_response)
|
||||
|
||||
def request_context(self, environ):
|
||||
"""Creates a request context from the given environment and binds
|
||||
it to the current context. This must be used in combination with
|
||||
the `with` statement because the request is only bound to the
|
||||
current context for the duration of the `with` block.
|
||||
|
||||
Example usage::
|
||||
|
||||
with app.request_context(environ):
|
||||
do_something_with(request)
|
||||
|
||||
:params environ: a WSGI environment
|
||||
"""
|
||||
return _RequestContext(self, environ)
|
||||
|
||||
def test_request_context(self, *args, **kwargs):
|
||||
"""Creates a WSGI environment from the given values (see
|
||||
:func:`werkzeug.create_environ` for more information, this
|
||||
function accepts the same arguments).
|
||||
"""
|
||||
return self.request_context(create_environ(*args, **kwargs))
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""Shortcut for :attr:`wsgi_app`."""
|
||||
return self.wsgi_app(environ, start_response)
|
||||
|
||||
|
||||
# context locals
|
||||
_request_ctx_stack = LocalStack()
|
||||
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
|
||||
request = LocalProxy(lambda: _request_ctx_stack.top.request)
|
||||
session = LocalProxy(lambda: _request_ctx_stack.top.session)
|
||||
g = LocalProxy(lambda: _request_ctx_stack.top.g)
|
||||
71
setup.py
|
|
@ -1,71 +0,0 @@
|
|||
"""
|
||||
Flask
|
||||
-----
|
||||
|
||||
Flask is a microframework for Python based on Werkzeug, Jinja 2 and good
|
||||
intentions. And before you ask: It's BSD licensed!
|
||||
|
||||
Flask is Fun
|
||||
````````````
|
||||
|
||||
::
|
||||
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def hello():
|
||||
return "Hello World!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
|
||||
And Easy to Setup
|
||||
`````````````````
|
||||
|
||||
::
|
||||
|
||||
$ easy_install Flask
|
||||
$ python hello.py
|
||||
* Running on http://localhost:5000/
|
||||
|
||||
Links
|
||||
`````
|
||||
|
||||
* `website <http://flask.pocoo.org/>`_
|
||||
* `documentation <http://flask.pocoo.org/docs/>`_
|
||||
* `development version
|
||||
<http://github.com/mitsuhiko/flask/zipball/master#egg=Flask-dev>`_
|
||||
|
||||
"""
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
setup(
|
||||
name='Flask',
|
||||
version='0.2',
|
||||
url='http://github.com/mitsuhiko/flask/',
|
||||
license='BSD',
|
||||
author='Armin Ronacher',
|
||||
author_email='armin.ronacher@active-4.com',
|
||||
description='A microframework based on Werkzeug, Jinja2 '
|
||||
'and good intentions',
|
||||
long_description=__doc__,
|
||||
py_modules=['flask'],
|
||||
zip_safe=False,
|
||||
platforms='any',
|
||||
install_requires=[
|
||||
'Werkzeug>=0.6.1',
|
||||
'Jinja2>=2.4'
|
||||
],
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Environment :: Web Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
]
|
||||
)
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
|
@ -1,316 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Flask Tests
|
||||
~~~~~~~~~~~
|
||||
|
||||
Tests Flask itself. The majority of Flask is already tested
|
||||
as part of Werkzeug.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
import os
|
||||
import sys
|
||||
import flask
|
||||
import unittest
|
||||
import tempfile
|
||||
|
||||
|
||||
example_path = os.path.join(os.path.dirname(__file__), '..', 'examples')
|
||||
sys.path.append(os.path.join(example_path, 'flaskr'))
|
||||
sys.path.append(os.path.join(example_path, 'minitwit'))
|
||||
|
||||
|
||||
class ContextTestCase(unittest.TestCase):
|
||||
|
||||
def test_context_binding(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/')
|
||||
def index():
|
||||
return 'Hello %s!' % flask.request.args['name']
|
||||
@app.route('/meh')
|
||||
def meh():
|
||||
return flask.request.url
|
||||
|
||||
with app.test_request_context('/?name=World'):
|
||||
assert index() == 'Hello World!'
|
||||
with app.test_request_context('/meh'):
|
||||
assert meh() == 'http://localhost/meh'
|
||||
|
||||
|
||||
class BasicFunctionalityTestCase(unittest.TestCase):
|
||||
|
||||
def test_request_dispatching(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/')
|
||||
def index():
|
||||
return flask.request.method
|
||||
@app.route('/more', methods=['GET', 'POST'])
|
||||
def more():
|
||||
return flask.request.method
|
||||
|
||||
c = app.test_client()
|
||||
assert c.get('/').data == 'GET'
|
||||
rv = c.post('/')
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD']
|
||||
rv = c.head('/')
|
||||
assert rv.status_code == 200
|
||||
assert not rv.data # head truncates
|
||||
assert c.post('/more').data == 'POST'
|
||||
assert c.get('/more').data == 'GET'
|
||||
rv = c.delete('/more')
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'POST']
|
||||
|
||||
def test_url_mapping(self):
|
||||
app = flask.Flask(__name__)
|
||||
def index():
|
||||
return flask.request.method
|
||||
def more():
|
||||
return flask.request.method
|
||||
|
||||
app.add_url_rule('/', 'index', index)
|
||||
app.add_url_rule('/more', 'more', more, methods=['GET', 'POST'])
|
||||
|
||||
c = app.test_client()
|
||||
assert c.get('/').data == 'GET'
|
||||
rv = c.post('/')
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD']
|
||||
rv = c.head('/')
|
||||
assert rv.status_code == 200
|
||||
assert not rv.data # head truncates
|
||||
assert c.post('/more').data == 'POST'
|
||||
assert c.get('/more').data == 'GET'
|
||||
rv = c.delete('/more')
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'POST']
|
||||
|
||||
def test_session(self):
|
||||
app = flask.Flask(__name__)
|
||||
app.secret_key = 'testkey'
|
||||
@app.route('/set', methods=['POST'])
|
||||
def set():
|
||||
flask.session['value'] = flask.request.form['value']
|
||||
return 'value set'
|
||||
@app.route('/get')
|
||||
def get():
|
||||
return flask.session['value']
|
||||
|
||||
c = app.test_client()
|
||||
assert c.post('/set', data={'value': '42'}).data == 'value set'
|
||||
assert c.get('/get').data == '42'
|
||||
|
||||
def test_missing_session(self):
|
||||
app = flask.Flask(__name__)
|
||||
def expect_exception(f, *args, **kwargs):
|
||||
try:
|
||||
f(*args, **kwargs)
|
||||
except RuntimeError, e:
|
||||
assert e.args and 'session is unavailable' in e.args[0]
|
||||
else:
|
||||
assert False, 'expected exception'
|
||||
with app.test_request_context():
|
||||
assert flask.session.get('missing_key') is None
|
||||
expect_exception(flask.session.__setitem__, 'foo', 42)
|
||||
expect_exception(flask.session.pop, 'foo')
|
||||
|
||||
def test_flashes(self):
|
||||
app = flask.Flask(__name__)
|
||||
app.secret_key = 'testkey'
|
||||
|
||||
with app.test_request_context():
|
||||
assert not flask.session.modified
|
||||
flask.flash('Zap')
|
||||
flask.session.modified = False
|
||||
flask.flash('Zip')
|
||||
assert flask.session.modified
|
||||
assert list(flask.get_flashed_messages()) == ['Zap', 'Zip']
|
||||
|
||||
def test_request_processing(self):
|
||||
app = flask.Flask(__name__)
|
||||
evts = []
|
||||
@app.before_request
|
||||
def before_request():
|
||||
evts.append('before')
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
response.data += '|after'
|
||||
evts.append('after')
|
||||
return response
|
||||
@app.route('/')
|
||||
def index():
|
||||
assert 'before' in evts
|
||||
assert 'after' not in evts
|
||||
return 'request'
|
||||
assert 'after' not in evts
|
||||
rv = app.test_client().get('/').data
|
||||
assert 'after' in evts
|
||||
assert rv == 'request|after'
|
||||
|
||||
def test_error_handling(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.errorhandler(404)
|
||||
def not_found(e):
|
||||
return 'not found', 404
|
||||
@app.errorhandler(500)
|
||||
def internal_server_error(e):
|
||||
return 'internal server error', 500
|
||||
@app.route('/')
|
||||
def index():
|
||||
flask.abort(404)
|
||||
@app.route('/error')
|
||||
def error():
|
||||
1/0
|
||||
c = app.test_client()
|
||||
rv = c.get('/')
|
||||
assert rv.status_code == 404
|
||||
assert rv.data == 'not found'
|
||||
rv = c.get('/error')
|
||||
assert rv.status_code == 500
|
||||
assert 'internal server error' in rv.data
|
||||
|
||||
def test_response_creation(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/unicode')
|
||||
def from_unicode():
|
||||
return u'Hällo Wörld'
|
||||
@app.route('/string')
|
||||
def from_string():
|
||||
return u'Hällo Wörld'.encode('utf-8')
|
||||
@app.route('/args')
|
||||
def from_tuple():
|
||||
return 'Meh', 400, {'X-Foo': 'Testing'}, 'text/plain'
|
||||
c = app.test_client()
|
||||
assert c.get('/unicode').data == u'Hällo Wörld'.encode('utf-8')
|
||||
assert c.get('/string').data == u'Hällo Wörld'.encode('utf-8')
|
||||
rv = c.get('/args')
|
||||
assert rv.data == 'Meh'
|
||||
assert rv.headers['X-Foo'] == 'Testing'
|
||||
assert rv.status_code == 400
|
||||
assert rv.mimetype == 'text/plain'
|
||||
|
||||
def test_url_generation(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/hello/<name>', methods=['POST'])
|
||||
def hello():
|
||||
pass
|
||||
with app.test_request_context():
|
||||
assert flask.url_for('hello', name='test x') == '/hello/test%20x'
|
||||
|
||||
def test_custom_converters(self):
|
||||
from werkzeug.routing import BaseConverter
|
||||
class ListConverter(BaseConverter):
|
||||
def to_python(self, value):
|
||||
return value.split(',')
|
||||
def to_url(self, value):
|
||||
return ','.join(super(ListConverter, self).to_url(x) for x in value)
|
||||
app = flask.Flask(__name__)
|
||||
app.url_map.converters['list'] = ListConverter
|
||||
@app.route('/<list:args>')
|
||||
def index(args):
|
||||
return '|'.join(args)
|
||||
c = app.test_client()
|
||||
assert c.get('/1,2,3').data == '1|2|3'
|
||||
|
||||
def test_static_files(self):
|
||||
app = flask.Flask(__name__)
|
||||
rv = app.test_client().get('/static/index.html')
|
||||
assert rv.status_code == 200
|
||||
assert rv.data.strip() == '<h1>Hello World!</h1>'
|
||||
with app.test_request_context():
|
||||
assert flask.url_for('static', filename='index.html') \
|
||||
== '/static/index.html'
|
||||
|
||||
|
||||
class JSONTestCase(unittest.TestCase):
|
||||
|
||||
def test_jsonify(self):
|
||||
d = dict(a=23, b=42, c=[1, 2, 3])
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/kw')
|
||||
def return_kwargs():
|
||||
return flask.jsonify(**d)
|
||||
@app.route('/dict')
|
||||
def return_dict():
|
||||
return flask.jsonify(d)
|
||||
c = app.test_client()
|
||||
for url in '/kw', '/dict':
|
||||
rv = c.get(url)
|
||||
assert rv.mimetype == 'application/json'
|
||||
assert flask.json.loads(rv.data) == d
|
||||
|
||||
def test_json_attr(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/add', methods=['POST'])
|
||||
def add():
|
||||
return unicode(flask.request.json['a'] + flask.request.json['b'])
|
||||
c = app.test_client()
|
||||
rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}),
|
||||
content_type='application/json')
|
||||
assert rv.data == '3'
|
||||
|
||||
def test_template_escaping(self):
|
||||
app = flask.Flask(__name__)
|
||||
with app.test_request_context():
|
||||
rv = flask.render_template_string('{{ "</script>"|tojson|safe }}')
|
||||
assert rv == '"<\\/script>"'
|
||||
rv = flask.render_template_string('{{ "<\0/script>"|tojson|safe }}')
|
||||
assert rv == '"<\\u0000\\/script>"'
|
||||
|
||||
|
||||
class TemplatingTestCase(unittest.TestCase):
|
||||
|
||||
def test_context_processing(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.context_processor
|
||||
def context_processor():
|
||||
return {'injected_value': 42}
|
||||
@app.route('/')
|
||||
def index():
|
||||
return flask.render_template('context_template.html', value=23)
|
||||
rv = app.test_client().get('/')
|
||||
assert rv.data == '<p>23|42'
|
||||
|
||||
def test_escaping(self):
|
||||
text = '<p>Hello World!'
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/')
|
||||
def index():
|
||||
return flask.render_template('escaping_template.html', text=text,
|
||||
html=flask.Markup(text))
|
||||
lines = app.test_client().get('/').data.splitlines()
|
||||
assert lines == [
|
||||
'<p>Hello World!',
|
||||
'<p>Hello World!',
|
||||
'<p>Hello World!',
|
||||
'<p>Hello World!',
|
||||
'<p>Hello World!',
|
||||
'<p>Hello World!'
|
||||
]
|
||||
|
||||
def test_macros(self):
|
||||
app = flask.Flask(__name__)
|
||||
with app.test_request_context():
|
||||
macro = flask.get_template_attribute('_macro.html', 'hello')
|
||||
assert macro('World') == 'Hello World!'
|
||||
|
||||
|
||||
def suite():
|
||||
from minitwit_tests import MiniTwitTestCase
|
||||
from flaskr_tests import FlaskrTestCase
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(ContextTestCase))
|
||||
suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase))
|
||||
suite.addTest(unittest.makeSuite(TemplatingTestCase))
|
||||
if flask.json_available:
|
||||
suite.addTest(unittest.makeSuite(JSONTestCase))
|
||||
suite.addTest(unittest.makeSuite(MiniTwitTestCase))
|
||||
suite.addTest(unittest.makeSuite(FlaskrTestCase))
|
||||
return suite
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='suite')
|
||||
|
|
@ -1 +0,0 @@
|
|||
<h1>Hello World!</h1>
|
||||
|
|
@ -1 +0,0 @@
|
|||
{% macro hello(name) %}Hello {{ name }}!{% endmacro %}
|
||||
|
|
@ -1 +0,0 @@
|
|||
<p>{{ value }}|{{ injected_value }}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{{ text }}
|
||||
{{ html }}
|
||||
{% autoescape false %}{{ text }}
|
||||
{{ html }}{% endautoescape %}
|
||||
{% autoescape true %}{{ text }}
|
||||
{{ html }}{% endautoescape %}
|
||||