Commits

Chris Perkins committed 1e26408

adding moviedemo

  • Participants

Comments (0)

Files changed (66)

+recursive-include moviedemo/public *
+include moviedemo/public/favicon.ico
+recursive-include moviedemo/i18n *
+recursive-include moviedemo/templates *
+This file is for you to describe the moviedemo application. Typically
+you would include information such as the information below:
+
+Installation and Setup
+======================
+
+Install ``moviedemo`` using the setup.py script::
+
+    $ cd moviedemo
+    $ python setup.py install
+
+Create the project database for any model classes defined::
+
+    $ paster setup-app development.ini
+
+Start the paste http server::
+
+    $ paster serve development.ini
+
+While developing you may want the server to reload after changes in package files (or its dependencies) are saved. This can be achieved easily by adding the --reload option::
+
+    $ paster serve --reload development.ini
+
+Then you are ready to go.

File devdata.db

Binary file added.

File development.ini

+#
+# moviedemo - Pylons development environment configuration
+#
+# The %(here)s variable will be replaced with the parent directory of this file
+#
+# This file is for deployment specific config options -- other configuration
+# that is always required for the app is done in the config directory, 
+# and generally should not be modified by end users. 
+
+[DEFAULT]
+debug = true
+# Uncomment and replace with the address which should receive any error reports
+#email_to = you@yourdomain.com
+smtp_server = localhost
+error_email_from = paste@localhost
+
+[server:main]
+use = egg:Paste#http
+host = 127.0.0.1 
+port = 8080
+
+[app:main]
+use = egg:moviedemo
+full_stack = true
+#lang = ru
+cache_dir = %(here)s/data
+beaker.session.key = moviedemo
+beaker.session.secret = somesecret
+
+# If you'd like to fine-tune the individual locations of the cache data dirs
+# for the Cache data, or the Session saves, un-comment the desired settings
+# here:
+#beaker.cache.data_dir = %(here)s/data/cache
+#beaker.session.data_dir = %(here)s/data/sessions
+
+# pick the form for your database
+# %(here) may include a ':' character on Windows environments; this can
+# invalidate the URI when specifying a SQLite db via path name
+# sqlalchemy.url=postgres://username:password@hostname:port/databasename 
+# sqlalchemy.url=mysql://username:password@hostname:port/databasename
+
+
+# If you have sqlite, here's a simple default to get you started
+# in development
+
+sqlalchemy.url = sqlite:///%(here)s/devdata.db
+#echo shouldn't be used together with the logging module.
+sqlalchemy.echo = false
+sqlalchemy.echo_pool = false
+sqlalchemy.pool_recycle = 3600
+
+# WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*
+# Debug mode will enable the interactive debugging tool, allowing ANYONE to
+# execute malicious code after an exception is raised.
+#set debug = false
+
+# Logging configuration
+# Add additional loggers, handlers, formatters here
+# Uses python's logging config file format
+# http://docs.python.org/lib/logging-config-fileformat.html
+
+[loggers]
+keys = root, moviedemo, sqlalchemy, auth
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+# If you create additional loggers, add them as a key to [loggers]
+[logger_root]
+level = WARN
+handlers = console
+
+[logger_moviedemo]
+level = WARN
+handlers =
+qualname = moviedemo
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+# "level = INFO" logs SQL queries.
+# "level = DEBUG" logs SQL queries and results.
+# "level = WARN" logs neither.  (Recommended for production systems.)
+
+
+# A logger for authentication, identification and authorization -- this is
+# repoze.who and repoze.what:
+[logger_auth]
+level = WARN
+handlers = 
+qualname = auth
+
+# If you create additional handlers, add them as a key to [handlers]
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+# If you create additional formatters, add them as a key to [formatters]
+[formatter_generic]
+format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S

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/__init__.py

Empty file added.

File moviedemo/config/__init__.py

Empty file added.

File moviedemo/config/app_cfg.py

+from tg.configuration import AppConfig, Bunch
+import moviedemo
+from moviedemo import model
+from moviedemo.lib import app_globals, helpers
+
+base_config = AppConfig()
+base_config.renderers = []
+
+base_config.package = moviedemo
+
+#Set the default renderer
+base_config.default_renderer = 'genshi'
+base_config.renderers.append('genshi') 
+# if you want raw speed and have installed chameleon.genshi
+# you should try to use this renderer instead.
+# warning: for the moment chameleon does not handle i18n translations
+#base_config.renderers.append('chameleon_genshi') 
+
+#Configure the base SQLALchemy Setup
+base_config.use_sqlalchemy = True
+base_config.model = moviedemo.model
+base_config.DBSession = moviedemo.model.DBSession
+
+# Configure the authentication backend
+base_config.auth_backend = 'sqlalchemy'
+base_config.sa_auth.dbsession = model.DBSession
+# what is the class you want to use to search for users in the database
+base_config.sa_auth.user_class = model.User
+# what is the class you want to use to search for groups in the database
+base_config.sa_auth.group_class = model.Group
+# what is the class you want to use to search for permissions in the database
+base_config.sa_auth.permission_class = model.Permission
+
+# override this if you would like to provide a different who plugin for 
+# managing login and logout of your application
+base_config.sa_auth.form_plugin = None
+
+# You may optionally define a page where you want users to be redirected to
+# on login:
+base_config.sa_auth.post_login_url = '/post_login'
+
+# You may optionally define a page where you want users to be redirected to
+# on logout:
+base_config.sa_auth.post_logout_url = '/post_logout'

File moviedemo/config/deployment.ini

