Differences between revisions 59 and 60
Revision 59 as of 2009-07-06 17:36:33
Size: 19227
Comment:
Revision 60 as of 2009-07-06 17:40:44
Size: 19191
Comment:
Deletions are marked like this. Additions are marked like this.
Line 8: Line 8:
Create virtualenv, activate it, and install all the packages:: Create virtualenv, activate it, and install all the packages.
Line 18: Line 18:
 *Create tg2 package using quickstart::  *Create tg2 package using quickstart
Line 22: Line 22:
 *Enter the Project name, and choose if you want identity to be enabled. You should see something like this::  *Enter the Project name, and choose if you want identity to be enabled. You should see something like this.
Line 144: Line 144:
Database == Database ==
Line 147: Line 147:
DB Connection
~~~~~~~~~~~~~


Your Database configuration is located in development.ini file under::
=== DB Connection ===

*Your Database configuration is located in development.ini file under::
{{{
Line 153: Line 152:

You can change this url to your version of database::
}}}
*You can change this url to your version of database::
{{{
Line 158: Line 157:
}}}
Line 161: Line 160:
DB Design
~~~~~~~~~


We need to create our database structure. We do it in model.py First you need to copy the model.template to model.py and insert the code for your table::
=== DB Design ===

*We need to create our database structure. We do it in model.py First you need to copy the model.template to model.py and insert the code for your table::
{{{
Line 167: Line 165:

Inside of it change the foo_table, Class Foo and mapper in a following way::

 
from pylons import config
 #from sqlalchemy import *
 import sqlalchemy
 from sqlalchemy.orm import mapper, relation
 
from addressbook.model import 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().date()),
     )

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

Create Database
~~~~~~~~~~~~~~~

Lets create the tables in our database by running the following command::

 
paster setup-app development.ini

URL
---
}}}
*
Inside of it change the foo_table, Class Foo and mapper in a following way::
{{{
from pylons import config
#from sqlalchemy import *
import sqlalchemy
from sqlalchemy.orm import mapper, relation
from addressbook.model import 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().date()),
    )
#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.
}}}

===
Create Database ===

*
Lets create the tables in our database by running the following command::
{{{
paster setup-app development.ini
}}}

==
URL ==
Line 222: Line 217:

 
class RootController(BaseController):
  """
    The root controller for the insurancecycleweb application.

  All the other controllers and WSGI applications should be mounted on this
    controller. For example::

        panel = ControlPanelController()
  another_app = AnotherWSGIApplication()

    Keep in mind that WSGI applications shouldn't be mounted directly: They
  must be wrapped around with :class:`tg.controllers.WSGIAppController`.

    """

     secc = SecureController()
  admin = Catwalk(model, DBSession)
     error = ErrorController()


     @expose('addressbook.templates.index')
     def index(self):
         return dict(page='index')

     @expose('addressbook.templates.about')
     def about(self):
         return dict(page='about')
     @expose('addressbook.templates.addaddress')
  def addaddress(self,**kw):
         return dict(page='addaddress')
{{{
class RootController(BaseController):
   """
   The root controller for the insurancecycleweb application.

   All the other controllers and WSGI applications should be mounted on this
   controller. For example::

       panel = ControlPanelController()
       another_app = AnotherWSGIApplication()

   Keep in mind that WSGI applications shouldn't be mounted directly: They
   must be wrapped around with :class:`tg.controllers.WSGIAppController`.

   """

    secc = SecureController()
    admin = Catwalk(model, DBSession)
    error = ErrorController()


    @expose('addressbook.templates.index')
    def index(self):
        return dict(page='index')

    @expose('addressbook.templates.about')
    def about(self):
        return dict(page='about')
    @expose('addressbook.templates.addaddress')
    def addaddress(self,**kw):
        return dict(page='addaddress')

}}}
Line 560: Line 556:

Turbpgears2 Application

Install TurboGears2

Create virtualenv, activate it, and install all the packages.

virtualenv --no-site-packages tg2env
cd tg2env/
source bin/activate
easy_install -i http://www.turbogears.org/2.0/downloads/current/index tg.devtools

Quick start a project

  • Create tg2 package using quickstart

paster quickstart
  • Enter the Project name, and choose if you want identity to be enabled. You should see something like this.

Enter project name: addressbook
Enter package name [addressbook]:
Do you need Identity (usernames/passwords) in this project? [no]
Selected and implied templates:
....
  • Done setting it up.

