bundled/flask/docs/patterns/packages.rst @ 73d048325dfd

Merged in akabos/hg-review (pull request #2)
author Christophe de Vienne <cdevienne@gmail.com>
date Fri, 12 Dec 2014 10:36:19 +0100
parents f33efe14bff1
children (none)
.. _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
            ...

Simple Packages
---------------

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's 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!'

You should then end up with something like that::

    /yourapplication
        /yourapplication
            /__init__.py
            /views.py
            /static
                /style.css
            /templates
                layout.html
                index.html
                login.html
                ...

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


.. _working-with-modules:

Working with Modules
--------------------

For larger applications with more than a dozen views it makes sense to
split the views into modules.  First let's look at the typical structure of
such an application::

    /yourapplication
        /yourapplication
            /__init__.py
            /views
                __init__.py
                admin.py
                frontend.py
            /static
                /style.css
            /templates
                layout.html
                index.html
                login.html
                ...

The views are stored in the `yourapplication.views` package.  Just make
sure to place an empty `__init__.py` file in there.  Let's start with the
`admin.py` file in the view package.

First we have to create a :class:`~flask.Module` object with the name of
the package.  This works very similar to the :class:`~flask.Flask` object
you have already worked with, it just does not support all of the methods,
but most of them are the same.

Long story short, here's a nice and concise example::

    from flask import Module

    admin = Module(__name__)

    @admin.route('/')
    def index():
        pass

    @admin.route('/login')
    def login():
        pass

    @admin.route('/logout')
    def logout():
        pass

Do the same with the `frontend.py` and then make sure to register the
modules in the application (`__init__.py`) like this::

    from flask import Flask
    from yourapplication.views.admin import admin
    from yourapplication.views.frontend import frontend

    app = Flask(__name__)
    app.register_module(admin, url_prefix='/admin')
    app.register_module(frontend)

We register the modules with the app so that it can add them to the
URL map for our application.  Note the prefix argument to the admin
module: by default when we register a module, that module's end-points
will be registered on `/` unless we specify this argument.

So what is different when working with modules?  It mainly affects URL
generation.  Remember the :func:`~flask.url_for` function?  When not
working with modules it accepts the name of the function as first
argument.  This first argument is called the "endpoint".  When you are
working with modules you can use the name of the function like you did
without, when generating modules from a function or template in the same
module.  If you want to generate the URL to another module, prefix it with
the name of the module and a dot.

Confused?  Let's clear that up with some examples.  Imagine you have a
method in one module (say `admin`) and you want to redirect to a
different module (say `frontend`).  This would look like this::

    @admin.route('/to_frontend')
    def to_frontend():
        return redirect(url_for('frontend.index'))

    @frontend.route('/')
    def index():
        return "I'm the frontend index"

Now let's say we only want to redirect to a different function in the same
module.  Then we can either use the full qualified endpoint name like we
did in the example above, or we just use the function name::

    @frontend.route('/to_index')
    def to_index():
        return redirect(url_for('index'))

    @frontend.route('/')
    def index():
        return "I'm the index"

.. _modules-and-resources:

Modules and Resources
---------------------

.. versionadded:: 0.5

If a module is located inside an actual Python package it may contain
static files and templates.  Imagine you have an application like this::


    /yourapplication
        __init__.py
        /apps
	    __init__.py
            /frontend
                __init__.py
                views.py
                /static
                    style.css
                /templates
                    index.html
                    about.html
                    ...
            /admin
                __init__.py
                views.py
                /static
                    style.css
                /templates
                    list_items.html
                    show_item.html
                    ...

The static folders automatically become exposed as URLs.  For example if
the `admin` module is exported with an URL prefix of ``/admin`` you can
access the style css from its static folder by going to
``/admin/static/style.css``.  The URL endpoint for the static files of the
admin would be ``'admin.static'``, similar to how you refer to the regular
static folder of the whole application as ``'static'``.

If you want to refer to the templates you just have to prefix it with the
name of the module.  So for the admin it would be
``render_template('admin/list_items.html')`` and so on.  It is not
possible to refer to templates without the prefixed module name.  This is
explicit unlike URL rules.

You also need to explicitly pass the ``url_prefix`` argument when
registering your modules this way::

    # in yourapplication/__init__.py
    from flask import Flask
    from yourapplication.apps.admin.views import admin
    from yourapplication.apps.frontend.views import frontend


    app = Flask(__name__)
    app.register_module(admin, url_prefix='/admin')
    app.register_module(frontend, url_prefix='/frontend')

This is because Flask cannot infer the prefix from the package names.

.. admonition:: References to Static Folders

   Please keep in mind that if you are using unqualified endpoints by
   default Flask will always assume the module's static folder, even if
   there is no such folder.

   If you want to refer to the application's static folder, use a leading
   dot::

       # this refers to the application's static folder
       url_for('.static', filename='static.css')

       # this refers to the current module's static folder
       url_for('static', filename='static.css')

   This is the case for all endpoints, not just static folders, but for
   static folders it's more common that you will stumble upon this because
   most applications will have a static folder in the application and not
   a specific module.