+#
+# moviedemo - TurboGears configuration
+#
+# The %(here)s variable will be replaced with the parent directory of this file
+#
+[DEFAULT]
+# WARGING == If debug is not set to false, you'll get the interactive
+# debugger on production, which is a huge security hole. 
+
+debug = false
+email_to = you@yourdomain.com
+smtp_server = localhost
+error_email_from = paste@localhost
+
+[server:main]
+use = egg:Paste#http
+host = 0.0.0.0
+port = 8080
+
+[app:main]
+use = egg:moviedemo
+full_stack = true
+cache_dir = %(here)s/data
+beaker.session.key = moviedemo
+beaker.session.secret = ${app_instance_secret}
+app_instance_uuid = ${app_instance_uuid}
+
+# If you'd like to fine-tune the individual locations of the cache data dirs
+# for the Cache data, or the Session saves, un-comment the desired settings
+# here:
+#beaker.cache.data_dir = %(here)s/data/cache
+#beaker.session.data_dir = %(here)s/data/sessions
+# Specify the database for SQLAlchemy to use via
+# turbogears.database
+# %(here) may include a ':' character on Windows environments; this can
+# invalidate the URI when specifying a SQLite db via path name
+sqlalchemy.url = sqlite:///%(here)s/somedb.db
+sqlalchemy.echo = False
+
+# WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*
+# Debug mode will enable the interactive debugging tool, allowing ANYONE to
+# execute malicious code after an exception is raised.
+#set debug = false
+
+# Logging configuration
+# Add additional loggers, handlers, formatters here
+# Uses python's logging config file format
+# http://docs.python.org/lib/logging-config-fileformat.html
+
+[loggers]
+keys = root, moviedemo, sqlalchemy, auth
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+# If you create additional loggers, add them as a key to [loggers]
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_moviedemo]
+level = INFO
+handlers =
+qualname = moviedemo
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+# "level = INFO" logs SQL queries.
+# "level = DEBUG" logs SQL queries and results.
+# "level = WARN" logs neither.  (Recommended for production systems.)
+
+
+# A logger for authentication, identification and authorization -- this is
+# repoze.who and repoze.what:
+[logger_auth]
+level = WARN
+handlers = 
+qualname = auth
+
+# If you create additional handlers, add them as a key to [handlers]
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+# If you create additional formatters, add them as a key to [formatters]
+[formatter_generic]
+format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S

File moviedemo/config/environment.py

+from moviedemo.config.app_cfg import base_config
+
+#Use base_config to setup the environment loader function
+load_environment = base_config.make_load_environment()

File moviedemo/config/middleware.py

+"""TurboGears middleware initialization"""
+from moviedemo.config.app_cfg import base_config
+from moviedemo.config.environment import load_environment
+
+#Use base_config to setup the necessary WSGI App factory. 
+#make_base_app will wrap the TG2 app with all the middleware it needs. 
+make_base_app = base_config.setup_tg_wsgi_app(load_environment)
+
+def make_app(global_conf, full_stack=True, **app_conf):
+    app = make_base_app(global_conf, full_stack=True, **app_conf)
+    #Wrap your base turbogears app with custom middleware
+    return app
+
+

File moviedemo/controllers/__init__.py

Empty file added.

File moviedemo/controllers/controller.template

+"""Main Controller"""
+# standard library imports
+
+# turbogears imports
+from tg import expose
+#from tg import redirect, validate, flash
+
+# third party imports
+#from pylons.i18n import ugettext as _
+#from repoze.what import predicates
+
+# project specific imports
+from moviedemo.lib.base import BaseController
+#from moviedemo.model import DBSession, metadata
+
+class SampleController(BaseController):
+    #Uncomment this line if your controller requires an authenticated user
+    #allow_only = authorize.not_anonymous()
+
+    @expose('moviedemo.templates.index')
+    def index(self):
+        return dict(page='index')

File moviedemo/controllers/error.py

+import os.path
+
+import paste.fileapp
+import tg
+from pylons.controllers.util import forward
+from pylons.middleware import error_document_template, media_path
+
+from moviedemo.lib.base import BaseController
+
+class ErrorController(object):
+    """Generates error documents as and when they are required.
+
+    The ErrorDocuments middleware forwards to ErrorController when error
+    related status codes are returned from the application.
+
+    This behaviour can be altered by changing the parameters to the
+    ErrorDocuments middleware in your config/middleware.py file.
+    """
+
+    @tg.expose('moviedemo.templates.error')
+    def document(self, *args, **kwargs):
+        """Render the error document"""
+        resp = tg.request.environ.get('pylons.original_response')
+        default_message = ("<p>We're sorry but we weren't able to process "
+        " this request.</p>")
+        values = dict(prefix=tg.request.environ.get('SCRIPT_NAME', ''),
+                 code=tg.request.params.get('code', resp.status_int),
+                 message=tg.request.params.get('message', default_message))
+        return values

