Commits

Chris Perkins  committed ce4333f

rest does up to new validation now.

  • Participants
  • Parent commits 1e26408

Comments (0)

Files changed (16)

File devdata.db

Binary file modified.

File ez_setup/README.txt

-This directory exists so that Subversion-based projects can share a single
-copy of the ``ez_setup`` bootstrap module for ``setuptools``, and have it
-automatically updated in their projects when ``setuptools`` is updated.
-
-For your convenience, you may use the following svn:externals definition::
-
-    ez_setup svn://svn.eby-sarna.com/svnroot/ez_setup
-
-You can set this by executing this command in your project directory::
-
-    svn propedit svn:externals .
-
-And then adding the line shown above to the file that comes up for editing.
-Then, whenever you update your project, ``ez_setup`` will be updated as well.

File ez_setup/__init__.py

-#!python
-"""Bootstrap setuptools installation
-
-If you want to use setuptools in your package's setup.py, just include this
-file in the same directory with it, and add this to the top of your setup.py::
-
-    from ez_setup import use_setuptools
-    use_setuptools()
-
-If you want to require a specific version of setuptools, set a download
-mirror, or use an alternate download directory, you can do so by supplying
-the appropriate options to ``use_setuptools()``.
-
-This file can also be run as a script to install or upgrade setuptools.
-"""
-import sys
-DEFAULT_VERSION = "0.6c7"
-DEFAULT_URL     = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
-
-md5_data = {
-    'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
-    'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
-    'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
-    'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
-    'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
-    'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
-    'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
-    'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
-    'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
-    'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
-    'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
-    'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
-    'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
-    'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
-    'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
-    'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
-    'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
-    'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
-    'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
-    'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
-    'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
-    'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
-    'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
-    'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
-    'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
-    'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
-    'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
-}
-
-import sys, os
-
-def _validate_md5(egg_name, data):
-    if egg_name in md5_data:
-        from md5 import md5
-        digest = md5(data).hexdigest()
-        if digest != md5_data[egg_name]:
-            print >>sys.stderr, (
-                "md5 validation of %s failed!  (Possible download problem?)"
-                % egg_name
-            )
-            sys.exit(2)
-    return data
-
-
-def use_setuptools(
-    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
-    download_delay=15
-):
-    """Automatically find/download setuptools and make it available on sys.path
-
-    `version` should be a valid setuptools version number that is available
-    as an egg for download under the `download_base` URL (which should end with
-    a '/').  `to_dir` is the directory where setuptools will be downloaded, if
-    it is not already available.  If `download_delay` is specified, it should
-    be the number of seconds that will be paused before initiating a download,
-    should one be required.  If an older version of setuptools is installed,
-    this routine will print a message to ``sys.stderr`` and raise SystemExit in
-    an attempt to abort the calling script.
-    """
-    try:
-        import setuptools
-        if setuptools.__version__ == '0.0.1':
-            print >>sys.stderr, (
-            "You have an obsolete version of setuptools installed.  Please\n"
-            "remove it from your system entirely before rerunning this script."
-            )
-            sys.exit(2)
-    except ImportError:
-        egg = download_setuptools(version, download_base, to_dir, download_delay)
-        sys.path.insert(0, egg)
-        import setuptools; setuptools.bootstrap_install_from = egg
-
-    import pkg_resources
-    try:
-        pkg_resources.require("setuptools>="+version)
-
-    except pkg_resources.VersionConflict, e:
-        # XXX could we install in a subprocess here?
-        print >>sys.stderr, (
-            "The required version of setuptools (>=%s) is not available, and\n"
-            "can't be installed while this script is running. Please install\n"
-            " a more recent version first.\n\n(Currently using %r)"
-        ) % (version, e.args[0])
-        sys.exit(2)
-
-def download_setuptools(
-    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
-    delay = 15
-):
-    """Download setuptools from a specified location and return its filename
-
-    `version` should be a valid setuptools version number that is available
-    as an egg for download under the `download_base` URL (which should end
-    with a '/'). `to_dir` is the directory where the egg will be downloaded.
-    `delay` is the number of seconds to pause before an actual download attempt.
-    """
-    import urllib2, shutil
-    egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
-    url = download_base + egg_name
-    saveto = os.path.join(to_dir, egg_name)
-    src = dst = None
-    if not os.path.exists(saveto):  # Avoid repeated downloads
-        try:
-            from distutils import log
-            if delay:
-                log.warn("""
----------------------------------------------------------------------------
-This script requires setuptools version %s to run (even to display
-help).  I will attempt to download it for you (from
-%s), but
-you may need to enable firewall access for this script first.
-I will start the download in %d seconds.
-
-(Note: if this machine does not have network access, please obtain the file
-
-   %s
-
-and place it in this directory before rerunning this script.)
----------------------------------------------------------------------------""",
-                    version, download_base, delay, url
-                ); from time import sleep; sleep(delay)
-            log.warn("Downloading %s", url)
-            src = urllib2.urlopen(url)
-            # Read/write all in one block, so we don't create a corrupt file
-            # if the download is interrupted.
-            data = _validate_md5(egg_name, src.read())
-            dst = open(saveto,"wb"); dst.write(data)
-        finally:
-            if src: src.close()
-            if dst: dst.close()
-    return os.path.realpath(saveto)
-
-def main(argv, version=DEFAULT_VERSION):
-    """Install or upgrade setuptools and EasyInstall"""
-
-    try:
-        import setuptools
-    except ImportError:
-        egg = None
-        try:
-            egg = download_setuptools(version, delay=0)
-            sys.path.insert(0,egg)
-            from setuptools.command.easy_install import main
-            return main(list(argv)+[egg])   # we're done here
-        finally:
-            if egg and os.path.exists(egg):
-                os.unlink(egg)
-    else:
-        if setuptools.__version__ == '0.0.1':
-            # tell the user to uninstall obsolete version
-            use_setuptools(version)
-
-    req = "setuptools>="+version
-    import pkg_resources
-    try:
-        pkg_resources.require(req)
-    except pkg_resources.VersionConflict:
-        try:
-            from setuptools.command.easy_install import main
-        except ImportError:
-            from easy_install import main
-        main(list(argv)+[download_setuptools(delay=0)])
-        sys.exit(0) # try to force an exit
-    else:
-        if argv:
-            from setuptools.command.easy_install import main
-            main(argv)
-        else:
-            print "Setuptools version",version,"or greater has been installed."
-            print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
-
-
-
-def update_md5(filenames):
-    """Update our built-in md5 registry"""
-
-    import re
-    from md5 import md5
-
-    for name in filenames:
-        base = os.path.basename(name)
-        f = open(name,'rb')
-        md5_data[base] = md5(f.read()).hexdigest()
-        f.close()
-
-    data = ["    %r: %r,\n" % it for it in md5_data.items()]
-    data.sort()
-    repl = "".join(data)
-
-    import inspect
-    srcfile = inspect.getsourcefile(sys.modules[__name__])
-    f = open(srcfile, 'rb'); src = f.read(); f.close()
-
-    match = re.search("\nmd5_data = {\n([^}]+)}", src)
-    if not match:
-        print >>sys.stderr, "Internal error!"
-        sys.exit(2)
-
-    src = src[:match.start(1)] + repl + src[match.end(1):]
-    f = open(srcfile,'w')
-    f.write(src)
-    f.close()
-
-
-if __name__=='__main__':
-    if len(sys.argv)>2 and sys.argv[1]=='--md5update':
-        update_md5(sys.argv[2:])
-    else:
-        main(sys.argv[1:])

File moviedemo/controllers/crc.py

+"""Main Controller"""
+from moviedemo.lib.base import BaseController
+from tg import expose, flash, require, url, request, redirect
+from pylons.i18n import ugettext as _
+#from tg import redirect, validate
+from moviedemo.model import DBSession, metadata
+from moviedemo.controllers.error import ErrorController
+from moviedemo import model
+from catwalk.tg2 import Catwalk
+from repoze.what import predicates
+from moviedemo.controllers.secure import SecureController
+
+# =====================================
+
+from tgext.crud import CrudRestController
+from moviedemo.model import DBSession, Movie
+from sprox.tablebase import TableBase
+from sprox.fillerbase import TableFiller
+
+from sprox.formbase import AddRecordForm
+
+class MovieAddForm(AddRecordForm):
+    __model__ = Movie
+    __omit_fields__ = ['genre_id', 'movie_id']
+movie_add_form = MovieAddForm(DBSession)
+
+ 
+from sprox.formbase import EditableForm
+
+class MovieEditForm(EditableForm):
+    __model__ = Movie
+    __omit_fields__ = ['genre_id', 'movie_id']
+movie_edit_form = MovieEditForm(DBSession)
+
+
+from sprox.fillerbase import EditFormFiller
+
+class MovieEditFiller(EditFormFiller):
+    __model__ = Movie
+movie_edit_filler = MovieEditFiller(DBSession)
+
+#this is de-activated so that the controller uses the Sprox form.
+from tw.core import WidgetsList
+from tw.forms import TableForm, TextField, CalendarDatePicker, SingleSelectField, TextArea
+from formencode.validators import Int, NotEmpty, DateConverter, DateValidator
+
+class MovieForm(TableForm):
+    class fields(WidgetsList):
+        title = TextField(validator=NotEmpty)
+        description = TextArea(attrs=dict(rows=7, cols=50))
+        release_date = CalendarDatePicker(validator=DateConverter())
+        genrechoices = ((None, '----------'),
+                         (1,"action"),
+                         (2,"animation"),
+                         (3,"comedy"),
+                         (4,"documentary"),
+                         (5,"drama"),
+                         (6,"sci-fi"))
+        genre = SingleSelectField(options=genrechoices)
+_movie_add_form = MovieForm("create_movie_form")
+#end deactivation
+
+def email_info():
+    print 'emailing'
+
+class MovieTable(TableBase):
+    __model__ = Movie
+    __omit_fields__ = ['genre_id', 'movie_id']
+movie_table = MovieTable(DBSession)
+
+class MovieTableFiller(TableFiller):
+    __model__ = Movie
+movie_table_filler = MovieTableFiller(DBSession)
+
+from sprox.dojo.tablebase import DojoTableBase
+class DojoMovieTable(DojoTableBase):
+    __model__ = Movie
+    __omit_fields__ = ['genre_id', 'movie_id']
+dojo_movie_table = DojoMovieTable(DBSession)
+
+from sprox.dojo.fillerbase import DojoTableFiller
+class DojoMovieTableFiller(DojoTableFiller):
+    __model__ = Movie
+dojo_movie_table_filler = DojoMovieTableFiller(DBSession)
+
+class MovieCrudRestController(CrudRestController):
+    model = Movie
+
+    table =  dojo_movie_table
+    table_filler = dojo_movie_table_filler
+    
+    new_form = movie_add_form
+    edit_form = movie_edit_form
+    edit_filler = movie_edit_filler
+    
+    @expose()
+    def post(self, **kw):
+        email_info()
+        return super(MovieController, self).post(**kw)
+    
+    def _post_delete(self, **kw):
+        "not allowed"
+        
+
+from sprox.tablebase import TableBase
+from sprox.formbase import EditableForm, AddRecordForm
+from sprox.fillerbase import TableFiller, EditFormFiller
+
+from tgext.admin import AdminController
+    
+class DeclarativeMovieController(CrudRestController):
+    model = Movie
+    
+    class add_form_type(AddRecordForm):
+        __model__ = Movie
+        __omit_fields__ = ['genre_id', 'movie_id']
+
+    class edit_form_type(EditableForm):
+        __model__ = Movie
+        __omit_fields__ = ['genre_id', 'movie_id']
+
+    class edit_filler_type(EditFormFiller):
+        __model__ = Movie
+
+    class table_type(TableBase):
+        __model__ = Movie
+        __omit_fields__ = ['genre_id', 'movie_id']
+
+    class table_filler_type(TableFiller):
+        __model__ = Movie

