paster quickstart produced a directory for our model in wiki20/model/. This directory contains an __init__.py file which makes that directory name into a python module (so you can use the Python expression import model).
In order to easily use our model within the application, modify the wiki20/model/__init__.py file to add Page to the module. Add the following line at the end of the file:
from wiki20.model.page import Page
Warning
It’s very important that this line is at the end because Page requires the rest of the model to be initialized before it can be imported:
Since a wiki is basically a linked collection of pages, we’ll define a Page class as the name of our model. Create a new file called page.py in the wiki20/model/ directory by using the model.template file provided for you there:
$ cd wiki20/model
$ cp model.template page.py
1 2 3 4 5 6 7 8 9 10 11 12 13 | from sqlalchemy import *
from sqlalchemy.orm import mapper, relation
from sqlalchemy import Table, ForeignKey, Column
from sqlalchemy.types import Integer, Unicode
#from sqlalchemy.orm import relation, backref
from wiki20.model import DeclarativeBase, metadata, DBSession
class SampleModel(DeclarativeBase):
__tablename__ = 'sample_model'
id = Column(Integer, primary_key=True)
data = Column(Unicode(255), nullable=False)
|
Edit your page.py to become
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from sqlalchemy import *
from sqlalchemy import Column
from sqlalchemy.types import Integer, Text
#from sqlalchemy.orm import relation, backref
from wiki20.model import DeclarativeBase
class Page(DeclarativeBase):
__tablename__ = 'pages'
id=Column(Integer, primary_key=True)
pagename=Column(Text, unique=True)
data=Column(Text)
def __init__(self, pagename, data):
self.pagename = pagename
self.data = data
|
7: The DeclarativeBase class, from which all of our ORM-enabled classes will derive, is imported into wiki20/model/__init__.py from sqlalchemy.ext.declarative, and from there to our model.
9-10: We define a DeclarativeBase class Page and SQLAlchemy automatically associates to this class a database table named pages. We’ll access the table through the Python object Page: columns of the table will correspond to attributes of Page, rows of the table to instances of Page. We’re using a “shorthand” configurational style of SQLAlchemy called “declarative”.
12-14: Define columns id, pagename and data of the database table.
16-18: Initialize pagename and data attributes of new Page instances.
The SQLAlchemy DeclarativeBase object defines what a single table looks like in the database, and adds any necessary constraints (so, for example, even if your database doesn’t enforce uniqueness, SQLAlchemy will attempt to do so). The __tablename__ attribute is the name of that table inside the database. Column objects are defined in the same way that you define them within a database: name, type, and constraints.
A metadata attribute of DeclarativeBase is automatically created by the paster command in the wiki20/model/__init__.py file using configuration settings taken from your development.ini file. Page.metadata is a dictionary that contains all the information necessary to connect to and use the database the page table is in, as well as information on related tables in the database. When you pass the metadata object to the various objects in your project they initialize themselves using that metadata.
Note that it’s also possible to start with an existing database, but that’s a more advanced topic that we won’t cover in this tutorial.
By default, projects created with quickstart are configured to use a very simple SQLite database – however, TurboGears 2 supports most popular databases. The database configuration is set in the development.ini file in the root directory of the project.
Search down the [app:main] section in development.ini for the line:
sqlalchemy.url = sqlite:///%(here)s/devdata.db
Turbogears will automatically replace the %(here)s variable with the parent directory of this file, so for our example it will produce sqlite:///Wiki-20/devdata.db. You won’t see the devdata.db file now because we haven’t yet initialized the database.
Before you can use your database, you need to initialize it and add some data. There’s built in support for this in TurboGears using paster setup-app and wiki20/websetup.py. The quickstart template gives you a basic template for this file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | """Setup the Wiki-20 application"""
import logging
import transaction
from tg import config
from wiki20.config.environment import load_environment
log = logging.getLogger(__name__)
def setup_app(command, conf, vars):
"""Place any commands to setup wiki20 here"""
load_environment(conf.global_conf, conf.local_conf)
# Load the models
from wiki20 import model
print "Creating tables"
model.metadata.create_all(bind=config['pylons.app_globals'].sa_engine)
transaction.commit()
print "Successfully setup"
|
We need to update the file to create our FrontPage data, starting at line 19:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | """Setup the Wiki-20 application"""
import logging
import transaction
from tg import config
from wiki20.config.environment import load_environment
log = logging.getLogger(__name__)
def setup_app(command, conf, vars):
"""Place any commands to setup wiki20 here"""
load_environment(conf.global_conf, conf.local_conf)
# Load the models
from wiki20 import model
print "Creating tables"
model.metadata.create_all(bind=config['pylons.app_globals'].sa_engine)
# Create the initial data
print "Creating initial data"
page = model.Page("FrontPage", "initial data")
model.DBSession.add(page)
transaction.commit()
print "Successfully setup"
|
19-20: documentation and progress report
22: create a first wikipage entry called FrontPage.
24: communicate that it should be saved to the database.
26: tell the database to commit that save.
If you’re familiar with SQLAlchemy this should look pretty standard to you. One difference to note (lines 4 and 26) is our use of the command:
transaction.commit()
where you may be used to seeing DBSession.commit(). This calls the transaction manager which helps us to support cross database transactions, as well as transactions in non-relational databases, but ultimately in the case of SQLAlchemy it calls DBSession.commit() just like you might if you were doing it directly.
Now run the paster setup-app command in your root directory:
$ paster setup-app development.ini
You’ll see output, but you should not see error messages. At this point your database is created and has some initial data in it that you can verify by looking at devdata.db. The file should exist and have a nonzero size.
Note
Try the following from the command-line (in the root directory of your project):
$ sqlite3 -line devdata.db 'select * from pages;'
You should see the output:
id = 1
pagename = FrontPage
data = initial data
Alternatively, initiate an sqlite session from the command-line:
$ sqlite3 devdata.db
Here’s a possible sequence of commands:
sqlite> .help (what help is available)
sqlite> .show (what are the current settings)
sqlite> .tables (name tables in the database)
sqlite> .schema pages (SQL schema used to create pages table)
sqlite> SELECT * FROM pages; (see everything (*) in the pages table)
sqlite> .header on (see the column names)
sqlite> .separator ' | ' (adjust output)
sqlite> SELECT * FROM pages; (see everything again)
sqlite> UPDATE pages SET data='new data' WHERE id=1; (change "data" field of record 1 in pages table)
sqlite> SELECT * FROM pages; (see the changes)
sqlite> .quit (quit sqlite - back to shell)
Note that there are two types of sqlite commands:
1) help commands all begin with "."
2) SQL commands don't begin with a "." - these *must* be terminated with a ";"
SQL keywords are usually shown in uppercase, but are case-insensitive.
You can also see your model from within a python or ipython shell: e.g. a sample ipython session.
That takes care of the “M” in MVC. Next is the “C”: controllers.