diff options
author | Luke Macken <lmacken@redhat.com> | 2008-06-27 21:43:09 -0400 |
---|---|---|
committer | Luke Macken <lmacken@redhat.com> | 2008-06-27 21:43:09 -0400 |
commit | ef6aa096bbb8f52c2bad76f3b086167db326da89 (patch) | |
tree | 022e2363dbfb8879948d89cfcc7ae1f2f53b64a0 | |
download | manas-ef6aa096bbb8f52c2bad76f3b086167db326da89.tar.gz manas-ef6aa096bbb8f52c2bad76f3b086167db326da89.tar.xz manas-ef6aa096bbb8f52c2bad76f3b086167db326da89.zip |
Initial TG2 quickstart
62 files changed, 1641 insertions, 0 deletions
diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..2796e80 --- /dev/null +++ b/README.txt @@ -0,0 +1,19 @@ +This file is for you to describe the manas application. Typically +you would include information such as the information below: + +Installation and Setup +====================== + +Install ``manas`` using easy_install:: + + easy_install manas + +Make a config file as follows:: + + paster make-config manas config.ini + +Tweak the config file as appropriate and then setup the application:: + + paster setup-app config.ini + +Then you are ready to go. diff --git a/development.ini b/development.ini new file mode 100644 index 0000000..b44e1b4 --- /dev/null +++ b/development.ini @@ -0,0 +1,98 @@ +# +# manas - 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 = 0.0.0.0 +port = 8080 + +[app:main] +use = egg:manas +full_stack = true +#lang = ru +cache_dir = %(here)s/data +beaker.session.key = manas +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:port@hostname/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 +sqlalchemy.echo = true +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, manas, sqlalchemy + +[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_manas] +level = DEBUG +handlers = +qualname = manas + +[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.) + +# 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 diff --git a/ez_setup/README.txt b/ez_setup/README.txt new file mode 100644 index 0000000..77c986d --- /dev/null +++ b/ez_setup/README.txt @@ -0,0 +1,14 @@ +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. diff --git a/ez_setup/__init__.py b/ez_setup/__init__.py new file mode 100644 index 0000000..6473c14 --- /dev/null +++ b/ez_setup/__init__.py @@ -0,0 +1,229 @@ +#!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:]) diff --git a/manas.egg-info/PKG-INFO b/manas.egg-info/PKG-INFO new file mode 100644 index 0000000..ec54845 --- /dev/null +++ b/manas.egg-info/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: manas +Version: 0.1dev +Summary: UNKNOWN +Home-page: UNKNOWN +Author: UNKNOWN +Author-email: UNKNOWN +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN diff --git a/manas.egg-info/SOURCES.txt b/manas.egg-info/SOURCES.txt new file mode 100644 index 0000000..b85862e --- /dev/null +++ b/manas.egg-info/SOURCES.txt @@ -0,0 +1,32 @@ +README.txt +setup.cfg +setup.py +manas/__init__.py +manas/websetup.py +manas.egg-info/PKG-INFO +manas.egg-info/SOURCES.txt +manas.egg-info/dependency_links.txt +manas.egg-info/entry_points.txt +manas.egg-info/paste_deploy_config.ini_tmpl +manas.egg-info/paster_plugins.txt +manas.egg-info/requires.txt +manas.egg-info/top_level.txt +manas/config/__init__.py +manas/config/app_cfg.py +manas/config/environment.py +manas/config/middleware.py +manas/controllers/__init__.py +manas/controllers/error.py +manas/controllers/root.py +manas/controllers/secc.py +manas/controllers/template.py +manas/lib/__init__.py +manas/lib/app_globals.py +manas/lib/base.py +manas/lib/helpers.py +manas/model/__init__.py +manas/model/identity.py +manas/templates/__init__.py +manas/tests/__init__.py +manas/tests/test_models.py +manas/tests/functional/__init__.py
\ No newline at end of file diff --git a/manas.egg-info/dependency_links.txt b/manas.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/manas.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/manas.egg-info/entry_points.txt b/manas.egg-info/entry_points.txt new file mode 100644 index 0000000..5bda1da --- /dev/null +++ b/manas.egg-info/entry_points.txt @@ -0,0 +1,7 @@ + + [paste.app_factory] + main = manas.config.middleware:make_app + + [paste.app_install] + main = pylons.util:PylonsInstaller +
\ No newline at end of file diff --git a/manas.egg-info/paste_deploy_config.ini_tmpl b/manas.egg-info/paste_deploy_config.ini_tmpl new file mode 100644 index 0000000..eb69733 --- /dev/null +++ b/manas.egg-info/paste_deploy_config.ini_tmpl @@ -0,0 +1,68 @@ +# +# manas - Pylons configuration +# +# The %(here)s variable will be replaced with the parent directory of this file +# +[DEFAULT] +debug = true +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:manas +full_stack = true +cache_dir = %(here)s/data +beaker.session.key = manas +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 + +# Specify the database for SQLObject to use. +#sqlobject.dburi = sqlite://%(here)s/somedb.db + +# 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 +# Uses python's logging config file format +# http://docs.python.org/lib/logging-config-fileformat.html +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s diff --git a/manas.egg-info/paster_plugins.txt b/manas.egg-info/paster_plugins.txt new file mode 100644 index 0000000..ab508e5 --- /dev/null +++ b/manas.egg-info/paster_plugins.txt @@ -0,0 +1,4 @@ +PasteScript +Pylons +TurboGears2 +tg.devtools diff --git a/manas.egg-info/requires.txt b/manas.egg-info/requires.txt new file mode 100644 index 0000000..11721d9 --- /dev/null +++ b/manas.egg-info/requires.txt @@ -0,0 +1,2 @@ +TurboGears2 +ToscaWidgets >= 0.9.1
\ No newline at end of file diff --git a/manas.egg-info/top_level.txt b/manas.egg-info/top_level.txt new file mode 100644 index 0000000..ea1b20b --- /dev/null +++ b/manas.egg-info/top_level.txt @@ -0,0 +1 @@ +manas diff --git a/manas/__init__.py b/manas/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/manas/__init__.py diff --git a/manas/config/__init__.py b/manas/config/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/manas/config/__init__.py diff --git a/manas/config/app_cfg.py b/manas/config/app_cfg.py new file mode 100644 index 0000000..3f9da94 --- /dev/null +++ b/manas/config/app_cfg.py @@ -0,0 +1,25 @@ +from tg.config import AppConfig, Bunch +from tg.environment import make_load_environment +import manas +from manas import model +from manas.lib import app_globals, helpers + +base_config = AppConfig() +base_config.renderers = [] + +base_config.package = manas + +#Set the default renderer +base_config.default_renderer = 'genshi' +base_config.renderers.append('genshi') + +#Configure the base SQLALchemy Setup +base_config.use_sqlalchemy = True +base_config.model = manas.model +#Configure the authentication backend +base_config.auth_backend = 'sqlalchemy' +base_config.sa_auth = Bunch() +base_config.sa_auth.dbsession = model.DBSession +base_config.sa_auth.user = model.User +base_config.sa_auth.user_criterion = model.User.user_name +base_config.sa_auth.user_id_column = 'user_id' diff --git a/manas/config/environment.py b/manas/config/environment.py new file mode 100644 index 0000000..d617357 --- /dev/null +++ b/manas/config/environment.py @@ -0,0 +1,5 @@ +from tg.environment import make_load_environment +from manas.config.app_cfg import base_config + +#Use base_config to setup the environment loader function +load_environment = make_load_environment(base_config) diff --git a/manas/config/middleware.py b/manas/config/middleware.py new file mode 100644 index 0000000..8656e8e --- /dev/null +++ b/manas/config/middleware.py @@ -0,0 +1,14 @@ +"""Pylons middleware initialization""" +from tg.middleware import setup_tg_wsgi_app +from manas.config.app_cfg import base_config +from manas.config.environment import load_environment + +#Use base_config to setup the nessisary WSGI App factory. +#make_base_app will wrap the TG2 app with all the middleware it needs. +make_base_app = setup_tg_wsgi_app(load_environment, base_config) + +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 +
\ No newline at end of file diff --git a/manas/controllers/__init__.py b/manas/controllers/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/manas/controllers/__init__.py diff --git a/manas/controllers/error.py b/manas/controllers/error.py new file mode 100644 index 0000000..4a5dd8e --- /dev/null +++ b/manas/controllers/error.py @@ -0,0 +1,41 @@ +import os.path + +import paste.fileapp +from pylons import request +from pylons.controllers.util import forward +from pylons.middleware import error_document_template, media_path + +from manas.lib.base import BaseController + +class ErrorController(BaseController): + """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. + """ + + def document(self): + """Render the error document""" + resp = request.environ.get('pylons.original_response') + page = error_document_template % \ + dict(prefix=request.environ.get('SCRIPT_NAME', ''), + code=request.params.get('code', resp.status_int), + message=request.params.get('message', resp.body)) + return page + + def img(self, id): + """Serve stock images""" + return self._serve_file(os.path.join(media_path, 'img', id)) + + def style(self, id): + """Serve stock stylesheets""" + return self._serve_file(os.path.join(media_path, 'style', id)) + + def _serve_file(self, path): + """Call Paste's FileApp (a WSGI application) to serve the file + at the specified path + """ + return forward(paste.fileapp.FileApp(path)) diff --git a/manas/controllers/root.py b/manas/controllers/root.py new file mode 100644 index 0000000..afd47c1 --- /dev/null +++ b/manas/controllers/root.py @@ -0,0 +1,38 @@ +"""Main Controller""" +from manas.lib.base import BaseController +from tg import expose, flash +from pylons.i18n import ugettext as _ +#from tg import redirect, validate +#from manas.model import DBSession, metadata +#from dbsprockets.dbmechanic.frameworks.tg2 import DBMechanic +#from dbsprockets.saprovider import SAProvider +from tg.ext.repoze.who import authorize +from manas.controllers.secc import Secc + +class RootController(BaseController): + #admin = DBMechanic(SAProvider(metadata), '/admin') + secc = Secc() + + @expose('manas.templates.index') + def index(self): + return dict(page='index') + + @expose('manas.templates.about') + def about(self): + return dict(page='about') + + @expose('manas.templates.index') + @authorize.require(authorize.has_permission('manage')) + def manage_permission_only(self, **kw): + return dict(page='managers stuff') + + @expose('manas.templates.index') + @authorize.require(authorize.is_user('editor')) + def editor_user_only(self, **kw): + return dict(page='editor stuff') + + @expose('manas.templates.login') + def login(self, **kw): + came_from = kw.get('came_from', '/') + return dict(page='login', header=lambda *arg: None, + footer=lambda *arg: None, came_from=came_from) diff --git a/manas/controllers/secc.py b/manas/controllers/secc.py new file mode 100644 index 0000000..98137f0 --- /dev/null +++ b/manas/controllers/secc.py @@ -0,0 +1,26 @@ +"""Test Secure Controller""" +from manas.lib.base import BaseController, SecureController +from tg import expose, flash +from pylons.i18n import ugettext as _ +#from tg import redirect, validate +#from manas.model import DBSession, metadata +#from dbsprockets.dbmechanic.frameworks.tg2 import DBMechanic +#from dbsprockets.saprovider import SAProvider +from tg.ext.repoze.who import authorize + + +class Secc(SecureController): + + require = authorize.has_permission('manage') + + @expose('manas.templates.index') + def index(self): + flash(_("Secure Controller here")) + return dict(page='index') + + @expose('manas.templates.index') + def some_where(self): + """should be protected because of the require attr + at the controller level. + """ + return dict() diff --git a/manas/controllers/template.py b/manas/controllers/template.py new file mode 100644 index 0000000..4d41453 --- /dev/null +++ b/manas/controllers/template.py @@ -0,0 +1,27 @@ +from ${package}.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) diff --git a/manas/i18n/ru/LC_MESSAGES/manas.po b/manas/i18n/ru/LC_MESSAGES/manas.po new file mode 100644 index 0000000..36532d4 --- /dev/null +++ b/manas/i18n/ru/LC_MESSAGES/manas.po @@ -0,0 +1,24 @@ +# 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 "Ваши приложение успешно запущено" + diff --git a/manas/lib/__init__.py b/manas/lib/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/manas/lib/__init__.py diff --git a/manas/lib/app_globals.py b/manas/lib/app_globals.py new file mode 100644 index 0000000..342c8cb --- /dev/null +++ b/manas/lib/app_globals.py @@ -0,0 +1,13 @@ +"""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 diff --git a/manas/lib/base.py b/manas/lib/base.py new file mode 100644 index 0000000..d27284a --- /dev/null +++ b/manas/lib/base.py @@ -0,0 +1,67 @@ +"""The base Controller API + +Provides the BaseController class for subclassing. +""" +from tg import TGController, tmpl_context +from pylons.templating import render_genshi as render +from pylons import request + +import manas.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'] + + # Create a container to send widgets to the template. Only those sent + # in here will have their resources automatically included in the + # template + try: + return TGController.__call__(self, environ, start_response) + finally: + #after everything is done clear out the Database Session + #to eliminate possible cross request DBSession polution. + model.DBSession.remove() + tmpl_context.identity = request.environ.get('repoze.who.identity') + +class SecureController(BaseController): + """this is a SecureController implementation for the + tg.ext.repoze.who plugin. + it will permit to protect whole controllers with a single predicate + placed at the controller level. + The only thing you need to have is a 'require' attribute which must + be a callable. This callable will only be authorized to return True + if the user is allowed and False otherwise. This may change to convey info + when securecontroller is fully debugged... + """ + + def check_security(self): + errors = [] + environ = request.environ + identity = environ.get('repoze.who.identity') + if not hasattr(self, "require") or \ + self.require is None or \ + self.require.eval_with_object(identity, errors): + return True + + # if we did not return this is an error :) + # TODO: do something with the errors variable like informing our user... + return False diff --git a/manas/lib/helpers.py b/manas/lib/helpers.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/manas/lib/helpers.py diff --git a/manas/model/__init__.py b/manas/model/__init__.py new file mode 100644 index 0000000..f98adc6 --- /dev/null +++ b/manas/model/__init__.py @@ -0,0 +1,45 @@ +"""The application's model objects""" + +from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy import MetaData + + +# Global session manager. DBSession() returns the session object +# appropriate for the current web request. +DBSession = scoped_session(sessionmaker(autoflush=True, transactional=True)) + +# Global metadata. If you have multiple databases with overlapping table +# names, you'll need a metadata for each database. +metadata = 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.""" + + # 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 identity import User, Group, Permission +from identity import users_table, groups_table, permissions_table, \ + user_group_table, group_permission_table + diff --git a/manas/model/identity.py b/manas/model/identity.py new file mode 100644 index 0000000..1efcd51 --- /dev/null +++ b/manas/model/identity.py @@ -0,0 +1,161 @@ +from pylons import config +from sqlalchemy import * +from sqlalchemy.orm import mapper, relation +from manas.model import metadata + +import datetime +from sqlalchemy import ForeignKey +import md5 +import sha + +groups_table = Table('tg_group', metadata, + Column('group_id', Integer, primary_key=True), + Column('group_name', Unicode(16), unique=True), + Column('display_name', Unicode(255)), + Column('created', DateTime, default=datetime.datetime.now) +) + +users_table = Table('tg_user', metadata, + Column('user_id', Integer, primary_key=True), + Column('user_name', Unicode(16), unique=True), + Column('email_address', Unicode(255), unique=True), + Column('display_name', Unicode(255)), + Column('password', Unicode(40)), + Column('created', DateTime, default=datetime.datetime.now) +) + +permissions_table = Table('tg_permission', metadata, + Column('permission_id', Integer, primary_key=True), + Column('permission_name', Unicode(16), unique=True), + Column('description', Unicode(255)) +) + +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")) +) + +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")) +) + +# identity model +class Group(object): + """An ultra-simple group definition. + """ + def __repr__(self): + return '<Group: name=%s>' % self.group_name + +class User(object): + """Reasonably basic User definition. Probably would want additional + attributes. + """ + def __repr__(self): + return '<User: email="%s", display name="%s">' % ( + self.email_address, self.display_name) + + def permissions(self): + perms = set() + for g in self.groups: + perms = perms | set(g.permissions) + return perms + permissions = property(permissions) + + def by_email_address(klass, email): + """A class method that can be used to search users + based on their email addresses since it is unique. + """ + session = DBSession() + return session.query(klass).filter(klass.email_address==email).first() + + by_email_address = classmethod(by_email_address) + + def by_user_name(klass, username): + """A class method that permits to search users + based on their user_name attribute. + """ + session = DBSession() + return session.query(klass).filter(klass.user_name==username).first() + + by_user_name = classmethod(by_user_name) + + def _set_password(self, password): + """encrypts password on the fly using the encryption + algo defined in the configuration + """ + algorithm = config.get('authorize.hashmethod', None) + self._password = self.__encrypt_password(algorithm, password) + + def _get_password(self): + """returns password + """ + return self._password + + password = property(_get_password, _set_password) + + def __encrypt_password(self, algorithm, password): + """Hash the given password with the specified algorithm. Valid values + for algorithm are 'md5' and 'sha1'. All other algorithm values will + be essentially a no-op.""" + hashed_password = password + + if isinstance(password, unicode): + password_8bit = password.encode('UTF-8') + + else: + password_8bit = password + + if "md5" == algorithm: + hashed_password = md5.new(password_8bit).hexdigest() + + elif "sha1" == algorithm: + hashed_password = sha.new(password_8bit).hexdigest() + + # TODO: re-add the possibility to provide own hasing algo + # here... just get the real config... + + #elif "custom" == algorithm: + # custom_encryption_path = turbogears.config.get( + # "identity.custom_encryption", None ) + # + # if custom_encryption_path: + # custom_encryption = turbogears.util.load_class( + # custom_encryption_path) + + # if custom_encryption: + # hashed_password = custom_encryption(password_8bit) + + # 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') + + return hashed_password + + def validate_password(self, password): + """Check the password against existing credentials. + """ + algorithm = config.get('authorize.hashmethod', None) + return self.password == self.__encrypt_password(algorithm, password) + +class Permission(object): + """A relationship that determines what each Group can do + """ + pass + + +mapper(User, users_table, + properties=dict(_password=users_table.c.password)) + +mapper(Group, groups_table, + properties=dict(users=relation(User, + secondary=user_group_table, backref='groups'))) + +mapper(Permission, permissions_table, + properties=dict(groups=relation(Group, + secondary=group_permission_table, backref='permissions'))) diff --git a/manas/model/model.template b/manas/model/model.template new file mode 100644 index 0000000..2cf9c40 --- /dev/null +++ b/manas/model/model.template @@ -0,0 +1,24 @@ +from pylons import config +from sqlalchemy import * +from sqlalchemy.orm import mapper, relation +from manas.model import metadata + +# Normal tables may be defined and mapped at module level. + +foo_table = Table("Foo", metadata, + Column("id", types.Integer, primary_key=True), + Column("bar", types.String(255), nullable=False), + ) + +class Foo(object): + def __init__(self, **kw): + """automatically mapping attributes""" + for key, value in kw.iteritems(): + setattr(self, key, value) + +mapper(Foo, foo_table) + +Classes for reflected tables may be defined here, but the table and +mapping itself must be done in the init_model function. + + diff --git a/manas/public/css/style.css b/manas/public/css/style.css new file mode 100644 index 0000000..4da3dd8 --- /dev/null +++ b/manas/public/css/style.css @@ -0,0 +1,206 @@ +/* + * Quick mash-up of CSS for the TG quick start page. + */ + +html, body { + color: black; + background-color: #ddd; + font: x-small "Lucida Grande", "Lucida Sans Unicode", geneva, verdana, sans-serif; + font-size: 83%; + margin: 0; + padding: 0; +} + +td, th {padding:3px;border:none;} +tr th {text-align:left;background-color:#f0f0f0;color:#333;} +tr.odd td {background-color:#edf3fe;} +tr.even td {background-color:#fff;} + + +a.link, a, a.active { + color: #369; +} + +.white { + color: white; +} + +h1,h2,h3,h4,h5,h6 { + font-family: "Century Schoolbook L", Georgia, serif; + font-weight: bold; +} + + +#main_content { + color: black; + font-size: 127%; + background-color: white; + width: 757px; + margin: 0 auto 1em auto; + border: 1px solid #aaa; + border-top: 0px solid #aaa; + padding: 10px; + clear: both; +} + + +.sidebar { + border: 1px solid #cce; + background-color: #ffffab; + 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 { + font-size: 80%; + margin-bottom: 0.5em; +} + +#getting_started_steps h2 { + font-size: 120%; +} + +#getting_started_steps p { + font: 100% "Lucida Grande", "Lucida Sans Unicode", geneva, verdana, sans-serif; +} + +#getting_started_steps,.alogo,.headtext { + font-family: "Century Schoolbook L", Georgia, serif; + font-weight: bold; +} + + +#header { + height: 125px; + width: 777px; + background: blue URL('../images/strype.png') repeat-x; + border-left: 1px solid #aaa; + border-right: 1px solid #aaa; + margin: 0 auto 0 auto; + color: white; +} + +.alogo { + font-size: 36px; + padding-left: 5px; + padding-top: 5px; + padding-right: 20px; + float: left; +} + +.headtext { + color: white; + font: x-small "Lucida Grande", "Lucida Sans Unicode", geneva, verdana, sans-serif; + font-size: 44px; + padding-left: 20px; + padding-top: 20px; + padding-right: 20px; + margin-bottom: 0px; +} + +.currentpage { + color: white; + font: x-small "Lucida Grande", "Lucida Sans Unicode", geneva, verdana, sans-serif; + font-size: 18px; + padding-top: 20px; + padding-right: 20px; + margin-bottom: 0px; + float: left; +} + +#footer { + border: 0px solid #aaa; + color: #888; + background-color: white; + padding: 10px; + font-size: 90%; + text-align: center; + width: 600px; + margin-left: 40px; +} + +.flogo { + padding-left: 20px; + padding-top: 0px; + padding-right: 20px; + margin-bottom: 0px; + float: left; +} + +.foottext{ +} + +.code { + font-family: monospace; + font-size: 127%; +} + +span.code { + font-weight: bold; + background: #eee; +} + +#status_block { + margin: 0 auto 0.5em auto; + padding: 5px 15px 15px 55px; + background: #eef URL('../images/ok.png') left center no-repeat; + border: 1px solid #cce; + width: 680px; + font-size: 120%; + font-weight: bolder; +} + +.notice { + margin: 0.5em auto 0.5em auto; + padding: 15px 10px 15px 55px; + width: 680px; + background: #eef URL('../images/info.png') left center no-repeat; + border: 1px solid #cce; +} + +.fielderror { + color: red; + font-weight: bold; +} + +div.clearingdiv { + clear:both; +} diff --git a/manas/public/favicon.ico b/manas/public/favicon.ico Binary files differnew file mode 100644 index 0000000..840986e --- /dev/null +++ b/manas/public/favicon.ico diff --git a/manas/public/images/error.png b/manas/public/images/error.png Binary files differnew file mode 100644 index 0000000..73c7d7f --- /dev/null +++ b/manas/public/images/error.png diff --git a/manas/public/images/grad_blue_7x80.png b/manas/public/images/grad_blue_7x80.png Binary files differnew file mode 100644 index 0000000..8eb30fa --- /dev/null +++ b/manas/public/images/grad_blue_7x80.png diff --git a/manas/public/images/grad_orange_11x100.png b/manas/public/images/grad_orange_11x100.png Binary files differnew file mode 100644 index 0000000..5b9a1d0 --- /dev/null +++ b/manas/public/images/grad_orange_11x100.png diff --git a/manas/public/images/header_inner.png b/manas/public/images/header_inner.png Binary files differnew file mode 100644 index 0000000..2b2d87d --- /dev/null +++ b/manas/public/images/header_inner.png diff --git a/manas/public/images/info.png b/manas/public/images/info.png Binary files differnew file mode 100644 index 0000000..668f9c0 --- /dev/null +++ b/manas/public/images/info.png diff --git a/manas/public/images/logo.gif b/manas/public/images/logo.gif Binary files differnew file mode 100644 index 0000000..593126c --- /dev/null +++ b/manas/public/images/logo.gif diff --git a/manas/public/images/logo.png b/manas/public/images/logo.png Binary files differnew file mode 100644 index 0000000..767021d --- /dev/null +++ b/manas/public/images/logo.png diff --git a/manas/public/images/ok.png b/manas/public/images/ok.png Binary files differnew file mode 100644 index 0000000..c402ac5 --- /dev/null +++ b/manas/public/images/ok.png diff --git a/manas/public/images/star.png b/manas/public/images/star.png Binary files differnew file mode 100644 index 0000000..f9b5aaa --- /dev/null +++ b/manas/public/images/star.png diff --git a/manas/public/images/strype.png b/manas/public/images/strype.png Binary files differnew file mode 100644 index 0000000..fd03dd5 --- /dev/null +++ b/manas/public/images/strype.png diff --git a/manas/public/images/tg2_04.gif b/manas/public/images/tg2_04.gif Binary files differnew file mode 100644 index 0000000..5e71843 --- /dev/null +++ b/manas/public/images/tg2_04.gif diff --git a/manas/public/images/tg_under_the_hood.png b/manas/public/images/tg_under_the_hood.png Binary files differnew file mode 100644 index 0000000..bc9c79c --- /dev/null +++ b/manas/public/images/tg_under_the_hood.png diff --git a/manas/public/images/under_the_hood_blue.png b/manas/public/images/under_the_hood_blue.png Binary files differnew file mode 100644 index 0000000..90e84b7 --- /dev/null +++ b/manas/public/images/under_the_hood_blue.png diff --git a/manas/templates/__init__.py b/manas/templates/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/manas/templates/__init__.py diff --git a/manas/templates/about.html b/manas/templates/about.html new file mode 100644 index 0000000..f1aa30d --- /dev/null +++ b/manas/templates/about.html @@ -0,0 +1,66 @@ +<!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="header.html" /> + <xi:include href="sidebars.html" /> + <xi:include href="footer.html" /> + <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 settled for you.</p> + </li> + <li class="getting_started"> + <h3>Design my site architecture</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="/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"><xi:include /></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"><span py:replace="page"/></span>. It means replace this <span class="code"><span /></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. (Also, there should NOT be a space between the $ sign and the curly braces, or it will not work.</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"><span py:replace="now.strftime('%Y')"></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 <title> 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> diff --git a/manas/templates/debug.html b/manas/templates/debug.html new file mode 100644 index 0000000..b415a72 --- /dev/null +++ b/manas/templates/debug.html @@ -0,0 +1,20 @@ +<!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> diff --git a/manas/templates/footer.html b/manas/templates/footer.html new file mode 100644 index 0000000..17486b5 --- /dev/null +++ b/manas/templates/footer.html @@ -0,0 +1,15 @@ +<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="/images/under_the_hood_blue.png" alt="TurboGears" /> + </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> +</py:def> +</html>
\ No newline at end of file diff --git a/manas/templates/header.html b/manas/templates/header.html new file mode 100644 index 0000000..6d533be --- /dev/null +++ b/manas/templates/header.html @@ -0,0 +1,17 @@ +<html xmlns:py="http://genshi.edgewall.org/" + xmlns:xi="http://www.w3.org/2001/XInclude" + py:strip=""> +<py:def function="header"> + <div id="header"> + <div class="alogo"> + <img src="/images/logo.png" alt="TG2!"/> + </div> + <div class="headtext"> + Welcome to TurboGears 2 + </div> + <div class="currentpage"> + Now Viewing: <span py:replace="page"/> + </div> + </div> +</py:def> +</html>
\ No newline at end of file diff --git a/manas/templates/index.html b/manas/templates/index.html new file mode 100644 index 0000000..95e7d8b --- /dev/null +++ b/manas/templates/index.html @@ -0,0 +1,42 @@ +<!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="header.html" /> + <xi:include href="sidebars.html" /> + <xi:include href="footer.html" /> + <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"> + <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>
\ No newline at end of file diff --git a/manas/templates/login.html b/manas/templates/login.html new file mode 100644 index 0000000..d0430df --- /dev/null +++ b/manas/templates/login.html @@ -0,0 +1,23 @@ +<!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>Login Form</title>
+</head>
+
+<body>
+
+<form action="/login_handler?came_from=${came_from}" method="POST">
+ Login: <input type="text" name="login"></input><br/>
+ Password: <input type="password" name="password"></input><br/>
+ <input type="submit" name="submit"/>
+</form>
+
+</body>
+</html>
diff --git a/manas/templates/master.html b/manas/templates/master.html new file mode 100644 index 0000000..3c5a5d8 --- /dev/null +++ b/manas/templates/master.html @@ -0,0 +1,28 @@ +<!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" + py:strip=""> +<?python +import tg +tg_flash = tg.get_flash() +?> +<head py:match="head" py:attrs="select('@*')"> + <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/> + <title py:replace="''">Your title goes here</title> + <meta py:replace="select('*')"/> + <link rel="stylesheet" type="text/css" media="screen" href="/css/style.css" /> +</head> + +<body py:match="body" py:attrs="select('@*')"> + ${header()} + <div id="main_content"> + <div id="status_block" py:if="tg_flash" class="flash" py:content="tg_flash"></div> + <div py:replace="select('*|text()')"/> + <!-- End of main_content --> + <div class="clearingdiv" /> + ${footer()} + </div> +</body> +</html> diff --git a/manas/templates/sidebars.html b/manas/templates/sidebars.html new file mode 100644 index 0000000..cde5deb --- /dev/null +++ b/manas/templates/sidebars.html @@ -0,0 +1,33 @@ +<html xmlns:py="http://genshi.edgewall.org/" + xmlns:xi="http://www.w3.org/2001/XInclude" + py:strip=""> + +<py:def function="sidebar_top"> + <div id="sb_top" class="sidebar"> + <h2>Get Started with TG2</h2> + <ul class="links"> + <li py:choose=""> + <span py:when="page=='index'"><a href="/about">About this page</a> A quick guide to this TG2 site </span> + <span py:otherwise=""><a href="/">Home</a> Back to your Quickstart Home page </span> + </li> + <li><a href="http://www.turbogears.org/2.0/docs/">TG2 Documents</a> - Read everything in the Getting Started section</li> + <li><a href="http://docs.turbogears.org/1.0">TG1 docs</a> (still useful, although a lot has changed for TG2) </li> + <li><a href="http://groups.google.com/group/turbogears"> Join the TG Mail List</a> for general TG use/topics </li> + </ul> + </div> +</py:def> + +<py:def function="sidebar_bottom"> + <div id="sb_bottom" class="sidebar"> + <h2>Developing TG2</h2> + <ul class="links"> + <li><a href="http://docs.turbogears.org/2.0/RoughDocs/">TG2 Documents</a> A work in progress</li> + <li><a href="http://trac.turbogears.org/query?milestone=2.0&order=priority">TG2 Trac tickets</a> What's happening now in TG2 dev</li> + <li><a href="http://trac.turbogears.org/timeline">TG Dev timeline</a> (recent ticket updates, svn checkins, wiki changes)</li> + <li><a href="http://trac.turbogears.org/browser">TG2 Trac repository</a> TG2 is in /trunk</li> + <li><a href="http://groups.google.com/group/turbogears-trunk"> Join the TG-Trunk Mail List</a> just for TG2 discuss/dev </li> + </ul> + </div> +</py:def> + +</html> diff --git a/manas/tests/__init__.py b/manas/tests/__init__.py new file mode 100644 index 0000000..91ff2e7 --- /dev/null +++ b/manas/tests/__init__.py @@ -0,0 +1,40 @@ +"""Pylons application test package + +When the test runner finds and executes tests within this directory, +this file will be loaded to setup the test environment. + +It registers the root directory of the project in sys.path and +pkg_resources, in case the project hasn't been installed with +setuptools. It also initializes the application via websetup (paster +setup-app) with the project's test.ini configuration file. +""" +import os +import sys +from unittest import TestCase + +import pkg_resources +import paste.fixture +import paste.script.appinstall +from paste.deploy import loadapp +from routes import url_for + +__all__ = ['url_for', 'TestController'] + +here_dir = os.path.dirname(os.path.abspath(__file__)) +conf_dir = os.path.dirname(os.path.dirname(here_dir)) + +sys.path.insert(0, conf_dir) +pkg_resources.working_set.add_entry(conf_dir) +pkg_resources.require('Paste') +pkg_resources.require('PasteScript') + +test_file = os.path.join(conf_dir, 'test.ini') +cmd = paste.script.appinstall.SetupCommand('setup-app') +cmd.run([test_file]) + +class TestController(TestCase): + + def __init__(self, *args, **kwargs): + wsgiapp = loadapp('config:test.ini', relative_to=conf_dir) + self.app = paste.fixture.TestApp(wsgiapp) + TestCase.__init__(self, *args, **kwargs) diff --git a/manas/tests/functional/__init__.py b/manas/tests/functional/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/manas/tests/functional/__init__.py diff --git a/manas/tests/test_models.py b/manas/tests/test_models.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/manas/tests/test_models.py diff --git a/manas/websetup.py b/manas/websetup.py new file mode 100644 index 0000000..dfbbc26 --- /dev/null +++ b/manas/websetup.py @@ -0,0 +1,54 @@ +"""Setup the manas application""" +import logging + +from paste.deploy import appconfig +from pylons import config + +from manas.config.environment import load_environment + +log = logging.getLogger(__name__) + +def setup_config(command, filename, section, vars): + """Place any commands to setup manas here""" + conf = appconfig('config:' + filename) + load_environment(conf.global_conf, conf.local_conf) + # Load the models + from manas import model + print "Creating tables" + model.metadata.create_all(bind=config['pylons.app_globals'].sa_engine) + + u = model.User() + u.user_name = u'manager' + u.display_name = u'Exemple manager' + u.email_address = u'manager@somedomain.com' + u.password = u'managepass' + + model.DBSession.save(u) + + g = model.Group() + g.group_name = u'managers' + g.display_name = u'Managers Group' + + g.users.append(u) + + model.DBSession.save(g) + + p = model.Permission() + p.permission_name = u'manage' + p.description = u'This permission give an administrative right to the bearer' + p.groups.append(g) + + model.DBSession.save(p) + model.DBSession.flush() + + u1 = model.User() + u1.user_name = u'editor' + u1.display_name = u'Exemple editor' + u1.email_address = u'editor@somedomain.com' + u1.password = u'editpass' + + model.DBSession.save(u1) + model.DBSession.flush() + + model.DBSession.commit() + print "Successfully setup" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..4f9966f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,31 @@ +[egg_info] +tag_build = dev +tag_svn_revision = true + +[easy_install] +find_links = http://www.pylonshq.com/download/ + +[nosetests] +with-pylons=test.ini + +# Babel configuration +[compile_catalog] +domain = manas +directory = manas/i18n +statistics = true + +[extract_messages] +add_comments = TRANSLATORS: +output_file = manas/i18n/manas.pot +width = 80 + +[init_catalog] +domain = manas +input_file = manas/i18n/manas.pot +output_dir = manas/i18n + +[update_catalog] +domain = manas +input_file = manas/i18n/manas.pot +output_dir = manas/i18n +previous = true diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6f16f8d --- /dev/null +++ b/setup.py @@ -0,0 +1,38 @@ +try: + from setuptools import setup, find_packages +except ImportError: + from ez_setup import use_setuptools + use_setuptools() + from setuptools import setup, find_packages + +setup( + name='manas', + version='0.1', + description='', + author='', + author_email='', + #url='', + install_requires=[ + "TurboGears2", + "ToscaWidgets >= 0.9.1" + ], + packages=find_packages(exclude=['ez_setup']), + include_package_data=True, + test_suite='nose.collector', + package_data={'manas': ['i18n/*/LC_MESSAGES/*.mo', + 'templates/*/*', + 'public/*/*']}, + #message_extractors = {'manas': [ + # ('**.py', 'python', None), + # ('templates/**.mako', 'mako', None), + # ('templates/**.html', 'genshi', None), + # ('public/**', 'ignore', None)]}, + + entry_points=""" + [paste.app_factory] + main = manas.config.middleware:make_app + + [paste.app_install] + main = pylons.util:PylonsInstaller + """, +)
\ No newline at end of file diff --git a/test.ini b/test.ini new file mode 100644 index 0000000..df583ef --- /dev/null +++ b/test.ini @@ -0,0 +1,21 @@ +# +# manas - Pylons testing environment configuration +# +# The %(here)s variable will be replaced with the parent directory of this file +# +[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 = 0.0.0.0 +port = 5000 + +[app:main] +use = config:development.ini + +# Add additional test specific configuration options as necessary. diff --git a/tests/test_controller.py b/tests/test_controller.py new file mode 100644 index 0000000..c6bb8f4 --- /dev/null +++ b/tests/test_controller.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from paste.fixture import TestApp + +import os.path +config = 'config:'+(os.path.abspath(os.path.basename(__name__)+'/../../development.ini#main')) + +app = TestApp(config) + +class TestTGController: + def test_index(self): + resp = app.get('/') + assert 'TurboGears 2 is a open source front-to-back web development' in resp.body, resp.body |