Permalink
Browse files

Added lazyloading pattern and explicit chapter links in tutorial.

This fixes #49.
  • Loading branch information...
1 parent 6c095de commit 9d19b77acf413de77b39ed1c6d972fb1e5fef1c3 @mitsuhiko mitsuhiko committed May 26, 2010
View
@@ -140,7 +140,7 @@
#html_additional_pages = {}
# If false, no module index is generated.
-#html_use_modindex = True
+html_use_modindex = False
# If false, no index is generated.
#html_use_index = True
@@ -152,7 +152,7 @@
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+html_show_sphinx = False
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
View
@@ -28,3 +28,4 @@ Snippet Archives <http://flask.pocoo.org/snippets/>`_.
flashing
jquery
errorpages
+ lazyloading
@@ -0,0 +1,104 @@
+Lazily Loading Views
+====================
+
+Flask is usually used with the decorators. Decorators are simple and you
+have the URL right next to the function that is called for that specific
+URL. However there is a downside to this approach: it means all your code
+that uses decorators has to be imported upfront or Flask will never
+actually find your function.
+
+This can be a problem if your application has to import quick. It might
+have to do that on systems like Google's AppEngine or other systems. So
+if you suddenly notice that your application outgrows this approach you
+can fall back to a centralized URL mapping.
+
+The system that enables having a central URL map is the
+:meth:`~flask.Flask.add_url_rule` function. Instead of using decorators,
+you have a file that sets up the application with all URLs.
+
+Converting to Centralized URL Map
+---------------------------------
+
+Imagine the current application looks somewhat like this::
+
+ from flask import Flask
+ app = Flask(__name__)
+
+ @app.route('/')
+ def index():
+ pass
+
+ @app.route('/user/<username>')
+ def user(username):
+ pass
+
+Then the centralized approach you would have one file with the views
+(`views.py`) but without any decorator::
+
+ def index():
+ pass
+
+ def user(username):
+ pass
+
+And then a file that sets up an application which maps the functions to
+URLs::
+
+ from flask import Flask
+ from yourapplication import views
+ app = Flask(__name__)
+ app.add_url_rule('/', view_func=views.index)
+ app.add_url_rule('/user/<username>', view_func=views.user)
+
+Loading Late
+------------
+
+So far we only split up the views and the routing, but the module is still
+loaded upfront. The trick to actually load the view function as needed.
+This can be accomplished with a helper class that behaves just like a
+function but internally imports the real function on first use::
+
+ from werkzeug import import_string, cached_property
+
+ class LazyView(object):
+
+ def __init__(self, import_name):
+ self.__module__, self.__name__ = import_name.rsplit('.', 1)
+ self.import_name = import_name
+
+ @cached_property
+ def view(self):
+ return import_string(self.import_name)
+
+ def __call__(self, *args, **kwargs):
+ return self.view(*args, **kwargs)
+
+What's important here is is that `__module__` and `__name__` are properly
+set. This is used by Flask internally to figure out how to do name the
+URL rules in case you don't provide a name for the rule yourself.
+
+Then you can define your central place to combine the views like this::
+
+ from flask import Flask
+ from yourapplication.helpers import LazyView
+ app = Flask(__name__)
+ app.add_url_rule('/',
+ view_func=LazyView('yourapplication.views.index'))
+ app.add_url_rule('/user/<username>',
+ view_func=LazyView('yourapplication.views.user'))
+
+You can further optimize this in terms of amount of keystrokes needed to
+write this by having a function that calls into
+:meth:`~flask.Flask.add_url_rule` by prefixing a string with the project
+name and a dot, and by wrapping `view_func` in a `LazyView` as needed::
+
+ def url(url_rule, import_name, **options):
+ view = LazyView('yourapplication.' + import_name)
+ app.add_url_rule(url_rule, view_func=view, **options)
+
+ url('/', 'views.index')
+ url('/user/<username>', 'views.user')
+
+One thing to keep in mind is that before and after request handlers have
+to be in a file that is imported upfront to work propery on the first
+request. The same goes for any kind of remaining decorator.
View
@@ -1,3 +1,5 @@
+.. _tutorial-css:
+
Step 7: Adding Style
====================
@@ -25,3 +27,5 @@ folder we created before:
.flash { background: #CEE5F5; padding: 0.5em;
border: 1px solid #AACBE2; }
.error { background: #F0D6D6; padding: 0.5em; }
+
+Continue with :ref:`tutorial-testing`.
View
@@ -1,3 +1,5 @@
+.. _tutorial-dbcon:
+
Step 4: Request Database Connections
------------------------------------
@@ -31,3 +33,5 @@ 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.
+
+Continue to :ref:`tutorial-views`.
View
@@ -1,3 +1,5 @@
+.. _tutorial-dbinit:
+
Step 3: Creating The Database
=============================
@@ -61,3 +63,5 @@ importing and calling that function::
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).
+
+Continue with :ref:`tutorial-dbcon`
@@ -1,3 +1,5 @@
+.. _tutorial-folders:
+
Step 0: Creating The Folders
============================
@@ -16,4 +18,6 @@ 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.
+Continue with :ref:`tutorial-schema`.
+
.. _Jinja2: http://jinja.pocoo.org/2/
@@ -1,3 +1,5 @@
+.. _tutorial-introduction:
+
Introducing Flaskr
==================
@@ -26,4 +28,6 @@ Here a screenshot from the final application:
:class: screenshot
:alt: screenshot of the final application
+Continue with :ref:`tutorial-folders`.
+
.. _SQLAlchemy: http://www.sqlalchemy.org/
View
@@ -1,3 +1,5 @@
+.. _tutorial-schema:
+
Step 1: Database Schema
=======================
@@ -19,3 +21,5 @@ 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.
+
+Continue with :ref:`tutorial-setup`.
View
@@ -1,3 +1,5 @@
+.. _tutorial-setup:
+
Step 2: Application Setup Code
==============================
@@ -62,3 +64,5 @@ focus on that a little later. First we should get the database working.
Want your server to be publically available? Check out the
:ref:`externally visible server <public-server>` section for more
information.
+
+Continue with :ref:`tutorial-dbinit`.
@@ -1,3 +1,5 @@
+.. _tutorial-templates:
+
Step 6: The Templates
=====================
@@ -105,3 +107,5 @@ the user to login:
</dl>
</form>
{% endblock %}
+
+Continue with :ref:`tutorial-css`.
@@ -1,3 +1,5 @@
+.. _tutorial-testing:
+
Bonus: Testing the Application
===============================
View
@@ -1,3 +1,5 @@
+.. _tutorial-views:
+
Step 5: The View Functions
==========================
@@ -85,3 +87,5 @@ that case if the user was logged in.
session.pop('logged_in', None)
flash('You were logged out')
return redirect(url_for('show_entries'))
+
+Continue with :ref:`tutorial-templates`.
@@ -127,6 +127,7 @@ def user_timeline(username):
follower.who_id = ? and follower.whom_id = ?''',
[session['user_id'], profile_user['user_id']],
one=True) is not None
+ broken_just_for_djangocon
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 = ?

0 comments on commit 9d19b77

Please sign in to comment.