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.