Commits

Ralph Bean  committed 00a9cc2

Turbogears quickstart.

  • Participants
  • Parent commits 08993e6

Comments (0)

Files changed (78)

File myapp/MANIFEST.in

+recursive-include myapp/public *
+include myapp/public/favicon.ico
+recursive-include myapp/i18n *
+recursive-include myapp/templates *

File myapp/README.txt

+This file is for you to describe the myapp application. Typically
+you would include information such as the information below:
+
+Installation and Setup
+======================
+
+Install ``myapp`` using the setup.py script::
+
+    $ cd myapp
+    $ 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 myapp/development.ini

+#
+# myapp - 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
+
+[sa_auth]
+cookie_secret = 5581a529-c2cc-49c1-8f27-d2c967d9648e
+
+[app:main]
+use = egg:myapp
+full_stack = true
+#lang = ru
+cache_dir = %(here)s/data
+beaker.session.key = myapp
+beaker.session.secret = 5581a529-c2cc-49c1-8f27-d2c967d9648e
+
+# 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
+
+# This line ensures that Genshi will render xhtml when sending the
+# output. Change to html or xml, as desired.
+templating.genshi.method = xhtml
+
+# if you are using Mako and want to be able to reload
+# the mako template from disk during the development phase
+# you should say 'true' here
+# This option is only used for mako templating engine
+# WARNING: if you want to deploy your application using a zipped egg
+# (ie: if your application's setup.py defines zip-safe=True, then you
+# MUST put "false" for the production environment because there will
+# be no disk and real files to compare time with.
+# On the contrary if your application defines zip-safe=False and is
+# deployed in an unzipped manner, then you can leave this option to true
+templating.mako.reloadfromdisk = true
+
+# the compiled template dir is a directory that must be readable by your
+# webserver. It will be used to store the resulting templates once compiled
+# by the TemplateLookup system.
+# During development you generally don't need this option since paste's HTTP
+# server will have access to you development directories, but in production
+# you'll most certainly want to have apache or nginx to write in a directory
+# that does not contain any source code in any form for obvious security reasons.
+templating.mako.compiled_templates_dir = %(here)s/data/templates
+
+# 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
+
+#turn this setting to "min" if you would like tw to produce minified
+#javascript files (if your library supports that)
+toscawidgets.framework.resource_variant=debug
+
+[loggers]
+keys = root, myapp, 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_myapp]
+level = DEBUG
+handlers =
+qualname = myapp
+
+[logger_sqlalchemy]
+level = INFO
+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 myapp/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 myapp/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 myapp/migration/__init__.py

Empty file added.

File myapp/migration/migrate.cfg

+[db_settings]
+# Used to identify which repository this database is versioned under.
+# You can use the name of your project.
+repository_id=migration
+
+# The name of the database table used to track the schema version.
+# This name shouldn't already be used by your project.
+# If this is changed once a database is under version control, you'll need to 
+# change the table name in each database too. 
+version_table=migrate_version
+
+# When committing a change script, Migrate will attempt to generate the 
+# sql for all supported databases; normally, if one of them fails - probably
+# because you don't have that database installed - it is ignored and the 
+# commit continues, perhaps ending successfully. 
+# Databases in this list MUST compile successfully during a commit, or the 
+# entire commit will fail. List the databases your application will actually 
+# be using to ensure your updates to that database work properly.
+# This must be a list; example: ['postgres','sqlite']
+required_dbs=[]

File myapp/migration/versions/__init__.py

Empty file added.

File myapp/myapp/__init__.py

+# -*- coding: utf-8 -*-
+"""The myapp package"""

File myapp/myapp/config/__init__.py

+# -*- coding: utf-8 -*-
+

File myapp/myapp/config/app_cfg.py

+# -*- coding: utf-8 -*-
+"""
+Global configuration file for TG2-specific settings in myapp.
+
+This file complements development/deployment.ini.
+
+Please note that **all the argument values are strings**. If you want to
+convert them into boolean, for example, you should use the
+:func:`paste.deploy.converters.asbool` function, as in::
+    
+    from paste.deploy.converters import asbool
+    setting = asbool(global_conf.get('the_setting'))
+ 
+"""
+
+from tg.configuration import AppConfig
+
+import myapp
+from myapp import model
+from myapp.lib import app_globals, helpers 
+
+base_config = AppConfig()
+base_config.renderers = []
+
+base_config.package = myapp
+
+#Enable json in expose
+base_config.renderers.append('json')
+#Set the default renderer
+base_config.default_renderer = 'mako'
+base_config.renderers.append('mako')
+#Configure the base SQLALchemy Setup
+base_config.use_sqlalchemy = True
+base_config.model = myapp.model
+base_config.DBSession = myapp.model.DBSession
+# Configure the authentication backend
+
+# YOU MUST CHANGE THIS VALUE IN PRODUCTION TO SECURE YOUR APP 
+base_config.sa_auth.cookie_secret = "ChangeME" 
+
+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
+
+# override this if you are using a different charset for the login form
+base_config.sa_auth.charset = 'utf-8'
+
+# 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 myapp/myapp/config/deployment.ini_tmpl

+#
+# myapp - 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
+
+[sa_auth]
+cookie_secret = ${app_instance_secret}
+
+[app:main]
+use = egg:myapp
+full_stack = true
+cache_dir = %(here)s/data
+beaker.session.key = myapp
+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
+
+# This line ensures that Genshi will render xhtml when sending the
+# output. Change to html or xml, as desired.
+templating.genshi.method = xhtml
+
+# 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, myapp, 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_myapp]
+level = INFO
+handlers =
+qualname = myapp
+
+[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 = %Y-%m-%d %H:%M:%S

File myapp/myapp/config/environment.py

+# -*- coding: utf-8 -*-
+"""WSGI environment setup for myapp."""
+
+from myapp.config.app_cfg import base_config
+
+__all__ = ['load_environment']
+
+#Use base_config to setup the environment loader function
+load_environment = base_config.make_load_environment()

File myapp/myapp/config/middleware.py

+# -*- coding: utf-8 -*-
+"""WSGI middleware initialization for the myapp application."""
+
+from myapp.config.app_cfg import base_config
+from myapp.config.environment import load_environment
+
+
+__all__ = ['make_app']
+
+# Use base_config to setup the necessary PasteDeploy application 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):
+    """
+    Set myapp up with the settings found in the PasteDeploy configuration
+    file used.
+    
+    :param global_conf: The global settings for myapp (those
+        defined under the ``[DEFAULT]`` section).
+    :type global_conf: dict
+    :param full_stack: Should the whole TG2 stack be set up?
+    :type full_stack: str or bool
+    :return: The myapp application with all the relevant middleware
+        loaded.
+    
+    This is the PasteDeploy factory for the myapp application.
+    
+    ``app_conf`` contains all the application-specific settings (those defined
+    under ``[app:main]``.
+    
+   
+    """
+    app = make_base_app(global_conf, full_stack=True, **app_conf)
+    
+    # Wrap your base TurboGears 2 application with custom middleware here
+    
+    return app

File myapp/myapp/controllers/__init__.py

+# -*- coding: utf-8 -*-
+"""Controllers for the myapp application."""

File myapp/myapp/controllers/controller.template

+# -*- coding: utf-8 -*-
+"""Sample controller module"""
+
+# 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 myapp.lib.base import BaseController
+#from myapp.model import DBSession, metadata
+
+
+class SampleController(BaseController):
+    #Uncomment this line if your controller requires an authenticated user
+    #allow_only = authorize.not_anonymous()
+    
+    @expose('myapp.templates.index')
+    def index(self):
+        return dict(page='index')

File myapp/myapp/controllers/error.py

+# -*- coding: utf-8 -*-
+"""Error controller"""
+
+from tg import request, expose
+
+__all__ = ['ErrorController']
+
+
+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.
+    
+    """
+
+    @expose('myapp.templates.error')
+    def document(self, *args, **kwargs):
+        """Render the error document"""
+        resp = 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=request.environ.get('SCRIPT_NAME', ''),
+                      code=request.params.get('code', resp.status_int),
+                      message=request.params.get('message', default_message))
+        return values

File myapp/myapp/controllers/root.py

+# -*- coding: utf-8 -*-
+"""Main Controller"""
+
+from tg import expose, flash, require, url, request, redirect
+from pylons.i18n import ugettext as _, lazy_ugettext as l_
+from tgext.admin.tgadminconfig import TGAdminConfig
+from tgext.admin.controller import AdminController
+from repoze.what import predicates
+
+from myapp.lib.base import BaseController
+from myapp.model import DBSession, metadata
+from myapp import model
+from myapp.controllers.secure import SecureController
+
+from myapp.controllers.error import ErrorController
+
+__all__ = ['RootController']
+
+
+class RootController(BaseController):
+    """
+    The root controller for the myapp application.
+
+    All the other controllers and WSGI applications should be mounted on this
+    controller. For example::
+
+        panel = ControlPanelController()
+        another_app = AnotherWSGIApplication()
+
+    Keep in mind that WSGI applications shouldn't be mounted directly: They
+    must be wrapped around with :class:`tg.controllers.WSGIAppController`.
+
+    """
+    secc = SecureController()
+
+    admin = AdminController(model, DBSession, config_type=TGAdminConfig)
+
+    error = ErrorController()
+
+    @expose('myapp.templates.index')
+    def index(self):
+        """Handle the front-page."""
+        return dict(page='index')
+
+    @expose('myapp.templates.about')
+    def about(self):
+        """Handle the 'about' page."""
+        return dict(page='about')
+
+    @expose('myapp.templates.environ')
+    def environ(self):
+        """This method showcases TG's access to the wsgi environment."""
+        return dict(environment=request.environ)
+
+    @expose('myapp.templates.data')
+    @expose('json')
+    def data(self, **kw):
+        """This method showcases how you can use the same controller for a data page and a display page"""
+        return dict(params=kw)
+
+    @expose('myapp.templates.authentication')
+    def auth(self):
+        """Display some information about auth* on this application."""
+        return dict(page='auth')
+    @expose('myapp.templates.index')
+    @require(predicates.has_permission('manage', msg=l_('Only for managers')))
+    def manage_permission_only(self, **kw):
+        """Illustrate how a page for managers only works."""
+        return dict(page='managers stuff')
+
+    @expose('myapp.templates.index')
+    @require(predicates.is_user('editor', msg=l_('Only for the editor')))
+    def editor_user_only(self, **kw):
+        """Illustrate how a page exclusive for the editor works."""
+        return dict(page='editor stuff')
+
+    @expose('myapp.templates.login')
+    def login(self, came_from=url('/')):
+        """Start the user login."""
+        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='/'):
+        """
+        Redirect the user to the initially requested page on successful
+        authentication or redirect her back to the login page if login failed.
+
+        """
+        if not request.identity:
+            login_counter = request.environ['repoze.who.logins'] + 1
+            redirect('/login',
+                params=dict(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('/')):
+        """
+        Redirect the user to the initially requested page on logout and say
+        goodbye as well.
+
+        """
+        flash(_('We hope to see you soon!'))
+        redirect(came_from)

File myapp/myapp/controllers/secure.py

+# -*- coding: utf-8 -*-
+"""Sample controller with all its actions protected."""
+from tg import expose, flash
+from pylons.i18n import ugettext as _, lazy_ugettext as l_
+from repoze.what.predicates import has_permission
+#from dbsprockets.dbmechanic.frameworks.tg2 import DBMechanic
+#from dbsprockets.saprovider import SAProvider
+
+from myapp.lib.base import BaseController
+#from myapp.model import DBSession, metadata
+
+__all__ = ['SecureController']
+
+
+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=l_('Only for people with the "manage" permission'))
+    
+    @expose('myapp.templates.index')
+    def index(self):
+        """Let the user know that's visiting a protected controller."""
+        flash(_("Secure Controller here"))
+        return dict(page='index')
+    
+    @expose('myapp.templates.index')
+    def some_where(self):
+        """Let the user know that this action is protected too."""
+        return dict(page='some_where')

File myapp/myapp/controllers/template.py

+# -*- coding: utf-8 -*-
+"""Fallback controller."""
+
+from myapp.lib.base import BaseController
+from tg import abort
+
+__all__ = ['TemplateController']
+
+
+class TemplateController(BaseController):
+    """
+    The fallback controller for myapp.
+    
+    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)
+    
+    """
+    
+    def view(self, url):
+        """Abort the request with a 404 HTTP status code."""
+        abort(404)

File myapp/myapp/i18n/ru/LC_MESSAGES/myapp.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 myapp/myapp/lib/__init__.py

+# -*- coding: utf-8 -*-
+

File myapp/myapp/lib/app_globals.py

+# -*- coding: utf-8 -*-
+
+"""The application's Globals object"""
+
+__all__ = ['Globals']
+
+
+class Globals(object):
+    """Container for objects available throughout the life of the application.
+
+    One instance of Globals is created during application initialization and
+    is available during requests via the 'app_globals' variable.
+
+    """
+
+    def __init__(self):
+        """Do nothing, by default."""
+        pass

File myapp/myapp/lib/base.py

+# -*- coding: utf-8 -*-
+
+"""The base Controller API."""
+
+from tg import TGController, tmpl_context
+from tg.render import render
+from tg import request
+from pylons.i18n import _, ungettext, N_
+import myapp.model as model
+
+__all__ = ['BaseController']
+
+
+class BaseController(TGController):
+    """
+    Base class for the controllers in the 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 myapp/myapp/lib/helpers.py

+# -*- coding: utf-8 -*-
+
+"""WebHelpers used in myapp."""
+
+from webhelpers import date, feedgenerator, html, number, misc, text

File myapp/myapp/model/__init__.py

+# -*- coding: utf-8 -*-
+"""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 Thread-local
+# session object appropriate for the current web request.
+maker = sessionmaker(autoflush=True, autocommit=False,
+                     extension=ZopeTransactionExtension())
+DBSession = scoped_session(maker)
+
+# Base class for all of our model classes: 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()
+
+# There are two convenient ways for you to spare some typing.
+# You can have a query property on all your model classes by doing this:
+# DeclarativeBase.query = DBSession.query_property()
+# Or you can use a session-aware mapper as it was used in TurboGears 1:
+# DeclarativeBase = declarative_base(mapper=DBSession.mapper)
+
+# 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.
+#
+######
+
+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 myapp.model.auth import User, Group, Permission

File myapp/myapp/model/auth.py

