Writing your first Aurora based Web application, part 2

In this second part of the Web application tutorial you will learn how to integrate components shipped with the Aurora library to address common needs and how to create components to integrate third party libraries.

Before you start coding we recommend you to copy the Python module produced in the first part of this tutorial named application into a new folder named tutorial-2. For cut and paste purposes, the source code for all stages of this tutorial can be browsed at https://github.com/yeiniel/aurora/tree/master/documentation/intro/webapp/src/tutorial-2.

Using views

The MVC pattern is widely used in Web application development. Products created using the Web application framework from the Aurora library are not enforced to use this pattern or any other related to the concept of separation of concerns (MVP, etc). Even though the library provide a component very useful to construct the View part for an MVC based product (The Views component). Next we are going to refactor our Web application to implement Web request handlers using the MVC pattern. As a first step we are going to make accessible the Views component from the Web application services by adding it as a Web application attribute (property).

1
2
3
4
5
6
7

    @property
    def views(self) -> views.Views:
        try:
            return self.__views
        except AttributeError:
            self.__views = views.Views()

This step require that you first import the views module. Now we are ready to modify the Web request handlers so they implement the MVC pattern. The code will look as follows:

def list_posts(self, request: foundation.Request) -> foundation.Response:
    """ List summaries for posts added more recently. """
    return self.views.render2response(request, 'list.html')

def show_post(self, request: foundation.Request) -> foundation.Response:
    """ Show a post. """
    return self.views.render2response(request, 'show.html')

def add_post(self, request: foundation.Request) -> foundation.Response:
    """ Add a new Blog post. """
    if request.method == 'POST':
        # process form submission
        pass
    else:
        return self.views.render2response(request, 'form.html')

The Web request handler services has been modified to use the render2response() service. This service takes a Web request object, the path of the template file (without the last extension, read the API documentation for that service for more information), an arbitrary number of arguments used as view context and return the corresponding Web response object.

Once the Web application has been modified the only missing step is adding the templates used to render the views, but we are going to do that latter once we write the data persistence logic. Create a folder inside the Web application folder named templates, this is the one that we are going to register as template source using the following snippet of code added to the Web application __init__() method:

1
2
3
4
5
        # add the `templates` folder as template source for the `views`
        # component
        self.views.add_path(os.path.join(os.path.dirname(__file__),
            'templates'))
        self.views.add_default('url_for', self.url_for)

This snippet additionally add as default views context element the url_for() Web application service used to create links on the Web responses.

At this point is recommended that you review the Web application components section of the API Documentation and learn about other Web application components shipped with the Aurora library.

Integrating SQLAlchemy

SQLAlchemy is a powerful Database abstraction library writen in Python that provide a ORM pattern implementation. We re going to use the ORM to implement the data layer of the Web application (the M part of the MVC design pattern). In order to integrate the library into the application we are going to add a new component and will name it engine_provider. Add a Python package named components to the Web application folder and there add a Python module with that name with the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

import sqlalchemy

__all__ = ['EngineProvider']


class EngineProvider:
    """ `SQLAlchemy`_ support provider.

    This component provide support for use the ``SQLAlchemy`` library to
    connect to one database. The `get_engine` method is the only exposed
    service.

    The source database is configured using the `dsn` component attribute.

    If you need different database connections in the same application you
    can create multiple instances of this component and distribute them as
    needed.

    .. _SQLAlchemy: http://www.sqlalchemy.org/
    """

    dsn = 'sqlite:///application.db'

    def __init__(self, dsn=None):
        self._engine = sqlalchemy.create_engine(dsn or self.dsn)

    def get_engine(self) -> sqlalchemy.engine.Engine:
        """ Return an :class:`sqlalchemy.engine.Engine` object.
        :return: a ready to use :class:`sqlalchemy.engine.Engine` object.
        """
        return self._engine

Once we have that component in place add Web application models into a separated Python module named models in the Web application folder as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

import sqlalchemy
from sqlalchemy.ext import declarative

__all__ = ['Post']


Model = declarative.declarative_base()


class Post(Model):
    __tablename__ = 'blog_post'

    id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
    title = sqlalchemy.Column(sqlalchemy.String, nullable=False)
    content = sqlalchemy.Column(sqlalchemy.Text, nullable=False)
    author = sqlalchemy.Column(sqlalchemy.String, nullable=False)
    date = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False)