File moviedemo/controllers/rest.py

+"""Rest Controller Info"""
+from datetime import datetime
+from tg.controllers import RestController, redirect
+from tg.decorators import expose, validate
+from moviedemo.model import DBSession, Movie, Genre, Director
+from formencode.validators import DateConverter, Int, NotEmpty
+
+class MovieRestController(RestController):
+
+    @expose('moviedemo.templates.rest.movies.get_all')
+    def get_all(self):
+        movies = DBSession.query(Movie).all()
+        return dict(movies=movies)
+    
+    @expose('moviedemo.templates.rest.movies.new')
+    def new(self, *args, **kw):
+        genres = DBSession.query(Genre).all()
+        directors = DBSession.query(Director).all()
+        
+        if 'directors' in kw and not isinstance(kw['directors'], list):
+            kw['directors'] = [kw['directors']]
+
+        return dict(values=kw, genres=genres, directors=directors)
+    
+    @validate({'title':NotEmpty, 
+               'description':NotEmpty, 
+               'genre_id':Int(not_empty=True), 
+               'release_date':DateConverter(not_empty=True)}, error_handler=new)
+    @expose()
+    def post(self, title, description, directors=None, genre_id=None, release_date=None):
+        if genre_id is not None:
+            genre_id = int(genre_id)
+        if directors is not None:
+            if not isinstance(directors, list):
+                directors = [directors]
+            directors = [DBSession.query(Director).get(director) for director in directors]
+        if release_date is not None:
+            release_date = datetime.strptime(release_date, "%m/%d/%y")
+        movie = Movie(title=title, description=description, release_date=release_date, directors=directors, genre_id=genre_id)
+        DBSession.add(movie)
+        redirect('./')

