Skip to content

Commit

Permalink
Added JSON Support and started working on jQuery docs
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Apr 19, 2010
1 parent c64a4e0 commit 6e2be6a
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 9 deletions.
59 changes: 54 additions & 5 deletions docs/api.rst
Expand Up @@ -56,12 +56,15 @@ Incoming Request Data

.. attribute:: stream

If the incoming form data was not encoded with a known encoding (for
example it was transmitted as JSON) the data is stored unmodified in
this stream for consumption. For example to read the incoming
request data as JSON, one can do the following::
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

json_body = simplejson.load(request.stream)
Contains the incoming request data as string in case it came with
a mimetype Flask does not handle.

.. attribute:: files

Expand Down Expand Up @@ -106,6 +109,20 @@ Incoming Request Data
`root_url` ``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
----------------

Expand Down Expand Up @@ -201,6 +218,38 @@ Message Flashing

.. 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>

Template Rendering
------------------

Expand Down
1 change: 1 addition & 0 deletions docs/patterns/index.rst
Expand Up @@ -19,3 +19,4 @@ end of the request, the database connection is closed again.
wtforms
templateinheritance
flashing
jquery
59 changes: 59 additions & 0 deletions docs/patterns/jquery.rst
@@ -0,0 +1,59 @@
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.

.. _jQuery: http://jquery.com/

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 two script statements to your `head` section. One for jQuery, and
one for your own script (called `app.js` here):

.. sourcecode:: html

<script type=text/javascript src="{{
url_for('static', filename='jquery.js') }}"></script>
<script type=text/javascript src="{{
url_for('static', filename='app.js') }}"></script>

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 backslashes for you.
29 changes: 29 additions & 0 deletions examples/jqueryexample/jqueryexample.py
@@ -0,0 +1,29 @@
# -*- 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()
25 changes: 25 additions & 0 deletions examples/jqueryexample/templates/index.html
@@ -0,0 +1,25 @@
<!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 }};

$(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);
});
});
});
</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>
58 changes: 56 additions & 2 deletions flask.py
Expand Up @@ -15,11 +15,23 @@

from jinja2 import Environment, PackageLoader, FileSystemLoader
from werkzeug import Request as RequestBase, Response as ResponseBase, \
LocalStack, LocalProxy, create_environ, SharedDataMiddleware
LocalStack, LocalProxy, create_environ, SharedDataMiddleware, \
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
Expand Down Expand Up @@ -49,6 +61,16 @@ def __init__(self, environ):
self.endpoint = None
self.view_args = None

@cached_property
def json(self):
"""If the mimetype is `application/json` this will contain the
parsed JSON data.
"""
if not json_available:
raise AttributeError('simplejson not available')
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
Expand Down Expand Up @@ -81,7 +103,6 @@ def _fail(self, *args, **kwargs):
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
Expand Down Expand Up @@ -133,6 +154,8 @@ def get_template_attribute(template_name, attribute):
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
"""
Expand Down Expand Up @@ -162,6 +185,35 @@ def get_flashed_messages():
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
"""
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.
Expand Down Expand Up @@ -326,6 +378,8 @@ def __init__(self, package_name):
url_for=url_for,
get_flashed_messages=get_flashed_messages
)
if json_available:
self.jinja_env.filters['tojson'] = json.dumps

def create_jinja_loader(self):
"""Creates the Jinja loader. By default just a package loader for
Expand Down
32 changes: 30 additions & 2 deletions tests/flask_tests.py
Expand Up @@ -32,7 +32,7 @@ def meh():
assert meh() == 'http://localhost/meh'


class BasicFunctionality(unittest.TestCase):
class BasicFunctionalityTestCase(unittest.TestCase):

def test_request_dispatching(self):
app = flask.Flask(__name__)
Expand Down Expand Up @@ -167,7 +167,35 @@ def test_static_files(self):
== '/static/index.html'


class Templating(unittest.TestCase):
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'


class TemplatingTestCase(unittest.TestCase):

def test_context_processing(self):
app = flask.Flask(__name__)
Expand Down

0 comments on commit 6e2be6a

Please sign in to comment.