+# -*- coding: utf-8 -*-
+"""
+Auth* related model.
+
+This is where the models used by :mod:`repoze.who` and :mod:`repoze.what` are
+defined.
+
+It's perfectly fine to re-use this definition in the myapp application,
+though.
+
+"""
+import os
+from datetime import datetime
+import sys
+try:
+    from hashlib import sha1
+except ImportError:
+    sys.exit('ImportError: No module named hashlib\n'
+             'If you are on python2.4 this library is not part of python. '
+             'Please install it. Example: easy_install hashlib')
+
+from sqlalchemy import Table, ForeignKey, Column
+from sqlalchemy.types import Unicode, Integer, DateTime
+from sqlalchemy.orm import relation, synonym
+
+from myapp.model import DeclarativeBase, metadata, DBSession
+
+__all__ = ['User', 'Group', 'Permission']
+
+
+#{ Association tables
+
+
+# This is the association table for the many-to-many relationship between
+# groups and permissions. This is required by repoze.what.
+group_permission_table = Table('tg_group_permission', metadata,
+    Column('group_id', Integer, ForeignKey('tg_group.group_id',
+        onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
+    Column('permission_id', Integer, ForeignKey('tg_permission.permission_id',
+        onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
+)
+
+# This is the association table for the many-to-many relationship between
+# groups and members - this is, the memberships. It's required by repoze.what.
+user_group_table = Table('tg_user_group', metadata,
+    Column('user_id', Integer, ForeignKey('tg_user.user_id',
+        onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
+    Column('group_id', Integer, ForeignKey('tg_group.group_id',
+        onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
+)
+
+
+#{ The auth* model itself
+
+
+class Group(DeclarativeBase):
+    """
+    Group definition for :mod:`repoze.what`.
+
+    Only the ``group_name`` column is required by :mod:`repoze.what`.
+
+    """
+
+    __tablename__ = 'tg_group'
+
+    #{ Columns
+
+    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)
+
+    #{ Relations
+
+    users = relation('User', secondary=user_group_table, backref='groups')
+
+    #{ Special methods
+
+    def __repr__(self):
+        return ('<Group: name=%s>' % self.group_name).encode('utf-8')
+
+    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):
+    """
+    User definition.
+
+    This is the user definition used by :mod:`repoze.who`, which requires at
+    least the ``user_name`` column.
+
+    """
+    __tablename__ = 'tg_user'
+
+    #{ Columns
+
+    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)
+
+    #{ Special methods
+
+    def __repr__(self):
+        return ('<User: name=%r, email=%r, display=%r>' % (
+                self.user_name, self.email_address, self.display_name)).encode('utf-8')
+
+    def __unicode__(self):
+        return self.display_name or self.user_name
+
+    #{ Getters and setters
+
+    @property
+    def permissions(self):
+        """Return a set with all permissions granted to the user."""
+        perms = set()
+        for g in self.groups:
+            perms = perms | set(g.permissions)
+        return perms
+
+    @classmethod
+    def by_email_address(cls, email):
+        """Return the user object whose email address is ``email``."""
+        return DBSession.query(cls).filter_by(email_address=email).first()
+
+    @classmethod
+    def by_user_name(cls, username):
+        """Return the user object whose user name is ``username``."""
+        return DBSession.query(cls).filter_by(user_name=username).first()
+
+    def _set_password(self, password):
+        """Hash ``password`` on the fly and store its hashed version."""
+        # Make sure password is a str because we cannot hash unicode objects
+        if isinstance(password, unicode):
+            password = password.encode('utf-8')
+        salt = sha1()
+        salt.update(os.urandom(60))
+        hash = sha1()
+        hash.update(password + salt.hexdigest())
+        password = salt.hexdigest() + hash.hexdigest()
+        # Make sure the hashed password is a unicode object at the end of the
+        # process because SQLAlchemy _wants_ unicode objects for Unicode cols
+        if not isinstance(password, unicode):
+            password = password.decode('utf-8')
+        self._password = password
+
+    def _get_password(self):
+        """Return the hashed version of the 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
+
+        """
+        hash = sha1()
+        if isinstance(password, unicode):
+            password = password.encode('utf-8')
+        hash.update(password + str(self.password[:40]))
+        return self.password[40:] == hash.hexdigest()
+
+
+class Permission(DeclarativeBase):
+    """
+    Permission definition for :mod:`repoze.what`.
+
+    Only the ``permission_name`` column is required by :mod:`repoze.what`.
+
+    """
+
+    __tablename__ = 'tg_permission'
+
+    #{ Columns
+
+    permission_id = Column(Integer, autoincrement=True, primary_key=True)
+
+    permission_name = Column(Unicode(63), unique=True, nullable=False)
+
+    description = Column(Unicode(255))
+
+    #{ Relations
+
+    groups = relation(Group, secondary=group_permission_table,
+                      backref='permissions')
+
+    #{ Special methods
+
+    def __repr__(self):
+        return ('<Permission: name=%r>' % self.permission_name).encode('utf-8')
+
+    def __unicode__(self):
+        return self.permission_name
+
+    #}
+
+
+#}

File myapp/myapp/model/model.template

+# -*- coding: utf-8 -*-
+"""Sample model module."""
+
+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 myapp.model import DeclarativeBase, metadata, DBSession
+
+
+class SampleModel(DeclarativeBase):
+    __tablename__ = 'sample_model'
+    
+    #{ Columns
+    
+    id = Column(Integer, primary_key=True)
+    
+    data = Column(Unicode(255), nullable=False)
+    
+    #}

File myapp/myapp/public/css/admin.css

+.admin_grid{
+    margin-top: 1em;
+    width: 400px;
+}
+.admin_grid img{
+    border: 0;
+    vertical-align: middle;
+}
+
+a.add_link, a.edit_link{
+    background-repeat: no-repeat;
+    padding-left: 20px;
+}
+a.add_link{
+    background-image: url('/images/add.png');
+}
+a.edit_link{
+    background-image: url('/images/pencil.png');
+}
+
+/* admin list view */
+.crud_table .grid {
+    border-collapse: collapse;
+    border: 1px solid #ccc;
+}
+.crud_table .grid .odd{
+    background-color: #eee;
+}
+.crud_table .grid th{
+    background: url("/images/menubg.png") no-repeat scroll -20px top transparent;
+    color: #fff;
+    font-weight: normal;
+    text-transform: capitalize;
+}
+.crud_table .grid td a{
+    color: #286571;
+}
+.crud_table .grid td.col_0 div{
+    float: left;
+}
+.crud_table .grid td.col_0 .edit_link, .crud_table .grid td.col_0 .delete-button{
+    display:block;
+    height:17px;
+    width:20px;
+    padding:0;
+    text-indent:-400px;
+    background-repeat: no-repeat;
+}
+.crud_table .grid td.col_0 .delete-button{
+    font-size: inherit;
+    background-image: url('/images/delete.png');
+    cursor: pointer;
+}
+
+/* admin add/edit view */
+.crud_edit label, .crud_add label{
+    font-size: 12px;
+}
+.crud_edit input, .crud_add input,
+.crud_edit select, .crud_add select{
+    width: 15em;
+}

File myapp/myapp/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;
+	overflow: hidden;
+}
+
+#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 #loginremember
+{
+    float:right;
+    vertical-align: middle;
+    height: 26px;
+}
+
+form.loginfields #labelremember 
+{
+    float: right;
+    width: auto;
+    margin:0;
+    margin-right: 15px;
+    vertical-align: middle;
+}
+
+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%;
+}
+
+.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, .notice {
+font-size:120%;
+font-weight:bolder;
+margin:0pt auto 0.5em;
+width:680px;
+}
+#flash div, .notice {
+padding:20px 15px 20px 65px;
+}
+#flash .ok {
+background:#d8ecd8 url(../images/ok.png) no-repeat scroll 10px center;
+}
+#flash .warning {
+background:#fff483 url(../images/warning.png) no-repeat scroll 10px center;
+}
+#flash .error {
+background:#f9c5c1 url(../images/error.png) no-repeat scroll 10px center;
+}
+#flash .alert,
+#flash .info {
+background:#EEEEFF url(../images/info.png) no-repeat scroll 10px center;
+}
+.notice {
+background:#EEEEFF url(../images/info.png) no-repeat scroll 10px center;
+}
+.fielderror {
+color:red;
+font-weight:bold;
+}
+div.clearingdiv {
+clear:both;
+}