File moviedemo/controllers/root.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 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
+
+models = {}
+for m in dir(model):
+    m = getattr(model, m)
+    if not inspect.isclass(m):
+        continue
+    try:
+        mapper = class_mapper(m)
+        models[m.__name__.lower()] = m
+    except:
+        pass
+
+class RootController(BaseController):
+    movie = MovieController(DBSession, models)
+
+    secc = SecureController()
+    admin = Catwalk(model, DBSession)
+    error = ErrorController()
+
+    @expose('moviedemo.templates.index')
+    def index(self):
+        return dict(page='index')
+
+    @expose('moviedemo.templates.about')
+    def about(self):
+        return dict(page='about')
+
+    @expose('moviedemo.templates.authentication')
+    def auth(self):
+        return dict(page='auth')
+
+    @expose('moviedemo.templates.index')
+    @require(predicates.has_permission('manage', msg=_('Only for managers')))
+    def manage_permission_only(self, **kw):
+        return dict(page='managers stuff')
+
+    @expose('moviedemo.templates.index')
+    @require(predicates.is_user('editor', msg=_('Only for the editor')))
+    def editor_user_only(self, **kw):
+        return dict(page='editor stuff')
+
+    @expose('moviedemo.templates.login')
+    def login(self, came_from=url('/')):
+        login_counter = request.environ['repoze.who.logins']
+        if login_counter > 0:
+            flash(_('Wrong credentials'), 'warning')
+        return dict(page='login', login_counter=str(login_counter),
+                    came_from=came_from)
+
+    @expose()
+    def post_login(self, came_from=url('/')):
+        if not request.identity:
+            login_counter = request.environ['repoze.who.logins'] + 1
+            redirect(url('/login', came_from=came_from, __logins=login_counter))
+        userid = request.identity['repoze.who.userid']
+        flash(_('Welcome back, %s!') % userid)
+        redirect(came_from)
+
+    @expose()
+    def post_logout(self, came_from=url('/')):
+        flash(_('We hope to see you soon!'))
+        redirect(came_from)

File moviedemo/controllers/secure.py

+"""Test Secure Controller"""
+from moviedemo.lib.base import BaseController
+from tg import expose, flash
+from pylons.i18n import ugettext as _
+#from tg import redirect, validate
+#from moviedemo.model import DBSession, metadata
+#from dbsprockets.dbmechanic.frameworks.tg2 import DBMechanic
+#from dbsprockets.saprovider import SAProvider
+from repoze.what.predicates import has_permission
+
+
+class SecureController(BaseController):
+    """Sample controller-wide authorization"""
+
+    # The predicate that must be met for all the actions in this controller:
+    allow_only = has_permission('manage',
+                                msg=_('Only for people with the "manage" permission'))
+
+    @expose('moviedemo.templates.index')
+    def index(self):
+        flash(_("Secure Controller here"))
+        return dict(page='index')
+
+    @expose('moviedemo.templates.index')
+    def some_where(self):
+        """should be protected because of the require attr
+        at the controller level.
+        """
+        return dict(page='some_where')

File moviedemo/controllers/template.py

+from moviedemo.lib.base import *
+
+class TemplateController(BaseController):
+
+    def view(self, url):
+        """By default, the final controller tried to fulfill the request
+        when no other routes match. It may be used to display a template
+        when all else fails, e.g.::
+
+            def view(self, url):
+                return render('/%s' % url)
+
+        Or if you're using Mako and want to explicitly send a 404 (Not
+        Found) response code when the requested template doesn't exist::
+
+            import mako.exceptions
+
+            def view(self, url):
+                try:
+                    return render('/%s' % url)
+                except mako.exceptions.TopLevelLookupException:
+                    abort(404)
+
+        By default this controller aborts the request with a 404 (Not
+        Found)
+        """
+        abort(404)

File moviedemo/i18n/ru/LC_MESSAGES/moviedemo.po

+# Russian translations for ${package}.
+# Copyright (C) 2008 ORGANIZATION
+# This file is distributed under the same license as the ${package} project.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: ${package} 0.0.0\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2008-01-13 14:00+0200\n"
+"PO-Revision-Date: 2008-01-13 14:00+0200\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: ru <LL@li.org>\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.1\n"
+
+#: ${package}/controllers/root.py:13
+msgid "Your application is now running"
+msgstr "Ваши приложение успешно запущено"
+

File moviedemo/lib/__init__.py

Empty file added.

File moviedemo/lib/app_globals.py

+"""The application's Globals object"""
+
+class Globals(object):
+    """Globals acts as a container for objects available throughout the
+    life of the application
+    """
+
+    def __init__(self):
+        """One instance of Globals is created during application
+        initialization and is available during requests via the 'g'
+        variable
+        """
+        pass

File moviedemo/lib/base.py

+"""The base Controller API
+
+Provides the BaseController class for subclassing.
+"""
+from tg import TGController, tmpl_context
+from tg.render import render
+from tg import request
+
+import moviedemo.model as model
+
+from pylons.i18n import _, ungettext, N_
+from tw.api import WidgetBunch
+
+class Controller(object):
+    """Base class for a web application's controller.
+
+    Currently, this provides positional parameters functionality
+    via a standard default method.
+    """
+
+class BaseController(TGController):
+    """Base class for the root of a web application.
+
+    Your web application should have one of these. The root of
+    your application is used to compute URLs used by your app.
+    """
+
+    def __call__(self, environ, start_response):
+        """Invoke the Controller"""
+        # TGController.__call__ dispatches to the Controller method
+        # the request is routed to. This routing information is
+        # available in environ['pylons.routes_dict']
+        
+        request.identity = request.environ.get('repoze.who.identity')
+        tmpl_context.identity = request.identity
+        return TGController.__call__(self, environ, start_response)

File moviedemo/lib/helpers.py

+from webhelpers import date, feedgenerator, html, number, misc, text 

File moviedemo/model/__init__.py