File moviedemo/controllers/root.py

 from moviedemo.lib.base import BaseController
 from tg import expose, flash, require, url, request, redirect
 from pylons.i18n import ugettext as _
-#from tg import redirect, validate
 from moviedemo.model import DBSession, metadata
 from moviedemo.controllers.error import ErrorController
 from moviedemo import model
 from repoze.what import predicates
 from moviedemo.controllers.secure import SecureController
 
-# =====================================
+from moviedemo.controllers.crc import MovieCrudRestController, DeclarativeMovieController
+from moviedemo.controllers.rest import MovieRestController
 
-from tgext.crud import CrudRestController
-from moviedemo.model import DBSession, Movie
-from sprox.tablebase import TableBase
-from sprox.fillerbase import TableFiller
-
-from sprox.formbase import AddRecordForm
-
-class MovieAddForm(AddRecordForm):
-    __model__ = Movie
-    __omit_fields__ = ['genre_id', 'movie_id']
-movie_add_form = MovieAddForm(DBSession)
-
- 
-from sprox.formbase import EditableForm
-
-class MovieEditForm(EditableForm):
-    __model__ = Movie
-    __omit_fields__ = ['genre_id', 'movie_id']
-movie_edit_form = MovieEditForm(DBSession)
-
-
-from sprox.fillerbase import EditFormFiller
-
-class MovieEditFiller(EditFormFiller):
-    __model__ = Movie
-movie_edit_filler = MovieEditFiller(DBSession)
-
-#this is de-activated so that the controller uses the Sprox form.
-from tw.core import WidgetsList
-from tw.forms import TableForm, TextField, CalendarDatePicker, SingleSelectField, TextArea
-from formencode.validators import Int, NotEmpty, DateConverter, DateValidator
-
-class MovieForm(TableForm):
-    class fields(WidgetsList):
-        title = TextField(validator=NotEmpty)
-        description = TextArea(attrs=dict(rows=7, cols=50))
-        release_date = CalendarDatePicker(validator=DateConverter())
-        genrechoices = ((None, '----------'),
-                         (1,"action"),
-                         (2,"animation"),
-                         (3,"comedy"),
-                         (4,"documentary"),
-                         (5,"drama"),
-                         (6,"sci-fi"))
-        genre = SingleSelectField(options=genrechoices)
-_movie_add_form = MovieForm("create_movie_form")
-#end deactivation
-
-def email_info():
-    print 'emailing'
-
-class MovieTable(TableBase):
-    __model__ = Movie
-    __omit_fields__ = ['genre_id', 'movie_id']
-movie_table = MovieTable(DBSession)
-
-class MovieTableFiller(TableFiller):
-    __model__ = Movie
-movie_table_filler = MovieTableFiller(DBSession)
-
-from sprox.dojo.tablebase import DojoTableBase
-class DojoMovieTable(DojoTableBase):
-    __model__ = Movie
-    __omit_fields__ = ['genre_id', 'movie_id']
-dojo_movie_table = DojoMovieTable(DBSession)
-
-from sprox.dojo.fillerbase import DojoTableFiller
-class DojoMovieTableFiller(DojoTableFiller):
-    __model__ = Movie
-dojo_movie_table_filler = DojoMovieTableFiller(DBSession)
-
-class MovieController(CrudRestController):
-    model = Movie
-
-    table =  dojo_movie_table
-    table_filler = dojo_movie_table_filler
-    
-    new_form = movie_add_form
-    edit_form = movie_edit_form
-    edit_filler = movie_edit_filler
-    
-    @expose()
-    def post(self, **kw):
-        email_info()
-        return super(MovieController, self).post(**kw)
-    
-    def _post_delete(self, **kw):
-        "not allowed"
-        
-
-from sprox.tablebase import TableBase
-from sprox.formbase import EditableForm, AddRecordForm
-from sprox.fillerbase import TableFiller, EditFormFiller
-
-from tgext.admin import AdminController
-    
-class DeclarativeMovieController(CrudRestController):
-    model = Movie
-    
-    class add_form_type(AddRecordForm):
-        __model__ = Movie
-        __omit_fields__ = ['genre_id', 'movie_id']
-
-    class edit_form_type(EditableForm):
-        __model__ = Movie
-        __omit_fields__ = ['genre_id', 'movie_id']
-
-    class edit_filler_type(EditFormFiller):
-        __model__ = Movie
-
-    class table_type(TableBase):
-        __model__ = Movie
-        __omit_fields__ = ['genre_id', 'movie_id']
-
-    class table_filler_type(TableFiller):
-        __model__ = Movie
-
-# =====================================
 
 import inspect
 from sqlalchemy.orm import class_mapper
     except:
         pass
 