Project Folder Structure

This will create a skeleton of you project. You address book project has the following structure::

 addressbook
 |-- README.txt
 |-- addressbook
 |   |-- __init__.py
 |   |-- config
 |   |   |-- __init__.py
 |   |   |-- app_cfg.py
 |   |   |-- environment.py
 |   |   `-- middleware.py
 |   |-- controllers
 |   |   |-- __init__.py
 |   |   |-- error.py
 |   |   |-- root.py
 |   |   |-- secc.py
 |    |   `-- template.py
 |   |-- i18n
 |   |   `-- ru
 |   |       `-- LC_MESSAGES
 |   |           `-- addressbook.po
 |   |-- lib
 |   |   |-- __init__.py
 |   |   |-- app_globals.py
 |   |   |-- base.py
 |   |   `-- helpers.py
 |   |-- model
 |   |   |-- __init__.py
 |   |   |-- identity.py
 |   |   `-- model.template
 |   |-- public
 |   |   |-- css
 |   |   |   `-- style.css
 |   |   |-- favicon.ico
 |   |   `-- images
 |   |       |-- error.png
 |   |       |-- grad_blue_7x80.png
 |   |       |-- header_inner2.png
 |   |       |-- info.png
 |   |       |-- logo.gif
 |   |       |-- logo.png
 |   |       |-- ok.png
 |   |       |-- star.png
 |   |       |-- strype2.png
 |   |       |-- tg2_04.gif
 |   |       |-- tg_under_the_hood.png
 |   |       `-- under_the_hood_blue.png
 |   |-- templates
 |   |   |-- __init__.py
 |   |   |-- about.html
 |   |   |-- debug.html
 |   |   |-- footer.html
 |   |   |-- header.html
 |   |   |-- index.html
 |   |   |-- login.html
 |   |   |-- master.html
 |   |   `-- sidebars.html
 |   |-- tests
 |   |   |-- __init__.py
 |   |   |-- functional
 |   |   |   `-- __init__.py
 |   |   `-- test_models.py
 |   `-- websetup.py
 |-- addressbook.egg-info
 |   |-- PKG-INFO
 |   |-- SOURCES.txt
 |   |-- dependency_links.txt
 |   |-- entry_points.txt
 |   |-- paste_deploy_config.ini_tmpl
 |   |-- paster_plugins.txt
 |   |-- requires.txt
 |   `-- top_level.txt
 |-- development.ini
 |-- ez_setup
 |   |-- README.txt
 |   `-- __init__.py
 |-- setup.cfg
 |-- setup.py
 |-- setup.pyc
 |-- test.ini

Inside your addressbook folder you find another addressbook folder and inside of it you will find::

   Config -Here you configure your application to the requirements you might have.
   Controller - Here your manipulate data, setup your urls, define what gets passed to what, your whole application flow etc.
   Model - Here you define all your database models
   Public - Here you keep all your static files, your css modules, your images.
   Templates - Here you store your genshi template files.
   i18n - Here you define your international settings.
   lib-
   tests - Here you define your nose test that you can write for your app.

Start the app

To start the project we will use the following command::

   paster serve --reload development.ini

   - paster provides a simple mechanism for running a TurboGears projects.
   - server tells paster to start the webserver
   --reload tells paster to reload the pages if one of the files have changed.
   -development.ini is a general configuration file for turbogears2

You should be able to open your favorite browser and see the initial turobgears website. Visit this site: http://localhost:8080/ You should see you site running.

inline:tg2welcome2.png

Database


DB Connection

*Your Database configuration is located in development.ini file under::

 sqlalchemy.url = sqlite:///%(here)s/devdata.db

*You can change this url to your version of database::

 sqlalchemy.url=postgres://username:password:port@hostname/databasename
 sqlalchemy.url=mysql://username:password@hostname:port/databasename

When you have your database connection ready we are ready to design simple database.

DB Design

*We need to create our database structure. We do it in model.py First you need to copy the model.template to model.py and insert the code for your table::

 cp mode.tempalte model.py

*Inside of it change the foo_table, Class Foo and mapper in a following way::

from pylons import config
#from sqlalchemy import *
import sqlalchemy
from sqlalchemy.orm import mapper, relation
from addressbook.model import 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().date()),
    )
#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.

Create Database