File myapp/myapp/public/favicon.ico

Added
New image

File myapp/myapp/public/images/add.png

Added
New image

File myapp/myapp/public/images/contentbg.png

Added
New image

File myapp/myapp/public/images/delete.png

Added
New image

File myapp/myapp/public/images/error.png

Added
New image

File myapp/myapp/public/images/header_inner2.png

Added
New image

File myapp/myapp/public/images/headerbg.png

Added
New image

File myapp/myapp/public/images/info.png

Added
New image

File myapp/myapp/public/images/inputbg.png

Added
New image

File myapp/myapp/public/images/loginbg.png

Added
New image

File myapp/myapp/public/images/loginbottombg.png

Added
New image

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

Added
New image

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

Added
New image

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

Added
New image

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

Added
New image

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

Added
New image

File myapp/myapp/public/images/menubg.png

Added
New image

File myapp/myapp/public/images/ok.png

Added
New image

File myapp/myapp/public/images/pagebg.png

Added
New image

File myapp/myapp/public/images/pencil.png

Added
New image

File myapp/myapp/public/images/star.png

Added
New image

File myapp/myapp/public/images/strype2.png

Added
New image

File myapp/myapp/public/images/under_the_hood_blue.png

Added
New image

File myapp/myapp/public/images/warning.png