+
 class RootController(BaseController):
-    movie = MovieController(DBSession, models)
-
-    secc = SecureController()
+    
+    #swap these lines out to use the different controllers in this demo
+    #movies = MovieCrudRestController(DBSession, models)
+    #movies = DeclarativeMovieController(DBSession, models)
+    movies = MovieRestController(DBSession)
+    
     admin = Catwalk(model, DBSession)
     error = ErrorController()
 

File moviedemo/model/__init__.py

 from sqlalchemy import Column, Integer, String, Date, Text, ForeignKey
 from sqlalchemy.orm import relation
 
+from sqlalchemy import Column, Integer, String, Date, Text, ForeignKey, Table
+from sqlalchemy.orm import relation
+
+from moviedemo.model import DeclarativeBase, metadata
+
+movie_directors_table = Table('movie_directors', metadata,
+                              Column('movie_id', Integer, ForeignKey('movies.movie_id'), primary_key = True),
+                              Column('director_id', Integer, ForeignKey('directors.director_id'), primary_key = True))
+
 class Genre(DeclarativeBase):
     __tablename__ = "genres"
     genre_id = Column(Integer, primary_key=True)
     genre = relation('Genre', backref='movies')
     release_date = Column(Date, nullable=True)
 
+class Director(DeclarativeBase):
+    __tablename__ = "directors"
+    director_id = Column(Integer, primary_key=True)
+    name = Column(String(100), nullable=False)
+    movies = relation(Movie, secondary=movie_directors_table, backref="directors")
+
 def init_model(engine):
     """Call me before using any of the tables or classes in the model."""
 

File moviedemo/public/css/style.css

 div.clearingdiv {
 clear:both;
 }
+
+.odd {
+background:#e0e0e0
+}

File moviedemo/templates/rest/__init__.py

Empty file added.

File moviedemo/templates/rest/movies/__init__.py

Empty file added.

File moviedemo/templates/rest/movies/edit.html

+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<xi:include href="master.html" />
+<head>
+<style>
+#menu_items {
+  padding:0px 12px 0px 2px;
+  list-style-type:None
+  }
+</style>
+</head>
+<body class="tundra">
+  <div style="height:0px;"> &nbsp; </div>
+  <div style="float:left; padding-left:0px;">
+        <ul id="menu_items" py:if="hasattr(tmpl_context, 'menu_items')">
+            <li py:for="lower, item in sorted(tmpl_context.menu_items.iteritems())"><a href="../../${lower}s/">$item.__name__</a></li>
+       </ul>
+  </div>
+  <div style="float:left;">
+    <h2 style="margin-top:1px;">Edit $model</h2>
+     ${XML(tmpl_context.widget(value=value, action='./'))}
+  </div>
+  <div style="height:0px; clear:both;"> &nbsp; </div>
+
+</body>
+</html>

File moviedemo/templates/rest/movies/get_all.html

+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+  <xi:include href="master.html" />
+<head/>
+<body>
+  <div style="height:0px;"> &nbsp; </div>
+  <div>
+    <div style="float:left width: 80%">
+      <h1 style="margin-top:1px;">Movie Listing</h1>
+      <a href='new/'>New Movie</a>
+        <table>
+          <tr>
+          <th>Title</th>
+          <th>Description</th>
+          <th>Genre</th>
+          <th>Directors</th>
+          <th>Release Date</th>
+          </tr>
+          <tr py:for="i, movie in enumerate(movies)" class="${i%2 and 'even' or 'odd'}">
+            <td>${movie.title}</td>
+            <td>${movie.description}</td>
+            <td>${movie.genre.name}</td>
+            <td>
+            <py:for each="director in movie.directors">
+            ${director.name},
+            </py:for>
+            </td>
+            <td>${movie.release_date}</td>
+          </tr>
+        </table>
+    </div>
+  </div>
+</body>
+</html>

File moviedemo/templates/rest/movies/get_delete.html

+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<xi:include href="master.html" />
+<head>
+</head>
+<body class="tundra">
+A page to help confirm deletes.
+</body>
+</html>

File moviedemo/templates/rest/movies/get_one.html

+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<xi:include href="master.html" />
+<head>
+  <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+  <title>Welcome to TurboGears 2.0, standing on the
+  shoulders of giants, since 2007</title>
+
+</head>
+<body class="tundra">
+  <div id="getting_started">
+&nbsp;
+<div>
+<h1>$model Listing</h1>
+</div>
+<div>
+<a href='new/'>New</a> $model
+</div>
+<br/>
+${XML(tmpl_context.widget())}
+</div>
+</body>
+</html>

File moviedemo/templates/rest/movies/new.html

+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<xi:include href="master.html" />
+<head/>
+<body>
+  <div style="height:0px;"> &nbsp; </div>
+  <h2> New Movie</h2>
+  <form method="POST" action="./">
+    <table>
+    <tr><td>Title </td><td><input type="textfield" name="title" value="${values.get('title', '')}"/>${tmpl_context.form_errors.get('title')}</td></tr>
+    <tr><td>Description</td><td> <textarea name="description">${values.get('description')}</textarea>${tmpl_context.form_errors.get('description')}</td></tr>
+    <tr><td>Genre</td>
+        <td>
+          <select name="genre_id">
+              <py:for each="genre in genres">
+                <py:if test="values.get('genre_id') == unicode(genre.genre_id)">
+                  <option value="$genre.genre_id" selected="selected">$genre.name</option>
+                </py:if>
+                <py:if test="values.get('genre_id')!= unicode(genre.genre_id)">
+                  <option value="$genre.genre_id">$genre.name</option>
+                </py:if>
+              </py:for>
+          </select>
+        </td></tr>
+    <tr><td>Directors</td>
+        <td>
+          <select multiple="true" name="directors">
+              <py:for each="director in directors">
+                $director.name
+                <py:if test="unicode(director.director_id) in values.get('directors', [])">
+                  <option value="$director.director_id" selected="selected">$director.name</option>
+                </py:if>
+                <py:if test="unicode(director.director_id) not in values.get('directors', [])">
+                  <option value="$director.director_id">$director.name</option>
+                </py:if>
+              </py:for>
+          </select>
+        </td></tr>
+    <tr><td>Release Date</td><td><input type="textfield" name="release_date" value="${values.get('release_date')}"/>${tmpl_context.form_errors.get('release_date')}</td></tr>
+    </table><br/>
+    <input type="submit" value="create"/>
+  </form>
+  
+</body>
+</html>

File moviedemo/websetup.py

 
     for genre in genres:
         model.DBSession.add(model.Genre(name=genre))
+        
+    directors = ['Robert Zemeckis', 'David Fincher', 'Andy Wachowski', 'Larry Wachowski', 'Joel Coen']
+    
+    for i, director in enumerate(directors):
+        director = model.Director(name=director)
+        model.DBSession.add(director)
+        directors[i] = director
     
     movies = [
         {'title': 'Back to the Future',
          'description':"""In 1985, Doc Brown invents time travel; in 1955, Marty McFly accidentally prevents his parents from meeting, putting his own existence at stake""",
          'genre_id': 6,
-         'release_date': '4/3/85'
+         'release_date': '4/3/85',
+         'directors': [directors[0]],
          },
         {'title': 'Fight Club',
          'description':"""An office employee and a soap salesman build a global organization to help vent male aggression.""",
          'genre_id': 1,
-         'release_date': '10/14/99'
+         'release_date': '10/14/99',
+        'directors': [directors[1]],
          },
-        {'title': 'Jabberwocky',
-         'description':"""After the death of his father the young cooper 'Dennis Cooper' goes to town where he has to pass several adventures...""",
+        {'title': 'The Matrix',
+         'description':"""A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against the controllers of it.""",
          'genre_id': 1,
-         'release_date': '4/15/75'
+         'release_date': '3/31/99',
+         'directors': [directors[2], directors[3]],
          },
         {'title': 'The Big Lebowski',
          'description':""""Dude" Lebowski, mistaken for a millionaire Lebowski, seeks restitution for his ruined rug and enlists his bowling buddies to help get it.""",
          'genre_id': 3,
-         'release_date': '4/15/75'
+         'release_date': '3/6/98',
+         'directors': [directors[4]]
          },
     ]
     for movie in movies: