building web pages with python evolving the application
Evolving the Application
Simplifying
We're off to a great start with our application! Let's just take a few moments to re-organize the structure and create some shortcuts that will come in handy throughout the development.
Take a look at the index class again:
class index(object):
def GET(self):
t = Template('index', FileSystemLoader('tpl'))
c = Context({
'cat': getCategories(),
'note': getNotes()
})
return HttpResponse(t.render(c))
All the lines of code below GET will be used over and over throughout the application every time we want to print a template. There's got to be an easier way to do this -- and here's how:
First, we'll create a new file called utils.py. utils.py will contain just what it says -- utilities. Here's our first set of utilities:
from jinja import Template, Context, FileSystemLoader
from colubrid import HttpResponse
def tplLoad(tpl, vars={}):
t = Template(tpl, FileSystemLoader('tpl'))
c = Context(vars)
return t.render(c)
def sendData(tpl, vars, ctype='text/html', code=200):
page = tplLoad(tpl, vars)
return HttpResponse(page, [('Content-Type', ctype)], code)
tplLoad() takes care of compiling and rendering the template for us. It accepts the name of the template and the dictionary of variables to use.
sendData() takes a the same input as tplLoad() so it can pass that information off to tplLoad(). It also takes two more parameters -- ctype and code. ctype will determine what Content-type you want to display on the browser -- whether it's html or txt, it's your choice. code is for an HTTP response code. It defaults to 200 which is just the standard code to display the page. Other codes you could use are 404 for Page not Found or 403 for Forbidden.
Now that we have our two helper functions made, lets modify the index class:
class index(object):
def GET(self):
return sendData('index', {
'cat': getCategories(),
'note': getNotes()
})
Much better, right?
Don't forget to modify your module imports at the top of app.py:
from colubrid import WebpyApplication, Request, execute, HttpResponse
from colubrid.server import StaticExports
from models import *
from utils import *
Preparing for the Future
The next modification we're going to make is organizing to prepare for future code. There's going to be a lot of other pages involved with the Notebook application, which means more classes and methods. After a while, the app.py file is going to get pretty long and crowded.
The index class can also be known as something called a Controller. Controller is the C in MVC. You already know M is for Model and now C is for Controller. V is for View -- which are the Jinja templates. Controllers are basically the glue that combines the Model with the View.
To better organize the index controller and all the other future controllers, we'll create a new directory in the root of notebook.demo called controllers:
mkdir controllers
Throughout the lifecyle of the Notebook application, there's going to be several actions associated with it. Sometimes you'll View notes, other times you'll Add and Edit notes, and finally, you can Delete notes. We'll further organize our controller folder by gathering all of the code assicated with these actions into one file.
The index controller can be thought of as a View action (since we're viewing information -- not to be confused with the View in MVC which means Templates). So now we'll create a view.py file inside the controllers folder and move the index controller there.
cd controllers
vi view.py
from utils import *
from models import *
class index(baseWebpy):
def GET(self):
return sendData('index', {
'cat': getCategories(),
'note': getNotes()
})
Next, for those familiar with Python modules, you'll know that anytime you create a directory full of modules, it's also helpful to have an __init__.py file that also includes these modules. This way, you can easily import all the modules in your code by just doing:
import controllers
The __init__.py file will just have
import view
We'll add more later.
Now it's time to modify app.py to accommodate for this change. First, import our controller modules at the top:
from colubrid import WebpyApplication, Request, execute, HttpResponse
from colubrid.server import StaticExports
from utils import *
import controllers
Next, change the urls mappings:
urls = [
(r'^/$', controllers.view.index),
]
Creating a Base Class
The last modification we're going to make is the creation of a base class and a base.py file. The base class will be used for all of our controller classes. This will serve one purpose: to reset the session each time a class is triggered (remember the that's session created in models.py). Doing this will prevent weird caching and saving of data while you're using the Notebook Application.
The base.py file will import all of the required modules needed by the controllers as well as the base class definition.
To create the base class, create a base.py file inside the controllers directory. Inside, add:
from utils import *
from models import *
class baseWebpy:
def __init__(self):
session.clear()
At the top of the view.py controller, replace all of your imports with just one:
from base import *
Finally, make the index class a child of baseWebpy:
class index(baseWebpy):
Checkpoint
Just to recap and make sure my code matches your's, here are all the files and what they should contain:
controllers/__init__.py
import view
controllers/base.py
from utils import *
from models import *
class baseWebpy:
def __init__(self):
session.clear()
controllers/view.py
from base import *
class index(baseWebpy):
def GET(self):
return sendData('index', {
'cat': getCategories(),
'note': getNotes()
})
utils.py
from jinja import Template, Context, FileSystemLoader
from colubrid import HttpResponse
def tplLoad(tpl, vars={}):
t = Template(tpl, FileSystemLoader('tpl'))
c = Context(vars)
return t.render(c)
def sendData(tpl, vars, ctype='text/html', code=200):
page = tplLoad(tpl, vars)
return HttpResponse(page, [('Content-Type', ctype)], code)
models.py
from sqlalchemy import *
db = create_engine('sqlite:///sql/notebook.db')
metadata = BoundMetaData(db)
category = Table('category', metadata,
Column('category_id', Integer, primary_key=True),
Column('category_category', String(255)),
)
note = Table('note', metadata,
Column('note_id', Integer, primary_key=True),
Column('note_title', String(255)),
Column('category_category_id', Integer, ForeignKey('category.category_id')),
)
revision = Table('revision', metadata,
Column('revision_id', Integer, primary_key=True),
Column('note_note_id', Integer, ForeignKey('note.note_id')),
Column('revision_body', String),
Column('revision_time', DateTime, default=func.current_timestamp()),
)
session = create_session()
def getCategories():
r = select([category])
return r.execute().fetchall()
def getNotes():
r = select([note,category],
and_(note.c.category_category_id==category.c.category_id))
return r.execute().fetchall()
app.py
from colubrid import WebpyApplication, Request, execute, HttpResponse
from colubrid.server import StaticExports
import controllers
class Notebook(WebpyApplication):
urls = [
(r'^$', controllers.view.index),
]
app = Notebook
app = StaticExports(app, {
'/static': './static'
})
if __name__ == '__main__':
execute(reload=True, debug=False)
tpl/base.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="stylesheet" href="/static/styles/style.css" />
<title>{% marker "title" %}</title>
</head>
<body>
`<div id="header">
<h1><a href="/" title="notebook">notebook</a></h1>
</div>
<div id="content">
{% marker "content" %}
</div>`
{% marker "sidebar" %}
</body>
tpl/index.html
{% extends "base" %}
{% marker "title" set "Notebook" %}
{% block "content" %}
<ul class="add">
<li><a href="/add/note">New Note</a></li>
<li><a href="/add/category">New Category</a></li>
</ul>
<h3>Existing Notes</h3>
<ul class="existing">
{% for n in note %}
<li><a href="/view/note/{{ n.note_id }}">{{ n.note_title }}</a></li>
{% endfor %}
</ul>
{% endblock %}
{% block "sidebar" %}
<div id="sidebar">
<h3>Categories</h3>
<ul class="sidelist">
{% for c in cat %}
<li><a href="/view/cat/{{ c.category_id }}">{{ c.category_category }}</a></li>
{% endfor %}
</ul>
</div>
{% endblock %}
After making all of these changes, you should be able to run the Colubrid server again and see the exact same output as at the end of Part 4.