*Lets create the tables in our database by running the following command::

paster setup-app development.ini

URL

Lets create a url where we will add addresses to our addressbook. Lets make it http://localhost:8080/addaddress We need to create a function in a root.py in controller folder. Our new function will look just like index but with a different name. See below code.

class RootController(BaseController):
   """
   The root controller for the insurancecycleweb application.

   All the other controllers and WSGI applications should be mounted on this
   controller. For example::

       panel = ControlPanelController()
       another_app = AnotherWSGIApplication()

   Keep in mind that WSGI applications shouldn't be mounted directly: They
   must be wrapped around with :class:`tg.controllers.WSGIAppController`.

   """

    secc = SecureController()
    admin = Catwalk(model, DBSession)
    error = ErrorController()


    @expose('addressbook.templates.index')
    def index(self):
        return dict(page='index')

    @expose('addressbook.templates.about')
    def about(self):
        return dict(page='about')
    @expose('addressbook.templates.addaddress')
    def addaddress(self,**kw):
        return dict(page='addaddress')

@expose

  • This line defines which template we will use for this url. The template we use: addressbook/templates/addaddress.html

def addaddress(self):

return dict(page='addaddress')

  • The dict returns data to the template in a python dictionary structure 'key=value'. We returned variable called page.

Above will set your url. Now you need to create/modify template html to show what you want and do some code in address to do what you want it to do with the data.

Template ~~~~~~~~ Now that we have defined which template we will use, lets create the actual html file and start our app to make sure everything is working. Lets create a tempalate real quick::

  • cd addressbook/templates/ cp index.html addaddress.html

Lets start the app and see if our url works::

  • paster serve --reload development.ini

Ok. It worked. Lets create a widget aka the form where will will provide information and submit it.

Widget ~~~~~~ Lets create a widget form that will be asking for our addresses. Add the following to the top of our controller/root.py. For now we will be adding it to root.py but as you get more familiar you will be putting these in a seperate file::

It will send the results to "saveaddress" which we have to create in root.py. But for now lets create our template.

Template ~~~~~~~~

Lets edit our template and add our form in there. We don't have to return the form as dictionary we can set pylons.c.myformname and use template_context.myformname in a template. Lets do just that. Edit our addressbook/templates/addaddress.html and add the following line
${tmpl_context.addressbook_form()}

Above will display our form in the template. The addressbook_form is eqivelent to pylons.c.addressbook_form in a root.py. The template will look like:

.. image:: tg2address_form.png

Controller ~~~~~~~~~~ Lets modify our root.py again and change the def addaddress and add def saveaddress functions. First make sure you have the following imported at the top of root.py::

  • from addressbook.lib.base import BaseController from tg import expose, flash from pylons.i18n import ugettext as _ from tg import redirect, validate import pylons from addressbook.model import DBSession, metadata from addressbook.model import Address

Our new add and save function should look like this::

  • @expose('addressbook.templates.addaddress') def addaddress(self,**kw):
    • """Form to add new record""" flash("Hello Addressbook!") # Passing the form in the return dict is no longer kosher, you can # set pylons.c.form instead and use c.form in your template # (remember to 'import pylons' too) pylons.c.address_form = address_form return dict(page='addaddress')
    @validate(address_form, error_handler=addaddress) @expose() def saveaddress(self, **kw):
    • """Save it to the database""" address = Addressbook()

      address.FirstName = kw['FirstName'] address.LastName = kw['LastName'] address.MaidenLastName = kw['MaidenLastName'] address.Email = kw['Email'] address.Address = kw['Address'] address.City = kw['City'] address.State = kw['State'] address.ZipCode = kw['ZipCode'] address.DOB = kw['DOB'] address.Description = kw['Description'] address.Gender = kw['Gender'] DBSession.save(address) DBSession.commit() flash("Successfully saved.") raise redirect("addaddress")

    @expose() def addresses(self, **kw):
    • """List our addressbook""" return dict()

The list function will show our addressbook.

DataGrid ~~~~~~~~ Now we need to display our data. We will do that with datagrid from toscawidget. First thing we need to do is to import proper programs. Then we will define what will be displayed. Add the above lines above our root class::

  • from tw.forms.datagrid import DataGrid

    address_grid = [('First Name','FirstName'),

    • ('Last Name','LastName'), ('Address','Address'), ('City','City'), ('State','State'), ('Zip Code','ZipCode'), ('Gender','Gender')]

Then Lets modify our list function to be something like this::

  • @expose('addressbook.templates.addresses')
    • def addresses(self, **kw):
      • """This function will display our data in a grid""" all=DBSession.query(Addressbook).all()

        pylons.c.address_grid=DataGrid(fields = address_grid) pylons.c.address_data=DBSession.query(Addressbook).all() return dict(page='addresses')

The address_grid will render the data, while address_data contains actual data. Now lets create list.html that will display our data.::

  • cd addressbook/tempalates cp index.html addresses.html

Add the following somewhere in a code::

  • <p>${tmpl_context.address_grid(tmpl_context.address_data)} </p>

Now start the app again and go to http://localhost:8080/addresses

The data grid looks like this:

.. image:: toscawidget_data_grid.png

Testing


Testing in turbogears2 is done by nosetests. You already have a folder designed for what you need to do. You can test your application for errors in few simple steps. The test files are located in addressbook/tests/

Run the tests using the following command::

  • python setup.py nosetests

You should see tests being run and at the end you will find::


  • Ran 0 tests in 0.045s OK

Index ~~~~~

You can add more tests to file yourapp/tests/functional/. We already have a test that checks for /index page. Lets create a another test for our application::

  • cp addressbook/tests/functional/test_root.py addressbook/tests/functional/test_addressbook.py

Inside change the first test and delete the rest::

  • class TestPageData(TestController):

    • def test_addressbook(self):
      • resp = self.app.get('/addaddress') ns = resp.namespace assert 'page' in ns assert ns['page'] == 'addaddress'

Save and run the tests again::

  • python setup.py nosetests

You will see::


  • Ran 18 tests in 1.981s OK

Done. You have created your first test. With the above code you can make sure each page is returning before you upload to production server.

Addresses ~~~~~~~~~ Now add the second test case to our TestPageData class. The tests case start with test_ ::

  • def test_addresses_page_data(self):
    • resp = self.app.get('/addresses') ns = resp.namespace #See if there is a page return assert 'page' in ns #See if the page return is called addresses. assert ns['page'] == 'addresses' #Test if the template is there. assert resp.template_name == 'addressbook.templates.addresses'

Adding Functioinality


Verify Email (Chained validator) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In order to verify email address, we will create 2 fields where we ask for email, and we will verify they are the same::

Then before you initialte the widget do::

  • #Chained Validator from formencode import Schema

    from formencode.validators import FieldsMatch

    emailValidator = Schema(chained_validators=(FieldsMatch('Email',

    • 'verify_email',
    • messages={'invalidNoMatch':"Emails do not match"}),))

Then call your instance and pass the extra validator::

  • #then, we create an instance of this form

    address_form = AddressForm("address_form", action='saveaddress', validator=emailValidator)

You can easly do the same with password validation

Send confirmation email ~~~~~~~~~~~~~~~~~~~~~~~

1. Install turbomail 3.0dev or higher. 2. Add this to yourpackage.lib.app_globals.py::

  • def init(self):

    • # ... from turbomail.adapters import tm_pylons tm_pylons.start_extension()

3. In the [default] section of development/deployment.ini add::

  • mail.on = true mail.manager = immediate mail.brand = mail.transport = smtp mail.smtp.server = your.mail.server mail.smtp.debug = mail.encoding = utf-8 mail.utf8qp.on = true

4. In your root.py do::

  • import turbomail

    message=turbomail.Message("from@example.com,'to@example.com','Thank you for Registering') message.plain="Hello this is a test" turbomail.send(message)

Send Confirmation link ~~~~~~~~~~~~~~~~~~~~~~

If you want to send a confirmation link you need to add a filed to your model table, generate the confirmation number, and email it to registering user.

To generate confirmation number you can use::

  • import uuid address.email_confirmation_uuid = str(uuid.uuid4())

To create a confirmation function that will accept that url do::

  • @expose() def confirmaddress(self, email_confirmation_uuid,**kw):
    • #print kw #print email_confirmation_uuid record=DBSession.query(model.Addressbook).filter(model.Addressbook.email_confirmation_uuid==email_confirmation_uuid).one() record.email_confirmed=True flash("Email Address Confirmed Successfully.") raise redirect("/addaddress")

MyWiki: TurboGears2/Addressbook (last edited 2011-02-01 22:00:14 by LukaszSzybalski)