Now to make the models available to the Web application definition we need to add the following import line:

1
from components import engine_provider

As we do with the views component we need to make accessible the component from the Web application services by importing the component Python module into the Web application definition Python module and adding it as a Web application attribute (property):

1
2
3
4
5
6
7
    def db(self) -> engine_provider.EngineProvider:
        try:
            return self.__db
        except AttributeError:
            self.__db = engine_provider.EngineProvider()
            return self.__db

Now to make things easy lets ensure that tables on database needed to store the model data are available by adding the following line to the Web application __init__() method:

1
2
3
        # try to create the database tables if needed
        models.Post.metadata.create_all(self.db.get_engine())

And finally lets show you the Web request handlers services once modified to use the get_engine() service from the EngineProvider component:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
        """ List summaries for posts added more recently. """
        orm_session = orm.sessionmaker(bind=self.db.get_engine())()

        return self.views.render2response(request, 'list.html',
            posts=orm_session.query(models.Post).order_by(
                sqlalchemy.desc(models.Post.date))[:10],
            blog=self)

    def show_post(self, request: foundation.Request) -> foundation.Response:
        """ Show a post. """
        post_id = request.params['id']

        orm_session = orm.sessionmaker(bind=self.db.get_engine())()

        return self.views.render2response(request, 'show.html',
            post=orm_session.query(models.Post).filter_by(id=post_id).one(),
            blog=self)

    def add_post(self, request: foundation.Request) -> foundation.Response:
        """ Add a new Blog post. """
        if request.method == 'POST':
            # process form submission
            # TODO: need to implement form validation here.
            post = models.Post(
                title=request.POST['title'],
                content=request.POST['content'],
                author='',
                date=datetime.datetime.utcnow(),
            )

            orm_session = orm.sessionmaker(bind=self.db.get_engine())()
            orm_session.add(post)
            orm_session.commit()

            # redirect to the post page
            resp = request.response_factory()
            resp.status_int = 302
            resp.location = request.application_url

            return resp
        else:
            return self.views.render2response(request, 'form.html', blog=self)

For this code to work you need first to import the following modules: datetime, sqlalchemy and sqlalchemy.orm.

The used architecture allow us to share a common database connections across a set of components and provide different database engine for different components if needed (consider the case you are using one component that use specific features from one database engine). Once we have the Blogging application passing real data objects into the views we only need to show you the templates used.

templates/list.html.suba

1
2
3
4
5
<section id="index">
    %(for post in posts:)
        %(render('summary', post=post, blog=blog))
    %/
</section>

templates/summary.suba

1
2
3
4
5
<section class="summary" id="summary-%(str(post.id))">
    <h1><a href="%(url_for(_handler=blog.show_post, id=str(post.id)))">
        %(post.title)</a></h1>
    <div class="content">%(post.content[:60]) ...</div>
</section>

templates/show.html.suba

1
2
3
4
5
6
<section class="show" id="show-%(str(post.id))">
    <h1><a href="%(url_for(_handler=blog.show_post, id=str(post.id)))">
        %(post.title)</a></h1>
    <div class="date">%(str(post.date))</div>
    <div class="content">%(post.content)</div>
</section>

templates/form.html.suba

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<section id="form">
    <form action="%(url_for(_handler=blog.add_post))" method="post">
        <fieldset>
            <legend>Add Post</legend>
            <div class="clarfix">
                <label for="title">Title</label>
                <div class="input">
                    <input type="text" id="title" name="title"></input>
                </div>
            </div>
            <div class="clarfix">
                <label for="content">Content</label>
                <div class="input">
                    <textarea id="content" name="content" class="xlarge" rows="3"></textarea>
                </div>
            </div>
            <div class="actions">
                <input class="btn primary" type="submit" name="submit" value="Add" />
            </div>
        </fieldset>
    </form>
</section>

As you can guess by the template file extension this templates are using the suba template engine, a lightweight template engine based on the mod (%) operator.

Conclusion

Well, this is all for now. In this tutorial you learn how to integrate components provided by the Aurora library into your application and how to create new ones to integrate third party libraries to provide your Web application with features not provided by the Aurora library and you have learn that the Aurora library provide a generic template based views framework with support by default for the suba template engine.