Fixed merging confict
This commit is contained in:
commit
eeb0e94951
9 changed files with 147 additions and 13 deletions
2
Makefile
2
Makefile
|
|
@ -1,4 +1,4 @@
|
||||||
.PHONY: clean-pyc test
|
.PHONY: clean-pyc test upload-docs
|
||||||
|
|
||||||
all: clean-pyc test
|
all: clean-pyc test
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,13 @@ To access the current session you can use the :class:`session` object:
|
||||||
# so mark it as modified yourself
|
# so mark it as modified yourself
|
||||||
session.modified = True
|
session.modified = True
|
||||||
|
|
||||||
|
.. attribute:: permanent
|
||||||
|
|
||||||
|
If set to `True` the session life for
|
||||||
|
:attr:`~flask.Flask.permanent_session_lifetime` seconds. The
|
||||||
|
default is 31 days. If set to `False` (which is the default) the
|
||||||
|
session will be deleted when the user closes the browser.
|
||||||
|
|
||||||
|
|
||||||
Application Globals
|
Application Globals
|
||||||
-------------------
|
-------------------
|
||||||
|
|
|
||||||
|
|
@ -91,3 +91,52 @@ Here the code::
|
||||||
|
|
||||||
Notice that this assumes an instanciated `cache` object is available, see
|
Notice that this assumes an instanciated `cache` object is available, see
|
||||||
:ref:`caching-pattern` for more information.
|
:ref:`caching-pattern` for more information.
|
||||||
|
|
||||||
|
|
||||||
|
Templating Decorator
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
A common pattern invented by the TurboGears guys a while back is a
|
||||||
|
templating decorator. The idea of that decorator is that you return a
|
||||||
|
dictionary with the values passed to the template from the view function
|
||||||
|
and the template is automatically rendered. With that, the following
|
||||||
|
three examples do exactly the same::
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('index.html', value=42)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
@templated('index.html')
|
||||||
|
def index():
|
||||||
|
return dict(value=42)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
@templated()
|
||||||
|
def index():
|
||||||
|
return dict(value=42)
|
||||||
|
|
||||||
|
As you can see, if no template name is provided it will use the endpoint
|
||||||
|
of the URL map + ``'.html'``. Otherwise the provided template name is
|
||||||
|
used. When the decorated function returns, the dictionary returned is
|
||||||
|
passed to the template rendering function. If `None` is returned, an
|
||||||
|
empty dictionary is assumed.
|
||||||
|
|
||||||
|
Here the code for that decorator::
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
def templated(template=None):
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
template_name = template
|
||||||
|
if template_name is None:
|
||||||
|
template_name = request.endpoint + '.html'
|
||||||
|
ctx = f(*args, **kwargs)
|
||||||
|
if ctx is None:
|
||||||
|
ctx = {}
|
||||||
|
return render_template(template_name, **ctx)
|
||||||
|
return decorated_function
|
||||||
|
return decorator
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ In the view function, the usage of this form looks like this::
|
||||||
form.password.data)
|
form.password.data)
|
||||||
db_session.add(user)
|
db_session.add(user)
|
||||||
flash('Thanks for registering')
|
flash('Thanks for registering')
|
||||||
redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
return render_template('register.html', form=form)
|
return render_template('register.html', form=form)
|
||||||
|
|
||||||
Notice that we are implying that the view is using SQLAlchemy here
|
Notice that we are implying that the view is using SQLAlchemy here
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,8 @@ The following converters exist:
|
||||||
`path` like the default but also accepts slashes
|
`path` like the default but also accepts slashes
|
||||||
=========== ===========================================
|
=========== ===========================================
|
||||||
|
|
||||||
|
.. _url-building:
|
||||||
|
|
||||||
URL Building
|
URL Building
|
||||||
````````````
|
````````````
|
||||||
|
|
||||||
|
|
@ -167,7 +169,8 @@ 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`
|
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
|
function. It accepts the name of the function as first argument and a
|
||||||
number of keyword arguments, each corresponding to the variable part of
|
number of keyword arguments, each corresponding to the variable part of
|
||||||
the URL rule. Here some examples:
|
the URL rule. Unknown variable parts are appended to the URL as query
|
||||||
|
parameter. Here some examples:
|
||||||
|
|
||||||
>>> from flask import Flask, url_for
|
>>> from flask import Flask, url_for
|
||||||
>>> app = Flask(__name__)
|
>>> app = Flask(__name__)
|
||||||
|
|
@ -184,9 +187,11 @@ the URL rule. Here some examples:
|
||||||
... print url_for('index')
|
... print url_for('index')
|
||||||
... print url_for('login')
|
... print url_for('login')
|
||||||
... print url_for('profile', username='John Doe')
|
... print url_for('profile', username='John Doe')
|
||||||
|
... print url_for('login', next='/')
|
||||||
...
|
...
|
||||||
/
|
/
|
||||||
/login
|
/login
|
||||||
|
/login?next=/
|
||||||
/user/John%20Doe
|
/user/John%20Doe
|
||||||
|
|
||||||
(This also uses the :meth:`~flask.Flask.test_request_context` method
|
(This also uses the :meth:`~flask.Flask.test_request_context` method
|
||||||
|
|
@ -452,7 +457,7 @@ transmitted in a `POST` or `PUT` request) you can use the
|
||||||
:attr:`~flask.request.form` attribute. Here a full example of the two
|
:attr:`~flask.request.form` attribute. Here a full example of the two
|
||||||
attributes mentioned above::
|
attributes mentioned above::
|
||||||
|
|
||||||
@app.route('/login', method=['POST', 'GET'])
|
@app.route('/login', methods=['POST', 'GET'])
|
||||||
def login():
|
def login():
|
||||||
error = None
|
error = None
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
|
|
||||||
|
|
@ -195,3 +195,22 @@ suite.
|
||||||
|
|
||||||
.. _MiniTwit Example:
|
.. _MiniTwit Example:
|
||||||
http://github.com/mitsuhiko/flask/tree/master/examples/minitwit/
|
http://github.com/mitsuhiko/flask/tree/master/examples/minitwit/
|
||||||
|
|
||||||
|
|
||||||
|
Other Testing Tricks
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Besides using the test client we used above there is also the
|
||||||
|
:meth:`~flask.Flask.test_request_context` method that in combination with
|
||||||
|
the `with` statement can be used to activate a request context
|
||||||
|
temporarily. With that you can access the :class:`~flask.request`,
|
||||||
|
:class:`~flask.g` and :class:`~flask.session` objects like in view
|
||||||
|
functions. Here a full example that showcases this::
|
||||||
|
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
with app.test_request_context('/?name=Peter'):
|
||||||
|
assert flask.request.path == '/'
|
||||||
|
assert flask.request.args['name'] == 'Peter'
|
||||||
|
|
||||||
|
All the other objects that are context bound can be used the same.
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ Bonus: Testing the Application
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
Now that you have finished the application and everything works as
|
Now that you have finished the application and everything works as
|
||||||
expected, it's probably not the best idea to add automated tests to
|
expected, it's probably not a good idea to add automated tests to simplify
|
||||||
simplify modifications in the future. The application above is used as a
|
modifications in the future. The application above is used as a basic
|
||||||
basic example of how to perform unittesting in the :ref:`testing` section
|
example of how to perform unittesting in the :ref:`testing` section of the
|
||||||
of the documentation. Go there to see how easy it is to test Flask
|
documentation. Go there to see how easy it is to test Flask applications.
|
||||||
applications.
|
|
||||||
|
|
|
||||||
36
flask.py
36
flask.py
|
|
@ -13,6 +13,7 @@ from __future__ import with_statement
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from jinja2 import Environment, PackageLoader, FileSystemLoader
|
from jinja2 import Environment, PackageLoader, FileSystemLoader
|
||||||
|
|
@ -93,7 +94,20 @@ class _RequestGlobals(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class _NullSession(SecureCookie):
|
class Session(SecureCookie):
|
||||||
|
"""Expands the session for support for switching between permanent
|
||||||
|
and non-permanent sessions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_permanent(self):
|
||||||
|
return self.get('_permanent', False)
|
||||||
|
def _set_permanent(self, value):
|
||||||
|
self['_permanent'] = bool(value)
|
||||||
|
permanent = property(_get_permanent, _set_permanent)
|
||||||
|
del _get_permanent, _set_permanent
|
||||||
|
|
||||||
|
|
||||||
|
class _NullSession(Session):
|
||||||
"""Class used to generate nicer error messages if sessions are not
|
"""Class used to generate nicer error messages if sessions are not
|
||||||
available. Will still allow read-only access to the empty session
|
available. Will still allow read-only access to the empty session
|
||||||
but fail on setting.
|
but fail on setting.
|
||||||
|
|
@ -158,6 +172,11 @@ def url_for(endpoint, **values):
|
||||||
any ``'admin.index'`` `index` of the `admin` module
|
any ``'admin.index'`` `index` of the `admin` module
|
||||||
==================== ======================= =============================
|
==================== ======================= =============================
|
||||||
|
|
||||||
|
Variable arguments that are unknown to the target endpoint are appended
|
||||||
|
to the generated URL as query arguments.
|
||||||
|
|
||||||
|
For more information, head over to the :ref:`Quickstart <url-building>`.
|
||||||
|
|
||||||
:param endpoint: the endpoint of the URL (name of the function)
|
:param endpoint: the endpoint of the URL (name of the function)
|
||||||
:param values: the variable arguments of the URL rule
|
:param values: the variable arguments of the URL rule
|
||||||
"""
|
"""
|
||||||
|
|
@ -511,6 +530,11 @@ class Flask(_PackageBoundObject):
|
||||||
#: The secure cookie uses this for the name of the session cookie
|
#: The secure cookie uses this for the name of the session cookie
|
||||||
session_cookie_name = 'session'
|
session_cookie_name = 'session'
|
||||||
|
|
||||||
|
#: A :class:`~datetime.timedelta` which is used to set the expiration
|
||||||
|
#: date of a permanent session. The default is 31 days which makes a
|
||||||
|
#: permanent session survive for roughly one month.
|
||||||
|
permanent_session_lifetime = timedelta(days=31)
|
||||||
|
|
||||||
#: options that are passed directly to the Jinja2 environment
|
#: options that are passed directly to the Jinja2 environment
|
||||||
jinja_options = ImmutableDict(
|
jinja_options = ImmutableDict(
|
||||||
autoescape=True,
|
autoescape=True,
|
||||||
|
|
@ -664,8 +688,8 @@ class Flask(_PackageBoundObject):
|
||||||
"""
|
"""
|
||||||
key = self.secret_key
|
key = self.secret_key
|
||||||
if key is not None:
|
if key is not None:
|
||||||
return SecureCookie.load_cookie(request, self.session_cookie_name,
|
return Session.load_cookie(request, self.session_cookie_name,
|
||||||
secret_key=key)
|
secret_key=key)
|
||||||
|
|
||||||
def save_session(self, session, response):
|
def save_session(self, session, response):
|
||||||
"""Saves the session if it needs updates. For the default
|
"""Saves the session if it needs updates. For the default
|
||||||
|
|
@ -676,7 +700,11 @@ class Flask(_PackageBoundObject):
|
||||||
object)
|
object)
|
||||||
:param response: an instance of :attr:`response_class`
|
:param response: an instance of :attr:`response_class`
|
||||||
"""
|
"""
|
||||||
session.save_cookie(response, self.session_cookie_name)
|
expires = None
|
||||||
|
if session.permanent:
|
||||||
|
expires = datetime.utcnow() + self.permanent_session_lifetime
|
||||||
|
session.save_cookie(response, self.session_cookie_name,
|
||||||
|
expires=expires, httponly=True)
|
||||||
|
|
||||||
def register_module(self, module, **options):
|
def register_module(self, module, **options):
|
||||||
"""Registers a module with this application. The keyword argument
|
"""Registers a module with this application. The keyword argument
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,14 @@
|
||||||
"""
|
"""
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import flask
|
import flask
|
||||||
import unittest
|
import unittest
|
||||||
import tempfile
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
|
from datetime import datetime
|
||||||
|
from werkzeug import parse_date
|
||||||
|
|
||||||
|
|
||||||
example_path = os.path.join(os.path.dirname(__file__), '..', 'examples')
|
example_path = os.path.join(os.path.dirname(__file__), '..', 'examples')
|
||||||
|
|
@ -118,6 +121,30 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
expect_exception(flask.session.__setitem__, 'foo', 42)
|
expect_exception(flask.session.__setitem__, 'foo', 42)
|
||||||
expect_exception(flask.session.pop, 'foo')
|
expect_exception(flask.session.pop, 'foo')
|
||||||
|
|
||||||
|
def test_session_expiration(self):
|
||||||
|
permanent = True
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.secret_key = 'testkey'
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
flask.session['test'] = 42
|
||||||
|
flask.session.permanent = permanent
|
||||||
|
return ''
|
||||||
|
rv = app.test_client().get('/')
|
||||||
|
assert 'set-cookie' in rv.headers
|
||||||
|
match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie'])
|
||||||
|
expires = parse_date(match.group())
|
||||||
|
expected = datetime.utcnow() + app.permanent_session_lifetime
|
||||||
|
assert expires.year == expected.year
|
||||||
|
assert expires.month == expected.month
|
||||||
|
assert expires.day == expected.day
|
||||||
|
|
||||||
|
permanent = False
|
||||||
|
rv = app.test_client().get('/')
|
||||||
|
assert 'set-cookie' in rv.headers
|
||||||
|
match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie'])
|
||||||
|
assert match is None
|
||||||
|
|
||||||
def test_flashes(self):
|
def test_flashes(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
app.secret_key = 'testkey'
|
app.secret_key = 'testkey'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue