Keywords: python, pyramid, turbogears, turbogears2 replacement, jquery, web framework, for business, for corporations, for enterprise, mako, sqlalchemy, mysql, mssql, unixodbc

Pyramid Setup

aptitude install python-virtualenv

virtualenv --no-site-packages pyramid_env
cd pyramid_env
source ./bin/activate
easy_install pyramid

Pyramid Project: myapp

pcreate -s alchemy myapp

Install remaining components

cd myapp
python setup.py develop

Run a test

python setup.py test -q

Check how much code is covered by tests

easy_install nose coverage
nosetests --cover-package=myapp --cover-erase --with-coverage

Populate the database

Replace myapp with your app name.

initialize_myapp_db development.ini

Start the Application

pserve development.ini --reload

Visit http://localhost:6543/

Pyramid Commands

Get a list of all defined routes: bin/paster

proutes development.ini

Get a list of probable views for a given URL: bin/paster

pviews development.ini /join

Get a shell with the Pyramid configuration :bin/paster

pshell development.in

Embedded video/webm

Addressbook

structure

You probably created your alchemy scaffold, and here is your folder structure:

myapp/
├── CHANGES.txt
├── development.ini
├── MANIFEST.in
├── myapp
│   ├── __init__.py
│   ├── __init__.pyc
│   ├── models.py
│   ├── models.pyc
│   ├── scripts
│   │   ├── initializedb.py
│   │   ├── initializedb.pyc
│   │   ├── __init__.py
│   │   └── __init__.pyc
│   ├── static
│   │   ├── favicon.ico
│   │   ├── footerbg.png
│   │   ├── headerbg.png
│   │   ├── ie6.css
│   │   ├── middlebg.png
│   │   ├── pylons.css
│   │   ├── pyramid.png
│   │   ├── pyramid-small.png
│   │   └── transparent.gif
│   ├── templates
│   │   └── mytemplate.pt
│   ├── tests.py
│   ├── tests.pyc
│   ├── views.py
│   └── views.pyc
├── myapp.db
├── myapp.egg-info
│   ├── dependency_links.txt
│   ├── entry_points.txt
│   ├── not-zip-safe
│   ├── PKG-INFO
│   ├── requires.txt
│   ├── SOURCES.txt
│   └── top_level.txt
├── production.ini
├── README.txt
├── setup.cfg
└── setup.py

views.py

  • This will be the primary file where we will be working on our web application
  • Lets add our add function that will allow you to enter new addressbook.
  • Edit init.py and add the new add_route.

config.add_route('add', '/add')
  • This will add a route "add" and the url that it will use is "/add"
  • Now lets create a view that will handle our form.
  • Edit views.py and add our new "add view handler.

@view_config(route_name='add', renderer='templates/add.pt')
def add_view(request):
    '''This is an add view. Here is where we define and pass any information to the template.'''
    #Code here
    return {'project':'myapp'}

add page template

  • Lets go to templates folder and create our add.pt
  • To simplify our coding we will copy an existing template and name it add.pt

cd templates
cp mytemplate.pt add.pt
  • Then replace the text:

#From:

Welcome to <span class="app-name">${project}</span>, an application generated by<br/>the Pyramid web application development framework.

#To:

Welcome to <span class="app-name">${project}</span>.<br/> Please add your address.
  • Lets check if it works, and our next step will be to add the form.

add1.png

widget

requires=[

    ...

    "tw2.core",
    "tw2.forms",
    "tw2.dynforms",
    "tw2.sqla",
    "tw2.jqplugins.jqgrid",
    "formencode",
    ],
  • Then run:

python setup.py develop
  • Now edit development.ini and add this on first line:

[pipeline:main]
pipeline =
    tw2.core
    myapp

[filter:tw2.core]
use = egg:tw2.core#middleware
  • And right below change app:main

from

[app:main]
use = egg:...

to

[app:myapp]
use = egg:...
  • Now we are ready to add the widget. Go to the view.py and add above your view

from tw2.forms import TableForm, TextField, CalendarDatePicker, SingleSelectField, TextArea
import tw2
#Validator
from formencode.validators import Int, NotEmpty, DateConverter, DateValidator,PostalCode,String,Email


class AddressForm(TableForm):
    title='MyApp Add Address Form'
    # This WidgetsList is just a container
    #class fields(WidgetsList):
    FirstName = TextField(validator=tw2.core.Required)
    LastName = TextField(validator=tw2.core.Required)
    MaidenLastName = TextField(validator=String)
    Email = TextField(validator=tw2.core.EmailValidator)
    Address = TextField(validator=tw2.core.Required)
    City = TextField(validator=tw2.core.Required)
    State = TextField(validator=tw2.core.Required)
    #Or you could do:
    StateChoices = (("IL"),
                     ("IN"),
                     ("MS"),
                    )
    State = SingleSelectField(options=StateChoices, validator=NotEmpty)
    ZipCode = TextField(size=5, validator=PostalCode())
    DOB = CalendarDatePicker(validator=tw2.core.DateValidator)
    GenderChoices = (("Female"),
                     ("Male"),
                    )
    Gender = SingleSelectField(options=GenderChoices)
    Description = TextArea(rows=3, cols=25)

* Now in the view.py add the following in your add_view. The first line checks if its a post request, then checks if all validators passed if it didn't pass then you need to catch the exception and send it back to the template. When all validators pass you can do what you need with the data.

import tw2.core as twc
@view_config(route_name='add', renderer='templates/add.pt')
def add_view(request):
    from myapp.widgets import AddressForm
    if request.method=='POST':
        # First, validate the posted data
        try:
            form_result = AddressForm.validate(request.POST)
        except twc.ValidationError, e:
            return {'widget': e.widget,'project':'myapp'}
        #print request
    return {'widget':AddressForm(),'project':'myapp'}

Add widget to html template

* The last part is to add our 'widget' to our template.Update your add.pt to:

   <div id="left" class="align-right">
 <p tal:content="structure widget.display()"></p>
        </div>

add_toscawidget2.png

Create database model

  • Now that we have a form, lets create a database structure that we will save our data to. Our Addressbook table definition is a bit different then the example above. Sqlalchemy has two ways of setting database SQLAlchemy ORM vs SQLAlchemy Core. The example that uses class MyModel(Base): is SQLalchemy ORM, the sqlalchemy.Table() is SQLAlchemy Core. I believe sqlalchemy core is much simpler and easier to understand when you look at examples of how to create tables in sql language.

  • Lets edit a model.py and add the following.

from sqlalchemy import (
    Column,
    Integer,
    Text,
    )

from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy.orm import (
    scoped_session,
    sessionmaker,
    )

from zope.sqlalchemy import ZopeTransactionExtension

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()

class MyModel(Base):
    __tablename__ = 'models'
    id = Column(Integer, primary_key=True)
    name = Column(Text, unique=True)
    value = Column(Integer)

    def __init__(self, name, value):
        self.name = name
        self.value = value

#Above was already in the file

#----------Starting from here is our model data.----------------
import sqlalchemy
from sqlalchemy.orm import mapper, relation
#from addressbook.model import metadata
import time
from sqlalchemy.sql.expression import func
metadata = Base.metadata

# Normal tables may be defined and mapped at module level.
# Our Addressbook table definition.
from datetime import datetime
addressbook_table = sqlalchemy.Table("Addressbook", metadata,
    sqlalchemy.Column('Address_Sid', sqlalchemy.Integer, primary_key=True),
    sqlalchemy.Column('FirstName', sqlalchemy.Unicode(40),nullable=False),
    sqlalchemy.Column('LastName', sqlalchemy.Unicode(40),nullable=False),
    sqlalchemy.Column('MaidenLastName', sqlalchemy.Unicode(40)),
    sqlalchemy.Column('Email', sqlalchemy.Unicode(80),nullable=False),
    sqlalchemy.Column('Address', sqlalchemy.Unicode(80),nullable=False),
    sqlalchemy.Column('City', sqlalchemy.Unicode(80),nullable=False),
    sqlalchemy.Column('State', sqlalchemy.String(2),nullable=False),
    sqlalchemy.Column('ZipCode', sqlalchemy.Integer,nullable=False),
    sqlalchemy.Column('DOB', sqlalchemy.Date(),nullable=False),
    sqlalchemy.Column('Gender', sqlalchemy.Unicode(6),nullable=False),
    sqlalchemy.Column('Description', sqlalchemy.Unicode(255),nullable=False),
    sqlalchemy.Column('Created', sqlalchemy.Integer, default=int(time.time())),
    sqlalchemy.Column('Last_UpdatedDate', sqlalchemy.Date, default=datetime.now().date(), onupdate=func.now()),
    )


#This is an empty class that will become our data class
class Addressbook(object):
    def __init__(self, **kw):
        """automatically mapping attributes"""
        for key, value in kw.iteritems():
            setattr(self, key, value)
#Mapping of Table to Python Object Class
mapper(Addressbook, addressbook_table)

# Classes for reflected tables may be defined here, but the table and
# mapping itself must be done in the init_model function.
  • Now lets populate the database.
  • Because we renamed our app name

[app:main]
to
[app:myapp]

to support toscawidget we changed our app name from "main" to "myapp" so will need to call the initiate by adding our new app name.

initialize_myapp_db development.ini#myapp

Saving Form to Database

  • Lets import our new Addressbook model we created in model.py
  • Edit the view.py and where it says:

from .models import (
    DBSession,
    MyModel,
    )
  • Change it to:

from .models import (
    DBSession,
    MyModel,
    Addressbook,
    )
  • Now lets go into our add_view and lets see what data comes back from our controller.
  • Right before the end of if request.method=='POST you can add print request.params, this will show you what came in via the form and in what format.

  • Here is a code to save the the form.If you need a quick guide on how to convert dates see #from_string_to_datetime

import tw2.core as twc
@view_config(route_name='add', renderer='templates/add.pt')
def add_view(request):
    #address_widget = AddressForm(action='saveaddress')
    from myapp.widgets import AddressForm
    #context='myapp.widget.AddressForm'
    if request.method=='POST':
        # First, validate the posted data
        try:
            form_result = AddressForm.validate(request.POST)
        except twc.ValidationError, e:
            return {'widget': e.widget,'project':'myapp'}
        #print request
        print request.params
        print request.params['FirstName']
        #Saving into a database
        address=Addressbook()
        address.FirstName = request.params['FirstName']
        address.LastName = request.params['LastName']
        address.MaidenLastName = request.params['MaidenLastName']
        address.Email = request.params['Email']
        address.Address = request.params['Address']
        address.City = request.params['City']
        address.State = request.params['State']
        address.ZipCode = request.params['ZipCode']
        import datetime,time #inproduction this should go with other import statements
        address.DOB = datetime.datetime(*time.strptime(request.params['DOB'],"%m/%d/%Y")[:3])
        address.Description = request.params['Description']
        address.Gender = request.params['Gender']
        DBSession.add(address)
    #request.session.flash("User Update Successful!")
    return {'widget':AddressForm(),'project':'myapp'}
  • Of course if you have the same field names in database, form fields you could probably auto-save the fields without explicitly typing it out as we did above. On the other hand if you are building a web UI on top of a database knowing how to save each fields is helpful, since the names might not match up.
  • Your record is now saved. How easy is this?

Display Records in Datagrid

  • Now that the records were saved we could redirect the form to a different URL that will show our records instead of returning the same form again. If you were building a form for a high volume data entry personnel you would probably want to display the form again, and display some message "save stressful, add another one", we will show you how to add a flash message later.
  • Lets create a new view to display our data:

from tw2.forms import DataGrid

#The datagrid will consist of the following fields.
addressbook_grid = DataGrid(fields=[
    ('First Name', 'FirstName'),
    ('Last Name', 'LastName'),
    ('Email', 'Email')
])

@view_config(route_name='view', renderer='templates/view.pt')
def view_view(request):
    try:
        data = DBSession.query(Addressbook).all()
    except DBAPIError:
        return Response(conn_err_msg, content_type='text/plain', status_int=500)
    return {'data':data, 'project':'myapp','grid':addressbook_grid}

Add view record grid to html

  • copy the add.pt

cd templates
cp add.pt view.pt
  • Now delete our form widget and add the grid.display(data) to our html template.

 <div id="left" class="align-right">
<div><p tal:content="structure grid.display(data)"></p></div>
</div>

view_toscawidget2_datagrid.png

Pyramid/enterprise

Pyramid for Enterprise

Install Pyramid

aptitude install python-virtualenv

virtualenv --no-site-packages pyramid_env
cd pyramid_env
source ./bin/activate
easy_install pyramid

Create Pyramid Project: myapp

pcreate -s alchemy myapp

Install remaining components

cd myapp
python setup.py develop

[Optional] Checking the first version into revision control of your choosing.

aptitude install bzr
cd myapp
bzr init .
bzr add
bzr commit -m"Initial Import"

Run a test

python setup.py test -q

Check how much code is covered by tests

easy_install nose coverage
nosetests --cover-package=myapp --cover-erase --with-coverage

Populate the database

Replace myapp with your app name.

initialize_myapp_db development.ini

Start the Application

pserve development.ini --reload

Visit http://localhost:6543/

Build application structure

Enable Mako

In order to use Mako as in Pylons, you must specify a template search path in the settings. Edit development.ini:

[app:main]
...
mako.directories = myapp:templates

enable mako with pyramid enable mako html with pyramid

Then in \init\.py add below. This will be needed so that designer can use .html mako template files in his software.

config = Configurator(settings=settings)
config.add_renderer(".html", "pyramid.mako_templating.renderer_factory")

Add first page

We will add new route in. This what tells pyramid program we want localhost/recall

vi __init__.py
#Add below home

Add below home

config.add_route('recall', '/recall')

Edit views.py and add our new page. This tells that anybody that calls localhost/recall should call our route_name called recall in views.py

@view_config(route_name='recall', renderer='recall.html')
def recall(request):
    return {'project':'Myapp'}

Now inside template folder add your new template

cd template
vi recall.html

Add

<html>
 <head>
     <title>${project} Application</title>
 </head>
   <body>
      <h1 class="title">Welcome to <code>${project}</code>, an
       application generated by the <a
       href="http://docs.pylonsproject.org/projects/pyramid/current/"
      >pyramid</a> web application framework using Mako.</h1>
   </body>
 </html>

Visit: http://localhost:6543/recall

pyramid_mako.png

Load mysql-python

First we need to add required package called "mysql-python". Add this to setup.py under required

'mysql-python',

Then do

python setup.py develop

This will install required software.

Autoload table from database

First we need to specify how to connect to database. In development.ini comment out the sqlite and add

#sqlalchemy.url = sqlite:///%(here)s/lm.sqlite
sqlalchemy.url=mysql://usernamehere:secretpassword@localhost/mydatabase_name?charset=utf8

?charset=utf8 is optional.

In models.py add below. This creates a python class called Recall and will autoload table called "recall_db" from the database. We use DeferredReflection because this is a helper function from sqlalchemy which does not require "engine" be to bound and loaded at this time. Allows to keep the model/database code in models.py for clean viewing.

from sqlalchemy.ext.declarative import DeferredReflection

class Recall(DeferredReflection, Base):
    __tablename__ = 'recall_db'

Now right below Base.metadata.bind = engine please add

DeferredReflection.prepare(engine)

This will load the function and autoload the table that we have defined in the models.py

adding url structure /path/bar/foo

My application requires that user accesses the site with url like

http://example.com/recall
http://example.com/recall/2005
http://example.com/recall/2005/Ford
http://example.com/recall/2005/Ford/Mustand

In order to achieve that we will add these lines of code in init.py

config.add_route('recall', '/recall')
config.add_route('recall_year', '/recall/{year}')
config.add_route('recall_make', '/recall/{year}/{make}')
config.add_route('recall_model', '/recall/{year}/{make}/{model}')

The /recall/{year} says user will access the address like http://example.com/recall/1999 where 1999 will become a variable year=1999 and will be sent to view with a name of recall_year

Now lets add the 3 new views in views.py

@view_config(route_name='recall_year', renderer='recall.html')
def recall_year(request):
    return {'project':'Myapp_year'}

@view_config(route_name='recall_make', renderer='recall.html')
def recall_make(request):
    return {'project':'Myapp_make'}

@view_config(route_name='recall_model', renderer='recall.html')
def recall_model(request):
    return {'project':'Myapp_model'}

Now we have used pyramid to "build url structure", autoload mysql table, and defined how our app will flow. Now lets load data, send what we want to template and display in our .html template before graphics person makes it pretty.

DBSession.query, mako and .html templates

DBSession.query

Since we have already defined the database, and autoloaded the table with pyramid lets import it, query it in views.py

Import Recall, and additional sqlalchemy functions

from .models import (
    DBSession,
    MyModel,
    Recall,
    )
from sqlalchemy import and_, desc

Query It

@view_config(route_name='recall', renderer='recall.html')
def recall(request):
    years=DBSession.query(Recall.YEARTXT).group_by(desc(Recall.YEARTXT))
    return {'project':'Recall','years':years}

Template for look in mako, mako loop context

vi templates/recall.html and inside body add

<ul id="years">
% if years:
Click on Year of the vehicle:
  % for year in years:
  <li>
    <span class="actions">
       <a href="${request.route_url('recall_year', year=year[0])}">${year[0]}</a>
    </span>
  </li>
  % endfor
% endif
</ul>

pyramid_mako_forloop_list.png

Because the return value in my case is list (u'2005'), I'm doing year[0] to return u'2005'.

The ${request.route_url('recall_year', year=year[0]) tells system to provide a link to route name in views.py called recall_year and pass parameter year with value of year[0]

query with the parameter passed via url

Now lets move on to recall_make, do the query for make based on year passed in request

Edit views.py

@view_config(route_name='recall_year', renderer='recall.html')
def recall_year(request):
    makes=DBSession.query(Recall.MAKETXT).filter(Recall.YEARTXT==request.matchdict['year']).group_by(Recall.MAKETXT)
    return {'project':'Myapp_year','cyear':request.matchdict['year'],'makes':makes}
  • cyear will be used for "You are browsing XXXX year"
  • makes is the query that lists all makes based on the year supplied

Edit recall.html

<ul id="makes">
% if makes:
Click on Make of the vehicle:
  % for make in makes:
  <li>
    <span class="actions">
       <a href="${request.route_url('recall_make', year=cyear,make=make[0])}">${make[0]}</a>
    </span>
  </li>
  % endfor
% endif
</ul>

Random References

https://www.youtube.com/watch?v=e08kOj2kISU

MyWiki: Pyramid (last edited 2013-08-14 01:51:06 by LukaszSzybalski)