+"""The application's model objects"""
+
+from zope.sqlalchemy import ZopeTransactionExtension
+from sqlalchemy.orm import scoped_session, sessionmaker
+#from sqlalchemy import MetaData
+from sqlalchemy.ext.declarative import declarative_base
+
+# Global session manager.  DBSession() returns the session object
+# appropriate for the current web request.
+maker = sessionmaker(autoflush=True, autocommit=False,
+                     extension=ZopeTransactionExtension())
+DBSession = scoped_session(maker)
+
+# By default, the data model is defined with SQLAlchemy's declarative
+# extension, but if you need more control, you can switch to the traditional
+# method.
+
+DeclarativeBase = declarative_base()
+
+# Global metadata.
+# The default metadata is the one from the declarative base.
+metadata = DeclarativeBase.metadata
+
+# If you have multiple databases with overlapping table names, you'll need a
+# metadata for each database. Feel free to rename 'metadata2'.
+#metadata2 = MetaData()
+
+#####
+# Generally you will not want to define your table's mappers, and data objects
+# here in __init__ but will want to create modules them in the model directory
+# and import them at the bottom of this file.
+#
+######
+from sqlalchemy import Column, Integer, String, Date, Text, ForeignKey
+from sqlalchemy.orm import relation
+
+class Genre(DeclarativeBase):
+    __tablename__ = "genres"
+    genre_id = Column(Integer, primary_key=True)
+    name = Column(String(100))
+
+class Movie(DeclarativeBase):
+    __tablename__ = "movies"
+    movie_id = Column(Integer, primary_key=True)
+    title = Column(String(100), nullable=False)
+    description = Column(Text, nullable=True)
+    genre_id = Column(Integer, ForeignKey('genres.genre_id'))
+    genre = relation('Genre', backref='movies')
+    release_date = Column(Date, nullable=True)
+
+def init_model(engine):
+    """Call me before using any of the tables or classes in the model."""
+
+    DBSession.configure(bind=engine)
+    # If you are using reflection to introspect your database and create
+    # table objects for you, your tables must be defined and mapped inside
+    # the init_model function, so that the engine is available if you
+    # use the model outside tg2, you need to make sure this is called before
+    # you use the model.
+
+    #
+    # See the following example:
+
+    #global t_reflected
+
+    #t_reflected = Table("Reflected", metadata,
+    #    autoload=True, autoload_with=engine)
+
+    #mapper(Reflected, t_reflected)
+
+# Import your model modules here.
+from moviedemo.model.auth import User, Group, Permission

File moviedemo/model/auth.py

+import sys
+try:
+    from hashlib import sha1
+except ImportError:
+    sys.exit('ImportError: no module named hashlib\nIf you are on python2.4 this library is not part of python. Please install it. Example: easy_install hashlib')
+import os
+from datetime import datetime
+
+from sqlalchemy import Table, ForeignKey, Column
+from sqlalchemy.types import String, Unicode, UnicodeText, Integer, DateTime, \
+                             Boolean, Float
+from sqlalchemy.orm import relation, backref, synonym
+
+from moviedemo.model import DeclarativeBase, metadata, DBSession
+
+
+# This is the association table for the many-to-many relationship between
+# groups and permissions.
+group_permission_table = Table('tg_group_permission', metadata,
+    Column('group_id', Integer, ForeignKey('tg_group.group_id',
+        onupdate="CASCADE", ondelete="CASCADE")),
+    Column('permission_id', Integer, ForeignKey('tg_permission.permission_id',
+        onupdate="CASCADE", ondelete="CASCADE"))
+)
+
+# This is the association table for the many-to-many relationship between
+# groups and members - this is, the memberships.
+user_group_table = Table('tg_user_group', metadata,
+    Column('user_id', Integer, ForeignKey('tg_user.user_id',
+        onupdate="CASCADE", ondelete="CASCADE")),
+    Column('group_id', Integer, ForeignKey('tg_group.group_id',
+        onupdate="CASCADE", ondelete="CASCADE"))
+)
+
+# auth model
+
+class Group(DeclarativeBase):
+    """An ultra-simple group definition.
+    """
+    __tablename__ = 'tg_group'
+
+    group_id = Column(Integer, autoincrement=True, primary_key=True)
+    group_name = Column(Unicode(16), unique=True, nullable=False)
+    display_name = Column(Unicode(255))
+    created = Column(DateTime, default=datetime.now)
+    users = relation('User', secondary=user_group_table, backref='groups')
+
+    def __repr__(self):
+        return '<Group: name=%s>' % self.group_name
+
+    def __unicode__(self):
+        return self.group_name
+
+#
+# The 'info' argument we're passing to the email_address and password columns
+# contain metadata that Rum (http://python-rum.org/) can use generate an
+# admin interface for your models.
+#
+class User(DeclarativeBase):
+    """Reasonably basic User definition. Probably would want additional
+    attributes.
+    """
+    __tablename__ = 'tg_user'
+
+    user_id = Column(Integer, autoincrement=True, primary_key=True)
+    user_name = Column(Unicode(16), unique=True, nullable=False)
+    email_address = Column(Unicode(255), unique=True, nullable=False,
+                           info={'rum': {'field':'Email'}})
+    display_name = Column(Unicode(255))
+    _password = Column('password', Unicode(80),
+                       info={'rum': {'field':'Password'}})
+    created = Column(DateTime, default=datetime.now)
+
+    def __repr__(self):
+        return '<User: email="%s", display name="%s">' % (
+                self.email_address, self.display_name)
+
+    def __unicode__(self):
+        return self.display_name or self.user_name
+
+    @property
+    def permissions(self):
+        perms = set()
+        for g in self.groups:
+            perms = perms | set(g.permissions)
+        return perms
+
+    @classmethod
+    def by_email_address(cls, email):
+        """A class method that can be used to search users
+        based on their email addresses since it is unique.
+        """
+        return DBSession.query(cls).filter(cls.email_address==email).first()
+
+    @classmethod
+    def by_user_name(cls, username):
+        """A class method that permits to search users
+        based on their user_name attribute.
+        """
+        return DBSession.query(cls).filter(cls.user_name==username).first()
+
+
+    def _set_password(self, password):
+        """Hash password on the fly."""
+        hashed_password = password
+
+        if isinstance(password, unicode):
+            password_8bit = password.encode('UTF-8')
+        else:
+            password_8bit = password
+        
+        salt = sha1()
+        salt.update(os.urandom(60))
+        hash = sha1()
+        hash.update(password_8bit + salt.hexdigest())
+        hashed_password = salt.hexdigest() + hash.hexdigest()
+
+        # make sure the hased password is an UTF-8 object at the end of the
+        # process because SQLAlchemy _wants_ a unicode object for Unicode columns
+        if not isinstance(hashed_password, unicode):
+            hashed_password = hashed_password.decode('UTF-8')
+
+        self._password = hashed_password
+
+    def _get_password(self):
+        """returns password
+        """
+        return self._password
+
+    password = synonym('_password', descriptor=property(_get_password,
+                                                        _set_password))
+
+    def validate_password(self, password):
+        """Check the password against existing credentials.
+
+        :param password: the password that was provided by the user to
+            try and authenticate. This is the clear text version that we will
+            need to match against the hashed one in the database.
+        :type password: unicode object.
+        :return: Whether the password is valid.
+        :rtype: bool
+        
+        """
+        hashed_pass = sha1()
+        hashed_pass.update(password + self.password[:40])
+        return self.password[40:] == hashed_pass.hexdigest()
+
+
+class Permission(DeclarativeBase):
+    """A relationship that determines what each Group can do
+    """
+    __tablename__ = 'tg_permission'
+
+    permission_id = Column(Integer, autoincrement=True, primary_key=True)
+    permission_name = Column(Unicode(16), unique=True, nullable=False)
+    description = Column(Unicode(255))
+    groups = relation(Group, secondary=group_permission_table,
+                      backref='permissions')
+
+    def __unicode__(self):
+        return self.permission_name

File moviedemo/model/model.template

+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 moviedemo.model import DeclarativeBase, metadata, DBSession
+
+class SampleModel(DeclarativeBase):
+    __tablename__ = 'sample_model'
+
+    id = Column(Integer, primary_key=True)
+    data = Column(Unicode(255), nullable=False)
+

File moviedemo/public/css/style.css

+html {
+	background: #555555 url('../images/pagebg.png') top left repeat-x;
+	text-align: center;
+	margin: 0;
+	padding: 0;
+}
+
+body {
+	text-align: left;
+	width: 960px;
+	margin: 0 auto;
+	padding: 0;
+	font-size: 12px;
+	font-family:"Lucida Grande","Lucida Sans Unicode",geneva,verdana,sans-serif;
+}
+
+a {
+	color: #286571;
+}
+
+#header {
+	height: 132px;
+	margin: 10px 10px 0 10px;
+	background: url('../images/headerbg.png') top left no-repeat;
+}
+
+#header h1 {
+	padding: 0;
+	margin: 0;
+	padding-top: 30px;
+	padding-left: 180px;
+	color: #fff;
+	position: relative;
+	font-size: 36px;
+}
+
+#header h1 .subtitle {
+	font-size: 60%;
+	position: absolute;
+	left: 240px;
+	top: 70px;
+}
+
+ul#mainmenu {
+	margin: 0;
+	padding: 0 10px;
+	background: url('../images/menubg.png') top left no-repeat;
+	height: 38px;
+}
+
+ul#mainmenu li {
+	list-style-type: none;
+	margin: 0;
+	padding: 0;
+	position: relative;
+	display: inline;
+	float: left;
+}
+
+ul#mainmenu li a {
+	color: #fff;
+	float: left;
+	height: 31px;
+	display: block;
+	line-height: 30px;
+	vertical-align: middle;
+	padding: 0 10px;
+	font-size: 12px;
+	text-decoration: none;
+	background: url('../images/menu-item-border.png') left top no-repeat;
+}
+
+ul#mainmenu li a:hover, ul#mainmenu li a.active {
+	background: url('../images/menu-item-actibg.png') left top no-repeat;
+}
+
+ul#mainmenu li.first a {
+	background: none;
+}
+
+ul#mainmenu li.first a:hover, ul#mainmenu li.first a.active {
+	background: url('../images/menu-item-actibg-first.png') left top no-repeat;
+}
+
+ul#mainmenu li.loginlogout
+{
+    float: right;
+    right: 10px;
+}
+
+ul#mainmenu li.loginlogout a:hover
+{
+    background: url('../images/menu-item-border.png') left top no-repeat;
+}
+
+#content {
+	background: #fff url('../images/contentbg.png') left bottom no-repeat;
+	margin : 0 10px 10px 10px;
+	padding: 0 10px;
+}
+
+#content .currentpage {
+	margin-top: 0;
+}
+
+/* Login form*/
+
+#loginform
+{
+    text-align: center;
+}
+
+form.loginfields
+{
+    text-align: left;
+    width: 270px;
+    background: url('../images/loginbg.png') top left no-repeat;
+    padding: 0;
+    margin: 0 auto;
+    border: 0;
+}
+
+form.loginfields h2
+{
+    font-size: 16px;
+    float: left;
+    padding: 0;
+    margin: 5px;
+    margin-top: 0;
+    background: url('../images/loginheader-left.png') top left no-repeat;
+}
+
+* html form.loginfields h2
+{
+    margin-left: 3px;
+    width: 80px;
+    text-align: center;
+}
+
+form.loginfields h2 span
+{
+    background: url('../images/loginheader-right.png') right top no-repeat;
+    height: 30px;
+    line-height: 30px;
+    display: block;
+    font-size: 100%;
+    font-weight: normal;
+    color: #fff;
+    padding: 0 10px;
+}
+
+*+html form.loginfields h2 span
+{
+    padding: 0 20px;
+}
+
+form.loginfields label
+{
+    clear: left;
+    float: left;
+    margin-top: 5px;
+    margin-left: 10px;
+    width: 65px;
+    line-height: 31px;
+    vertical-align: middle;
+}
+
+form.loginfields input.text
+{
+    float: left;
+    margin-left: 10px;
+    margin-top: 5px;
+    width: 165px;
+    height: 21px;
+    padding: 5px;
+    border: 0;
+    background: url('../images/inputbg.png') top left no-repeat;
+    color: #fff;
+}
+
+form.loginfields input#submit
+{
+    background: url('../images/loginbottombg.png') bottom left no-repeat;
+    width: 270px;
+    height: 61px;
+    border: 0;
+    margin-top: 10px;
+    color: #fff;
+    padding: 10px 140px 20px 10px;
+}
+
+* html form.loginfields input#submit
+{
+    clear: both;
+    margin-left: -10px;
+}
+
+/* Sidebar */
+.sidebar {
+  border: 1px solid #cce;
+  background-color: #eee;
+  margin: 0.5em;
+  margin-left: 1.5em;
+  padding: 1em;
+  float: right;
+  width: 200px;
+  font-size: 88%;
+  background-color: #fffe1;
+  background-repeat:repeat-x;
+}
+
+.sidebar h2 {
+  margin-top: 0;
+  color: black;
+}
+
+.sidebar ul {
+  margin-left: 1.5em;
+  padding-left: 0;
+}
+
+
+
+#sb_top {
+  clear: right;
+}
+
+#sb_bottom {
+  clear: right;
+}
+
+#getting_started {
+  margin-left: 20px;
+}
+
+#getting_started_steps a {
+  text-decoration: none;
+}
+
+#getting_started_steps a:hover {
+  text-decoration: underline;
+}
+
+#getting_started_steps li {
+  margin-bottom: 0.5em;
+}
+
+/* Other and footer */
+#footer {
+	background: #fff;
+	padding: 10px;
+	color:#888888;
+	font-size:90%;
+}
+.flogo {
+	float:left;
+	margin-bottom:0px;
+	padding-left:20px;
+	padding-right:20px;
+	padding-top:0px;
+}
+.foottext p {
+	margin: 0; padding: 0;
+}
+.code {
+font-family:monospace;
+font-size:127%;
+}
+span.code {
+background:#EEEEEE none repeat scroll 0% 0%;
+font-weight:bold;
+}
+#flash {
+font-size:120%;
+font-weight:bolder;
+margin:0pt auto 0.5em;
+width:680px;
+}
+#flash div {
+padding:5px 15px 15px 55px;
+}
+#flash .ok {
+background:#EEEEFF url(../images/ok.png) no-repeat scroll left center;
+border:1px solid #CCCCEE;
+}
+#flash .warning {
+background:#FF9999 url(../images/error.png) no-repeat scroll left center;
+border:1px solid #FF3333;
+}
+#flash .alert {
+background:#FFFF99 url(../images/info.png) no-repeat scroll left center;
+border:1px solid #FFFF00;
+}
+.notice {
+background:#EEEEFF url(../images/info.png) no-repeat scroll left center;
+border:1px solid #CCCCEE;
+margin:0.5em auto;
+padding:15px 10px 15px 55px;
+width:680px;
+}
+.fielderror {
+color:red;
+font-weight:bold;
+}
+div.clearingdiv {
+clear:both;
+}

File moviedemo/public/favicon.ico

Added
New image

File moviedemo/public/images/contentbg.png

Added
New image

File moviedemo/public/images/error.png

Added
New image

File moviedemo/public/images/header_inner2.png

Added
New image

File moviedemo/public/images/headerbg.png

Added
New image

File moviedemo/public/images/info.png

Added
New image

File moviedemo/public/images/inputbg.png

Added
New image

File moviedemo/public/images/loginbg.png

Added
New image

File moviedemo/public/images/loginbottombg.png

Added
New image

File moviedemo/public/images/loginheader-left.png

Added
New image

File moviedemo/public/images/loginheader-right.png

Added
New image

File moviedemo/public/images/menu-item-actibg-first.png

Added
New image

File moviedemo/public/images/menu-item-actibg.png

Added
New image

File moviedemo/public/images/menu-item-border.png

Added
New image

File moviedemo/public/images/menubg.png

Added
New image

File moviedemo/public/images/ok.png

Added
New image

File moviedemo/public/images/pagebg.png

Added
New image

File moviedemo/public/images/star.png

Added
New image

File moviedemo/public/images/strype2.png

Added
New image

File moviedemo/public/images/under_the_hood_blue.png

Added
New image

File moviedemo/templates/__init__.py

Empty file added.

File moviedemo/templates/about.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<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>Learning TurboGears 2.0: Quick guide to the Quickstart pages.</title>
+</head>
+
+<body>
+    ${sidebar_top()}
+    ${sidebar_bottom()}
+  <div id="getting_started">
+    <h2>Architectural basics of a quickstart TG2 site.</h2>
+    <p>The TG2 quickstart command produces this basic TG site.  Here's how it works.</p>
+    <ol id="getting_started_steps">
+      <li class="getting_started">
+        <h3>Code my data model</h3>
+        <p> When you want a model for storing favorite links or wiki content, 
+            the <strong>/model</strong> folder in your site is ready to go.</p>
+        <p> You can build a dynamic site without any data model at all. There 
+            still be a default data-model template for you if you didn't enable 
+            identity in quickstart. If you enable identity, you'll got 
+            identity data-model made for you.</p>
+      </li>
+      <li class="getting_started">
+        <h3>Design my URL structure</h3>
+        <p> The "<span class="code">root.py</span>" file under the 
+            <strong>/controllers</strong> folder has your URLs.  When you 
+            called this url (<span class="code"><a href="${tg.url('/about')}">about</a></span>), 
+            the command went through the RootController class to the 
+            <span class="code">about</span><span class="code">()</span> method.</p>
+        <p> Those Python methods are responsible to create the dictionary of
+             variables that will be used in your web views (template).</p>
+      </li>
+      <li class="getting_started">
+        <h3>Reuse the web page elements</h3>
+        <p> A web page viewed by user could be constructed by single or 
+            several reusable templates under <strong>/templates</strong>. 
+            Take 'about' page for example, each reusable templates generating 
+            a part of the page. We'll cover them in the order of where they are 
+            found, listed near the top of the about.html template</p>
+        <p> <strong><span class="code">header.html</span></strong> - The 
+            "header.html" template contains the HTML code to display the 
+            'header': The blue gradient, TG2 logo, and some site text at the 
+            top of every page it is included on. When the "about.html" template 
+            is called, it includes this "header.html" template (and the others) 
+            with a <span class="code">&lt;xi:include /&gt;</span> tag, part of 
+            the Genshi templating system. The "header.html" template is not a 
+            completely static HTML -- it also dynamically displays the current 
+            page name with a Genshi template method called "replace" with the 
+            code: <span class="code">&lt;span py:replace="page"/&gt;</span>. 
+            It means replace this <span class="code">&lt;span /&gt;</span> 
+            region with the contents found in the variable 'page' that has 
+            been sent in the dictionary to this "about.html" template, and is 
+            available through that namespace for use by this "header.html" 
+            template.  That's how it changes in the header depending on what 
+            page you are visiting.
+        </p>
+        <p> <strong><span class="code">sidebars.html</span></strong> - The
+             sidebars (navigation areas on the right side of the page) are 
+             generated as two separate <span class="code">py:def</span> blocks 
+             in the "sidebars.html" template.  The <span class="code">py:def</span> 
+             construct is best thought of as a "macro" code... a simple way to 
+             separate and reuse common code snippets.  All it takes to include 
+             these on the "about.html" page template is to write  
+             <span class="code">
+<br/><br/>
+    $${sidebar_top()}
+<br/>
+    $${sidebar_bottom()}
+<br/><br/>       
+        </span> in the page where they are wanted.  CSS styling (in 
+        "/public/css/style.css") floats them off to the right side.  You can 
+        remove a sidebar or add more of them, and the CSS will place them one 
+        atop the other.</p>
+        <p>This is, of course, also exactly how the header and footer 
+            templates are also displayed in their proper places, but we'll 
+            cover that in the "master.html" template below.</p>
+        <p>Oh, and in sidebar_top we've added a dynamic menu that shows the 
+            link to this page at the top when you're at the "index" page, and 
+            shows a link to the Home (index) page when you're here.  Study the 
+            "sidebars.html" template to see how we used 
+            <span class="code">py:choose</span> for that.</p>
+        <p> <strong><span class="code">footer.html</span></strong> - The 
+            "footer.html" block is simple, but also utilizes a special 
+            "replace" method to set the current YEAR in the footer copyright 
+            message. The code is: 
+            <span class="code">&lt;span py:replace="now.strftime('%Y')"&gt;
+                </span> and it uses the variable "now" that was passed 
+            in with the dictionary of variables.  But because "now" is a 
+            datetime object, we can use the Python 
+            <span class="code">"strftime()"</span> method with the "replace" 
+            call to say "Just Display The Year Here".  Simple, elegant; we 
+            format the date display in the template (the View in the 
+            Model/View/Controller architecture) rather than formatting it in 
+            the Controller method and sending it to the template as a string 
+            variable.</p>
+        <p> <strong><span class="code">master.html</span></strong> - The 
+            "master.html" template is called last, by design.  The "master.html" 
+            template controls the overall design of the page we're looking at, 
+            calling first the "header" py:def macro, then the putting everything 
+            from this "about.html" template into the "main_content" div, and 
+            then calling the "footer" macro at the end.  Thus the "master.html" 
+            template provides the overall architecture for each page in this 
+            site.</p>
+        <p>But why then shouldn't we call it first?  Isn't it the most 
+            important?  Perhaps, but that's precisely why we call it LAST. 
+            The "master.html" template needs to know where to find everything 
+            else, everything that it will use in py:def macros to build the
+             page.  So that means we call the other templates first, and then 
+             call "master.html". </p>
+        <p>There's more to the "master.html" template... study it to see how 
+           the &lt;title&gt; tags and static JS and CSS files are brought into 
+           the page.  Templating with Genshi is a powerful tool and we've only 
+           scratched the surface.  There are also a few little CSS tricks 
+           hidden in these pages, like the use of a "clearingdiv" to make 
+           sure that your footer stays below the sidebars and always looks 
+           right.  That's not TG2 at work, just CSS.  You'll need all your 
+           skills to build a fine web app, but TG2 will make the hard parts 
+           easier so that you can concentrate more on good design and content 
+           rather than struggling with mechanics.</p>
+      </li>
+    </ol>
+    <p>Good luck with TurboGears 2!</p>
+  </div>
+</body>
+</html>

File moviedemo/templates/authentication.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<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>Learning TurboGears 2.0: Quick guide to authentication.</title>
+</head>
+
+<body>
+    ${sidebar_top()}
+    ${sidebar_bottom()}
+  <div id="getting_started">
+    <h2>Authentication &amp; Authorization in a TG2 site.</h2>
+    <p>If you have access to this page, this means you have enabled authentication and authorization
+    in the quickstart to create your project.</p>
+    <p>
+    The paster command will have created a few specific controllers for you. But before you
+    go to play with those controllers you'll need to make sure your application has been
+    properly bootstapped.
+    This is dead easy, here is how to do this:
+    </p>
+
+    <span class="code">
+    paster setup-app development.ini
+    </span>
+
+    <p>
+    inside your application's folder and you'll get a database setup (using the preferences you have
+    set in your development.ini file). This database will also have been prepopulated with some
+    default logins/passwords so that you can test the secured controllers and methods.
+    </p>
+    <p>
+    To change the comportement of this setup-app command you just need to edit the <span class="code">websetup.py</span> file.
+    </p>
+    <p>
+    Now try to visiting the <a href="${tg.url('/manage_permission_only')}">manage_permission_only</a> URL. You will be challenged with a login/password form.
+    </p>
+    <p>
+    Only managers are authorized to visit this method. You will need to log-in using:
+        <p>
+        <span class="code">
+        login: manager
+        </span>
+        </p>
+        <p>
+        <span class="code">
+        password: managepass
+        </span>
+        </p>
+    </p>
+    <p>
+    Another protected resource is <a href="${tg.url('/editor_user_only')}">editor_user_only</a>. This one is protected by a different set of permissions.
+    You will need to be <span class="code">editor</span> with a password of <span class="code">editpass</span> to be able to access it.
+    </p>
+    <p>
+    The last kind of protected resource in this quickstarted app is a full so called <a href="${tg.url('/secc')}">secure controller</a>. This controller is protected globally.
+    Instead of having a @require decorator on each method, we have set an allow_only attribute at the class level. All the methods in this controller will
+    require the same level of access. You need to be manager to access <a href="${tg.url('/secc')}">secc</a> or <a href="${tg.url('/secc/some_where')}">secc/some_where</a>.
+    </p>
+  </div>
+</body>
+</html>

File moviedemo/templates/debug.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<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>Sample Template, for looking at template locals</title>
+</head>
+
+<body>
+    <h1>All objects from locals():</h1>
+
+    <div py:for="item in sorted(locals()['data'].keys())">
+      ${item}: ${repr(locals()['data'][item])}</div>
+</body>
+</html>

File moviedemo/templates/error.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<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>A ${code} Error has Occurred </title>
+</head>
+
+<body>
+<h1>Error ${code}</h1>
+
+<div>${XML(message)}</div>
+</body>
+</html>

File moviedemo/templates/footer.html

+<html xmlns:py="http://genshi.edgewall.org/"
+      xmlns:xi="http://www.w3.org/2001/XInclude"
+      py:strip="">
+<py:def function="footer">
+<div id="footer"> 
+  <div class="flogo">
+    <img src="${tg.url('/images/under_the_hood_blue.png')}" alt="TurboGears" />
+    <p><a href="http://www.turbogears.org/2.0/">Powered by TurboGears 2</a></p>
+  </div>
+  <div class="foottext">
+    <p>TurboGears is a open source front-to-back web development
+      framework written in Python. Copyright (c) 2005-2008 </p>
+  </div>
+  <div class="clearingdiv"></div>
+</div>
+</py:def>    
+</html>

File moviedemo/templates/header.html

+<html xmlns:py="http://genshi.edgewall.org/"
+      xmlns:xi="http://www.w3.org/2001/XInclude"
+      py:strip="">
+<py:def function="header">
+  <div id="header">
+  	<h1>
+  		Welcome to TurboGears 2
+		<span class="subtitle">The Python web metaframework</span>
+	</h1>
+  </div>
+</py:def>
+</html>

File moviedemo/templates/index.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<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>
+    ${sidebar_top()}
+  <div id="getting_started">
+    <h2>Presentation</h2>
+    <p>TurboGears 2 is rapid web application development toolkit designed to make your life easier.</p>
+    <ol id="getting_started_steps">
+      <li class="getting_started">
+        <h3>Code your data model</h3>
+        <p> Design your data model, Create the database, and Add some bootstrap data.</p>
+      </li>
+      <li class="getting_started">
+        <h3>Design your URL architecture</h3>
+        <p> Decide your URLs, Program your controller methods, Design your 
+            templates, and place some static files (CSS and/or JavaScript). </p>
+      </li>
+      <li class="getting_started">
+        <h3>Distribute your app</h3>
+        <p> Test your source, Generate project documents, Build a distribution.</p>
+      </li>
+    </ol>
+  </div>
+  <div class="clearingdiv" />
+  <div class="notice"> Thank you for choosing TurboGears. 
+  </div>
+</body>
+</html>

File moviedemo/templates/login.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+                    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<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>