Other Languages: Spanish, Polish

Turbogears2 documentation is located here: http://lucasmanual.com/mywiki/TurboGears2 PDF Version: http://lucasmanual.com/pdf/TurboGears2.pdf

First Web Application in Turbogears2

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.1/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 authentication and authorization in this project? [yes]
Selected and implied templates:
....
  • Done setting it up.

Folder Structure

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

addressbook/
|-- MANIFEST.in
|-- README.txt
|-- addressbook
|   |-- __init__.py
|   |-- config
|   |   |-- __init__.py
|   |   |-- app_cfg.py
|   |   |-- deployment.ini_tmpl
|   |   |-- environment.py
|   |   `-- middleware.py
|   |-- controllers
|   |   |-- __init__.py
|   |   |-- controller.template
|   |   |-- error.py
|   |   |-- root.py
|   |   |-- secure.py
|   |   `-- template.py
|   |-- i18n
|   |   `-- ru
|   |       `-- LC_MESSAGES
|   |           `-- addressbook.po
|   |-- lib
|   |   |-- __init__.py
|   |   |-- app_globals.py
|   |   |-- base.py
|   |   `-- helpers.py
|   |-- model
|   |   |-- __init__.py
|   |   |-- auth.py
|   |   `-- model.template
|   |-- public
|   |   |-- css
|   |   |   `-- style.css
|   |   |-- favicon.ico
|   |   `-- images
|   |       |-- contentbg.png
|   |       |-- error.png
|   |       |-- header_inner2.png
|   |       |-- headerbg.png
|   |       |-- info.png
|   |       |-- inputbg.png
|   |       |-- loginbg.png
|   |       |-- loginbottombg.png
|   |       |-- loginheader-right.png
|   |       |-- menu-item-actibg-first.png
|   |       |-- menu-item-actibg.png
|   |       |-- menu-item-border.png
|   |       |-- menubg.png
|   |       |-- ok.png
|   |       |-- pagebg.png
|   |       |-- star.png
|   |       |-- strype2.png
|   |       |-- under_the_hood_blue.png
|   |       `-- warning.png
|   |-- templates
|   |   |-- __init__.py
|   |   |-- about.html
|   |   |-- authentication.html
|   |   |-- debug.html
|   |   |-- error.html
|   |   |-- footer.html
|   |   |-- header.html
|   |   |-- index.html
|   |   |-- login.html
|   |   |-- master.html
|   |   `-- sidebars.html
|   |-- tests
|   |   |-- __init__.py
|   |   |-- functional
|   |   |   |-- __init__.py
|   |   |   |-- test_authentication.py
|   |   |   `-- test_root.py
|   |   `-- models
|   |       |-- __init__.py
|   |       `-- test_auth.py
|   `-- websetup.py
|-- addressbook.egg-info
|   |-- PKG-INFO
|   |-- SOURCES.txt
|   |-- dependency_links.txt
|   |-- entry_points.txt
|   |-- paster_plugins.txt
|   |-- requires.txt
|   `-- top_level.txt
|-- development.ini
|-- ez_setup
|   |-- README.txt
|   `-- __init__.py
|-- setup.cfg
|-- setup.py
|-- setup.pyc
`-- test.ini

http://turbogears.org/2.1/docs/_images/tg2_files.jpg

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.
   Controllers - 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- This is where you can attach plugins like turbomail, and configure global settings for you application, and setup helpers for templates. 
   tests - Here you define your nose test that you can write for your app.

Start the app

  • If this is your first time running the app you need to run setup.py. You should be in a folder that has the development.ini and setup.py

cd addressbook
python setup.py develop

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.

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

cd addressbook/model
cp model.template 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 controllers 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 addressbook 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):``
  This adds our url http://localhost:8080/addaddress
``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 template 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. You should see the "Now Viewing:addaddress" This is using the page variable that we passed to the template. Lets create a widget aka the form where we 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:

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


class AddressForm(TableForm):
    # This WidgetsList is just a container
    class fields(WidgetsList):
        FirstName = TextField(validator=NotEmpty)
        LastName = TextField(validator=NotEmpty)
        MaidenLastName = TextField(validator=String)
        Email = TextField(validator=Email)
        Address = TextField(validator=NotEmpty)
        City = TextField(validator=NotEmpty)
        State = TextField(validator=NotEmpty)
        #Or you could do:
        #StateChoices = (("IL"),
        #                 ("IN"),
        #                 ("MS"),
        #                )
        #State = SingleSelectField(options=StateChoices, validator=NotEmpty)
        ZipCode = TextField(size=6, validator=PostalCode())
        DOB = CalendarDatePicker(validator=DateConverter())
        GenderChoices = (("Female"),
                         ("Male"),
                        )
        Gender = SingleSelectField(options=GenderChoices)
        Description = TextArea(attrs=dict(rows=3, cols=25))

#then, we create an instance of this form
address_form = AddressForm("address_form", action='saveaddress')

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.tmpl_context.myformname and use tmpl_context.myformname in a template. Lets do just that. Edit our addressbook/templates/addaddress.html and add the following line:

${tmpl_context.address_form()}

Above will display our form in the template. The address_form is equivalent to pylons.tmpl_context.address_form in a root.py. The template will look like:

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 Addressbook

Our new change modifies addaddress and adds the save function which should look like this:

    @expose('addressbook.templates.addaddress')
    def addaddress(self,**kw):
        """Form to add new record"""
        # Flash updates the status on the page. Try modifying it to something you want.
        flash("Hello Addressbook!")
        # This is where we attach the form to pylons.tmpl_context.something and whatever you attach will be available in the template as tmpl_context.something
        # Passing the form in the return dict is no longer required, you can
        # set pylons.tmpl_context.form instead and use c.form in your template
        pylons.tmpl_context.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.add(address)
        DBSession.commit()
        flash("Successfully saved.")
        raise redirect("addaddress")
    @expose()
    def addresses(self, **kw):
        """List our addressbook"""
        return dict()

The addaddress will now display our widget in out template and when you click on Save it will send the data to saveaddress. Saveaddress will save the data into database. The only thing left now is to show the data that is in 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 addresses 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.tmpl_context.address_grid=DataGrid(fields = address_grid)
        pylons.tmpl_context.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 addresses.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:

toscawidget_data_grid.png

Testing/UnitTest

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

Test 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'

Functionality

Email Functionality

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::

    Email = TextField(validator=Email)
    verify_email = TextField(validator=Email)

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)

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")

Authorization

Show/Allow based on page name

  • If you want to show a menu for example only on certain pages you could check the page name in the following way:

<span py:choose="">
<span py:when="defined('page') and 'someword' in page"> Put some code here </span>
<span py:otherwise=""><a href="${tg.url('/')}">Home</a> Back to your Quickstart Home page </span>
</span>
  • Or

<span py:choose="">
<span py:when="defined('page') and page=='IT'"> Put some code here </span>
<span py:otherwise=""><a href="${tg.url('/')}">Home</a> Back to your Quickstart Home page </span>
</span>

Show/Allow based on authority and permissions

With groups and roles, we get:

    USER:
    - username
    - password
    - belongs to group A
    - also belongs to groups B, C
    - has roles 'author', 'editor', 'admin'

    GROUP or ROLE:
    - group/role ID
    - can_view_x?
    - can_view_y?
    - can_modify_y?

\ *See websetup.py file for details on how to add permissions. The file contains code like:

    manager = model.User()
    manager.user_name = u'manager'
    manager.display_name = u'Example manager'
    manager.email_address = u'manager@somedomain.com'
    manager.password = u'managepass'

    model.DBSession.add(manager)

Database Tricks

Autoload Database

  • To autoload the database you need to do something similar to this.
  • We will autoload "recall_db".
  • Edit init.py file in myapp/model/init

Add these import statements

from sqlalchemy import Table
from sqlalchemy.orm import mapper, relation

Create a python class before the init_model(engine) class.

class Recall(object):
    def __init__(self, **kw):
        """automatically mapping attributes"""
        for key, value in kw.iteritems():
            setattr(self, key, value)

Inside the def init_model(engine) add the following at the end:

    recall_table = Table('recall_db', metadata, autoload=True,autoload_with=engine)

    mapper(Recall, recall_table,primary_key=[recall_table.c.RECORD_ID])
  1. The class Recall creates a python object class that we will map our database. Its important for it to be before and outside of init_model. As long as its outside of init_model then tg2 admin interface will be able to see it and will mount it in the /admin interface.

  2. recall_table is a table definition that tells our program to use recall_db, autoload it with the engine we registered, and use the metadata that we setup.

  3. mapper is mapping our table with python object. Since we didn't have a primary key in the table we told the mapper to use one of the columns.

Done. Your table is autoloaded now and you can see it in admin interface.

Rum Admin Interface

  • You can replace catwalk (default tg2 admin interface with rum interface. You do that by installing a helper app called tgrum in your virtual enviroment:

easy_install tgrum
  • Now go to root.py and add the following before root controller and replace admin= with the new line.:

from tgrum import RumAlchemyController
from tg import config

# Create a prediacte to protect the RumAlchemy admin
is_manager = predicates.has_permission(
    'manage',
    msg=_('Only for people with the "manage" permission')
    )

And change the admin to:

class RootController(BaseController):
    admin = RumAlchemyController(model,
        is_manager,
        template_path=config['paths']['templates'][0],
        render_flash=False,
        )

Done. Now go to admin interface and you should have rum admin interface running. You can add users, change users password, etc. tgrum_admin.png

See http://python-rum.org/wiki/TgRum and http://docs.python-rum.org/tip/user/deploy.html#running-rum-inside-turbogears-2 for more details.

Sqlalchemy

Connection

Here is the example mysql connection. Set that up in development.ini and pick what database you want to use.

sqlalchemy.url=mysql://user:pass@localhost/mydatabase
or 
sqlalchemy.url=mysql://user:pass@localhost/mydatabase?charset=utf8

Query, group by

In this example I query the Recall Table that I defined in model code. This one is autoloaded.

        if year=='':
            pylons.c.years=DBSession.query(Recall.YEARTXT).group_by(Recall.YEARTXT)
        elif make=='':
            pylons.c.makes=DBSession.query(Recall.MAKETXT).filter(Recall.YEARTXT==year).group_by(Recall.MAKETXT)
        elif model=='':
            pylons.c.models=DBSession.query(Recall.MODELTXT).filter(and_(Recall.YEARTXT==year,Recall.MAKETXT==make)).group_by(Recall.MODELTXT)
        else:
            pylons.c.data=DBSession.query(Recall).filter(and_(and_(Recall.YEARTXT==year,Recall.MAKETXT==make),Recall.MODELTXT==model))

Keywords: Geo, OpenLayers, turbogears, turbogears2, Openstreetmap,mapnik,postgis, TMS,WMS,GML, layers, maps, googlemaps,

Maps with Turbogears

Install

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

Create project

paster quickstart geotry
  • Run development install

python setup.py develop
  • Add tg.ext.geo to

vi geotry.egg-info/paster_plugins.txt

layers.ini

  • Add layers.ini. This file contains information about your geographic table that you have in your database.

[zipcodes]            #Name of the page/section
singular=zipcode      #Singular name
plural=zipcodes       #Plural name
db=gis                #Name of the database in postgreGIS
table=zipcodes        #Table name that holds geographic infomration
epsg=4326             #Projection, "European petroleum survey group" Standard projection on most maps
units=degrees         #Units
geomcolumn=the_geom   #The geographic column that holds shapes of geographic data. Usually its called the_geom
idcolumn=Integer:gid  #ID
  • Run command that creates geo functions. These function will create python code that will let you edit, view and delete geographic data.

paster geo-layer zipcodearea
  • This creates files zipcodearea.py in controller and model. This file would contain the controller code for retrieving the zipcodes, posting new / edit zipcodes and deleting zipcodes

-- controllers
|   |   |-- __init__.py
|   |   |-- error.py
|   |   |-- root.py
|   |   |-- secc.py
|   |   |-- template.py
|   |   `-- zipcodearea.py
  • What this does is to create a code for you that will output one layer that should be GML with url=zipcodesarea. These zipcodes would be fetched by GML by calling http://localhost:8080/zipcodearea. You can try pointing your browser to this url and you should get a GeoJSON formatted zipcode data.

  • GML is Geography Markup language it is useful in getting geographic data in vector form.

Controllers

  • Add this line to a root.py

vi geotry/controllers/root.py
  • Below all import statements:

from geotry.controllers.zipcodearea import ZipcodeareaController


class RootController(BaseController):
    #admin = DBMechanic(SAProvider(metadata), '/admin')
    zipcodearea=ZipcodeareaController()

development.ini

  • Add the postgre sql username and pass to your development.ini

sqlalchemy.url=postgres://myuser:mypass@localhost/gis

run app

paster serve --reload development.ini

Generate layers and maps

  • Remember the terms. These are needed for the opnelayers widget.
  • GML is Geography Markup language it is useful in getting geographic data in vector form.
  • WMS is web map service it is useful for getitng geographic maps in raster form (form of an image like gif or png) You need to run WMS server to get data in this type of format.
  • TMS is Tile Map Service provides access to cartographic maps of geo-referenced data, not direct access to the data itself. This document standardizes the way in which map tiles are requested by clients, and the ways that servers describe their holdings.
  • URL Scheme. mapnik can generate png files for each area. Depending on the zoom level the final tiles are stored in folders http://tah.openstreetmap.org/Tiles/tile/10/262/380.png . This file is in 10/262/380.png. Mapnik uses google like url scheme and therefore if you want to use it with openlayers you need to create custom get_url function, but that was done already. OpenStreetMap.js. If you incude this file you can add a layer of javascript that looks like: OpenLayers.Layer.OSM.Mapnik("Mapnik") This will display data from openstreetmap.org. If you want to modify the data source you will have to edit OpenStreetMap.js and point to your new url.

  • See available layers in tw.openlayers

Mylayers

  • Import necessary files.
  • Add the following code right below all the import statements:

from tw.api import WidgetsList, js_symbol
from tw.openlayers import Map, GML, WMS, OMSMapnik,OMSRenderer, LayerSwitcher, OverviewMap, \
                    MouseToolbar, MousePosition, PanZoomBar, \
                    Permalink, SelectFeature
  • Then add this class which will define your layers.

class MyLayers(WidgetsList):
    ol = WMS(name="OpenLayers WMS",
        url=["http://labs.metacarta.com/wms/vmap0"],
        options = {'layers':'basic'})
    nasa = WMS(name="NASA Global Mosaic",
        url=['http://t1.hypercube.telascience.org/cgi-bin/landsat7'],
        options={'layers': 'landsat7'})
    mapnik = OSMMapnik(name='MAPNIK')
    transportation = GML(name="Transportation", url="countries",
        options = {
            "format": js_symbol(" OpenLayers.Format.GeoJSON"),
            "isBaseLayer": False,
            "projection": js_symbol(' new OpenLayers.Projection("EPSG:4326")')
        })
  • Add the options for display:

class MyControls(WidgetsList):
    ls = LayerSwitcher()
    ovm = OverviewMap()
    mtb = MouseToolbar()
    mp = MousePosition()
    pzb = PanZoomBar()
    pl = Permalink()
    sf = SelectFeature(layer_name="Transportation", options={
            "hover": True,
            "onSelect": js_symbol("show_info"),
            "onUnselect": js_symbol("erase_info")})
  • Generate the map

mymap = Map(id="map", layers=MyLayers(), controls=MyControls(),
                            center=(15,0), zoom=3)
  • Add the map to a index function

@expose('geogrid.templates.index')
    def index(self):
        pylons.c.map = mymap
        return dict(page='index')

Layer in wms is usually a url part http://localhost/mymaps/basic/someWMSservice
layers:basic should get you a basic map using VMAP0 data refer to WMS way of serving maps (http://localhost/mymaps/basic/someWMSservice)
but layers:landsat7 should show up landsat imagery (http://localhost/mymaps/landsat7/someWMSservice)

http://www.alistapart.com/d/takecontrolofyourmaps/webmapstack.png http://tah.openstreetmap.org/Tiles/tile/10/262/380.png

References

  1. http://www.turbogears.org/2.0/docs/main/Extensions/Geo/MapFishTutorial.html (Main source)

  2. http://wiki.openstreetmap.org/index.php/OpenLayers_Simple_Example (Stand alone java script. Your final TG could should have similar characteristics)

  3. http://geo.turbogears.org/ (Examples of maps in turbogears2 apps)

  4. http://www2.computer.org/portal/c/document_library/get_file?folderId=144965&name=DLFE-4309.pdf (IEEE Review)

  5. This document was made in Chicago IL.

Development Process

SubController/Controller Class

  • If you know it will take a lot of code you can start working in a seperate file outside of root.py. We will create IT page. :

cd myapp/controllers/
cp controller.tempalate it.py
  • Edit it.py and see what is in it.
  • Now Edit root.py and add the following inside your Base controller.

    from myapp.controllers.it import SampleController
    it = SampleController()

Deployment

Create production.ini file by running:

paster make-config myapp production.ini

and follow these instructions.

http://turbogears.org/2.0/docs/main/Deployment/modwsgi+virtualenv.html

Others

URLAliasing

  1. Url Aliases "Assign some nice, user- and seo-friendly URLs (like /company/about) to not so nice ones (like /node/13) - and, well, have an ability to access sub-urls in a nice way, too (like /company/about/edit in the example above). An additional behaviour is to redirect the user to a 'nice' url (/company/about) when he accesses the original one (/node/13)"

script_name

  1. script_name "The initial portion of the request URL's "path" that corresponds to the application object, so that the application knows its virtual "location". This may be an empty string, if the application corresponds to the "root" of the server."

Open ID and Turbogears2

  1. Open ID and Turbogears2

TG2 Paginate

  1. http://rapidprototype.ch/bg2docs/tg2pagination.html

Multiple databases in TurboGears 2.0

  1. http://blog.curiasolutions.com/?p=87

Multiple File Upload

  1. https://user.sitepen.com/~mwilcox/dojotoolkit/demos/uploader/demo.html?forceNoFlash

  2. http://www.sitepen.com/blog/2008/09/02/the-dojo-toolkit-multi-file-uploader/

  3. http://docs.dojocampus.org/dojox/form/FileUploader

  4. http://mwilcox.dojotoolkit.org/dtk/dojox/form/tests/test_FileUploader.html?forceNoFlash

Production sample app

  1. http://monroe-threebean.rhcloud.com/graph?from_date=01%2F01%2F1989&to_date=10%2F13%2F2011

FAQ

To get a one of the columns to display a link in datagrid from toscawidget, Do

from genshi import Markup
def get_link(item):
    return Markup("""<a href="/addresshere/%s">%s</a>""" % (item.customerid,item.CustomerID))

address_grid = [('CustomerID',get_link),
........
]

Not directly via config, no.

Your post pointed me in the right direction. I tried your idea, but
the SessionMiddleware is needed (they call it core middleware  for a
reason). Other code expects it to be there, so it can't be just
removed. Instead, I went one level deeper and modified the
SessionMiddleware itself to NOT set a cookie. I didn't want to modify
the beaker package itself of course (bad practice). I could in theory
subclass the beaker SessionMiddleware and then sublass AppConfig to
add my custom SessionMiddleware, but it becomes a little cumbersome.
So, I ended up just replacing the __call__the method of the original
beaker SessionMiddleware in my AppCfg.py file. The commented lines
bellow are the lines that set the cookie:


### Start code #####
from beaker.middleware import SessionMiddleware
from beaker.session import SessionObject
def custom_session_middleware__call__(self, environ, start_response):
   session = SessionObject(environ, **self.options)
   if environ.get('paste.registry'):
       if environ['paste.registry'].reglist:
           environ['paste.registry'].register(self.session, session)
   environ[self.environ_key] = session
   environ['beaker.get_session'] = self._get_session

   def session_start_response(status, headers, exc_info = None):
       #if session.accessed():
       #    session.persist()
       #    if session.__dict__['_headers']['set_cookie']:
       #        cookie = session.__dict__['_headers']['cookie_out']
       #        if cookie:
       #            headers.append(('Set-cookie', cookie))
       return start_response(status, headers, exc_info)
   return self.wrap_app(environ, session_start_response)

SessionMiddleware.__call__ = custom_session_middleware__call__
### End code #####

mounting test-controllers/getting root-controller instance

Assuming the paster-stuff is bootstrapped through code like

  here_dir = os.path.dirname(os.path.abspath(ableton.__file__))
  conf_dir = os.path.dirname(here_dir)
  wsgiapp = loadapp('config:test.ini', relative_to=conf_dir)

you then can do it simply like this (inside a function/method!!)

  import myproject.controllers.root as root
  root.RootController.mountpoint = TestController()

Then you can access the controller through the usual

  self.app.get("/mountpoint/action")

Of course mounting of whole controller hierarchies is perfectly fine.

So I create a function in our base-test-class that allows to register a passed
controller for a given mountpoint. Voila, greatness ensues.

retrieve user identity

  • You can get the current logged in identity by grabbing it from the

environ:

identity = request.environ.get('repoze.who.identity')
user = identity.user
group_names = identity.groups
  • Lots of folks do something like this in their call method of their

BaseController:

tmpl_context.identity = request.environ.get('repoze.who.identity')
  • In your template you could do the following:

    <li py:if="tg.predicates.has_permission('can_manage_links')"><a href="${tg.url('/links/')}">Postal Links Mgmt</a></li>
    <li py:if="tg.predicates.has_any_permission('can_create_events', 'can_edit_events', 'can_delete_events')">
    <a href="${tg.url('/events/')}">Events Mgmt</a></li>
    <li py:if="tg.predicates.has_permission('can_see_ban_reason')"><a href="${tg.url('/usermgt/')}">User Mgmt</a></li>
  • OR

<span py:if="tg.predicates.in_group('banned')">

login_handler and userid

Problem:

I am trying to implement post logon url so that the user is directed
to an appropriate page after they log in.  How do I specify which page
the user will get directed to based on their username and password.
The users permissions must be used to determine the proper page

Solution:

Have a look at the post_login method of RootController. Change the
last few lines to

userid = request.identity['repoze.who.userid']
if came_from.decode() == '/':
   came_from = tg.url(get_userpage(userid))
flash(....)
redirect(came_from)

get_userpage should contain your page access logic and return a url
string based on userid

ip address of the user/visitor

in paster, the IP isn't passed in the environment:

       import os
       print os.environ['REMOTEHOST']

in wsgi, the IP is passed in the environment:

       import os
       print os.environ['REMOTE_ADDR']

With webob, I believe you can access the request object directly and
get the remote IP address:

http://turbogears.org/2.0/docs/modules/thirdparty/webob.html

Custom Tg2 Index for your product

You can achieve this by creating a custom index-page that you restrict
easy_install to use when fetching eggs to install. For that, put a line like

[easy_install]
find_links = http://eggbasket.office.ableton.com/versionset/81
allow_hosts = eggbasket.office.ableton.com

into setup.cfg parallel to setup.py in your project.

We do so by having a customizied EggBasket that supports a thing we call
versionsets. And a commandline-tool called Easterbunny that uploads a
virtualenv as whole, making it one of those versionsets.

This works extremely smooth for us for a year now.

Other options also exist, zc.buildout works also AFAIK, but I never toyed
around with that.

semi-dynamic tw forms

  • If you have a list of fields you want to create a widget for you can create it this way:

some_list=['FirstName','LastName','Address']

my_widgets=[]
for k in some_list:
            my_widgets.append(TextField(k,validator=NotEmpty))
        my_form=TableForm('my_form',action='save',children=my_widgets)
  • First line creates a list object.
  • The for loop goes through the list and creates Textfield. Note that names cannot have a space nor special characters like '-', etc.
  • my_form creates the actual form that you can display.

Serving files and its mime type

If 

"""
   @expose(content_type=CUSTOM_CONTENT_TYPE)
   def image(self, id):
         image = Image.get(id)
         pylons.request['Content-Type'] = image.mime_type
         return image.data
"""

So, instead of "image.mime_type", "mimetyes.guess_type(filename)[0]".

General Errors

Addition modules required

ImportError: No module named MySQLdb

Fixed with:

easy_install MySQL-python

Debian and mysql-python error

easy_install mysql-python

Searching for mysql-python

Reading http://pypi.python.org/simple/mysql-python/

Reading http://sourceforge.net/projects/mysql-python

Best match: MySQL-python 1.2.3c1

Downloading http://pypi.python.org/packages/source/M/MySQL-python/MySQL-python-1.2.3c1.tar.gz#md5=310dd856e439d070b59ece6dd7a0734d

Processing MySQL-python-1.2.3c1.tar.gz

Running MySQL-python-1.2.3c1/setup.py -q bdist_egg --dist-dir /tmp/easy_install-6mOTZy/MySQL-python-1.2.3c1/egg-dist-tmp-Lh1qcu

sh: mysql_config: command not found

Traceback (most recent call last):

  File "/usr/local/pythonenv/tg2envb7/bin/easy_install", line 8, in <module>

    load_entry_point('setuptools==0.6c8', 'console_scripts', 'easy_install')()

  File "build/bdist.linux-i686/egg/setuptools/command/easy_install.py", line 1671, in main

  File "build/bdist.linux-i686/egg/setuptools/command/easy_install.py", line 1659, in with_ei_usage

  File "build/bdist.linux-i686/egg/setuptools/command/easy_install.py", line 1675, in <lambda>

  File "/usr/lib/python2.5/distutils/core.py", line 151, in setup

    dist.run_commands()

  File "/usr/lib/python2.5/distutils/dist.py", line 974, in run_commands

    self.run_command(cmd)

  File "/usr/lib/python2.5/distutils/dist.py", line 994, in run_command

    cmd_obj.run()

  File "build/bdist.linux-i686/egg/setuptools/command/easy_install.py", line 211, in run

  File "build/bdist.linux-i686/egg/setuptools/command/easy_install.py", line 446, in easy_install

  File "build/bdist.linux-i686/egg/setuptools/command/easy_install.py", line 476, in install_item

  File "build/bdist.linux-i686/egg/setuptools/command/easy_install.py", line 655, in install_eggs

  File "build/bdist.linux-i686/egg/setuptools/command/easy_install.py", line 930, in build_and_install

  File "build/bdist.linux-i686/egg/setuptools/command/easy_install.py", line 919, in run_setup

  File "build/bdist.linux-i686/egg/setuptools/sandbox.py", line 27, in run_setup

  File "build/bdist.linux-i686/egg/setuptools/sandbox.py", line 63, in run

  File "build/bdist.linux-i686/egg/setuptools/sandbox.py", line 29, in <lambda>

  File "/usr/local/turbogears/lm/setup.py", line 15, in <module>

    #url='',

  File "/tmp/easy_install-6mOTZy/MySQL-python-1.2.3c1/setup_posix.py", line 43, in get_config

  File "/tmp/easy_install-6mOTZy/MySQL-python-1.2.3c1/setup_posix.py", line 24, in mysql_config

EnvironmentError: mysql_config not found

Solution:

apt-get install libmysqlclient15-dev
easy_install mysql-python

ToscaWidget conflict

  Installed /home/lek/work/mine/tg2env/lib/python2.6/site-packages/tw.forms-0.9.7.2-py2.6.egg
  error: Installed distribution ToscaWidgets 0.9.7.1 conflicts with requirement ToscaWidgets>=0.9.7.2
  • Fixed with:

easy_install -U tw.forms

easy_install pyodbc

  • On Debian in order to install pyodbc you need the following packages:

aptitude install python-dev
aptitude install unixodbc
aptitude install unixodbc-dev
aptitude install g++
  • Then you can do:

easy_install pydobc

Some errors you might get if yo don't have them installed

Keywords: Tutorial, TurboGears, SqlObject, SqlAlchemy, web, python, features, Linux, windows, database, mysql, postgre, mssql, examples, howto, web apps, cherrypy to paste, manual, easy, first time to turbogears, documentation, solutions, fixed, solved, working, pylons, python web framework, Tosca TW widget Forms, TurboGears controller, TurboGears model, TurboGears and sqlalchemy, TurboGears and AJAX, Turbogears Documentation, Turbogears Manual, Turbogears Howto, Turbogears how to, Turbogears faq,trubogears, DOJO, plugin, web development.

MyWiki: TurboGears2 (last edited 2011-10-16 19:24:46 by LukaszSzybalski)