Added
New image

File myapp/myapp/templates/__init__.py

Empty file added.

File myapp/myapp/templates/about.mak

+<%inherit file="local:templates.master"/>
+
+<%def name="title()">
+Learning TurboGears 2.1: Quick guide to the Quickstart pages.
+</%def>
+
+${parent.sidebar_top()}
+${parent.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 
+            authentication and authorization in quickstart. If you have enabled
+            authorization, the auth data-model is ready-made.</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 the url or this page (<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 template generating 
+            a part of the page.</p>
+
+        <p> <strong><span class="code">about.mak</span></strong> - This is the
+        template that created this page.  If you take a look at this template
+        you will see that there are a lot of wacky symbols, if you are not familiar
+        with <a href="http://www.makotemplates.org">Mako</a> you should probably
+        check out the docs on their site.  </p>
+        
+        <p>Let's take a look at what this template does in order of execution.
+        The first thing this template does
+        is inherit from <strong><span class="code">master.mak</span></strong>. We'll
+        go into the details of this later, but for now just know that this
+        template is allowed to both call on master.mak, and also override it's
+        capabilites.  This inheritance is what gives mako it's power to
+        provide reusable templates.
+        </p>
+        <p>But um, whats that 'local:' stuff about? </p>
+        <p>
+        Well, TG wants to provide re-usable components like tgext.admin, and also
+        provide a way for you to use your default site layouts.  This is done
+        easily by providing a shortcut to the namespace of your project, so the
+        component template finder can find <strong>your</strong> master.html and format 
+        itself the way you want it to.
+        </p>
+        <p> The next thing about.mak does is to create a function called title()
+        which provides the title for the document.  This overrides the title
+        method provided by master.mak, and therefore, when the template
+        is rendered, it will use the title provided by this funciton.
+        
+        <p>
+        Next, there are a couple of calls to the master template to set up the 
+        boxes you see in the page over on the right, We'll examine what
+        these are in the master template in a second.  Finally, we get to the meat
+        of the document, and that's pretty much all about.mak does
+        </p>
+        
+        <p> <strong><span class="code">master.mak</span></strong> -
+        This template provides the overall layout for your project, and
+        allows you to override different elements of the overall structure
+        of your default look-and-feel.  Let's take a look at this template
+        from top to bottom again.</p>
+        <p>The first 15 lines provide an overall layout for the document,
+        with definitions for the header, title and body.  Each one of these
+        sections has been turned into a method that you can change within your
+        child templates, but you do not have to provide any single one of them
+        TurboGears extensions may however expect these methods to be provided
+        to display their content properly.  Keep this in mind if you decide
+        to alter your master.mak.  You are of course always free to modify
+        your master template, but doing so can render your extensions useless,
+        so tread carefully.</p>
+        
+        <p> The next section describes the overall layout for the body of the
+        document, with flash messages appearing at the top, and self.body()
+        appearing at the bottom.  self.body will take whatever is not defined
+        as a method in your child template and render it in this space.  This
+        is really useful because it allows you to be freestyle with your
+        body definition.  If you don't want to override any of the master
+        template defines, all you have to do is inherit the master, and then
+        provide the code you want to appear surrounded by your header and footer.
+        </p>
+        
+        <p>
+        Next are the title, header, footer and sidebar defines.  These are all of
+        course overriddable.