Moved website into separate branch
2
.gitignore
vendored
|
|
@ -3,5 +3,5 @@
|
||||||
*.pyo
|
*.pyo
|
||||||
env
|
env
|
||||||
dist
|
dist
|
||||||
website/_mailinglist/*
|
_mailinglist/*
|
||||||
*.egg-info
|
*.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 %}
|
|
||||||