TurboGears2-2.1.5/ 0000775 0001750 0001750 00000000000 11737460177 014507 5 ustar marvin marvin 0000000 0000000 TurboGears2-2.1.5/setup.cfg 0000664 0001750 0001750 00000001363 11737460177 016333 0 ustar marvin marvin 0000000 0000000 [aliases]
release = egg_info -RDb "" sdist bdist_egg register upload
tgdevelop = develop -i http://tg.gy/215/
tgtesting = easy_install -i http://tg.gy/215/ AddOns BytecodeAssembler Chameleon coverage DecoratorTools Extremes Genshi Jinja2 Kajiki kid nose PEAK_Rules repoze.tm2 repoze.what repoze.what.plugins.sql repoze.what_pylons repoze.what_quickstart repoze.who repoze.who_friendlyform repoze.who.plugins.sa repoze.who_testutil simplegeneric simplejson sprox SQLAlchemy SymbolType tgext.admin tgext.crud ToscaWidgets transaction TurboJson TurboKid tw.forms zope.interface zope.sqlalchemy WebFlash yolk basketweaver
cover = nosetests --with-coverage
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
[nosetests]
exclude = who_testutil|kajiki
TurboGears2-2.1.5/PKG-INFO 0000664 0001750 0001750 00000002557 11737460177 015615 0 ustar marvin marvin 0000000 0000000 Metadata-Version: 1.0
Name: TurboGears2
Version: 2.1.5
Summary: Next generation TurboGears built on Pylons
Home-page: http://www.turbogears.org/
Author: Mark Ramm, Christopher Perkins, Jonathan LaCour, Rick Copland, Alberto Valverde, and the TurboGears community
Author-email: mark.ramm@gmail.com, alberto@toscat.net, m.pedersen@icelus.org
License: MIT
Description:
TurboGears brings together a best of breed python tools
to create a flexible, full featured, and easy to use web
framework.
TurboGears 2 provides an integrated and well tested set of tools for
everything you need to build dynamic, database driven applications.
It provides a full range of tools for front end javascript
develeopment, back database development and everything in between:
* dynamic javascript powered widgets (ToscaWidgets)
* automatic JSON generation from your controllers
* powerful, designer friendly XHTML based templating (Genshi)
* object or route based URL dispatching
* powerful Object Relational Mappers (SQLAlchemy)
The latest development version is available in the
`TurboGears Git repository`_.
.. _TurboGears Git repository:
https://sourceforge.net/p/turbogears2/tg2/
Keywords: turbogears pylons
Platform: UNKNOWN
TurboGears2-2.1.5/tg/ 0000775 0001750 0001750 00000000000 11737460177 015121 5 ustar marvin marvin 0000000 0000000 TurboGears2-2.1.5/tg/templates/ 0000775 0001750 0001750 00000000000 11737460177 017117 5 ustar marvin marvin 0000000 0000000 TurboGears2-2.1.5/tg/templates/__init__.py 0000664 0001750 0001750 00000000000 11675520537 021214 0 ustar marvin marvin 0000000 0000000 TurboGears2-2.1.5/tg/__init__.py 0000664 0001750 0001750 00000006310 11736737176 017237 0 ustar marvin marvin 0000000 0000000 """TurboGears 2 is a reinvention of TurboGears and a return to TurboGears' roots.
TurboGears is a project that is built upon a foundation of library development
best-of-breed component selection, and the re-use of already existing code.
And it was always intended to be a small collection of tools, docs, and helpers
that made developing with that best-of-breed stack easy.
In retrospect, some of the code that was added to the main TurboGears project
should have been released as independent projects that integrate with
TurboGears. This would have allowed those pieces to grow independently of
TurboGears, and would have allowed TurboGears to remain smaller and easier
to develop and debug.
TurboGears 0.5 release was just a few hundred lines of Python code, but it
built on thousands of lines of code in other libraries. Those libraries had
already been deployed, used, and tested, and were known to be
"production ready."
TurboGears2 returns to that philosophy. It is built on Pylons, but it brings
a best-of-breed approach to Pylons. TurboGears 2 is commited to the following
Python components and libraries, which are backwards compatable with
TurboGears 1.1:
* Models: SQLAlchemy
* Template engines: Genshi
* URL Dispatching: Object dispatch
* Form Handling: ToscaWidgets
The zen of TurboGears is::
Keep simple things simple and complex things possible
Give good defaults everywhere and allow choices where they matter
Do your best to do things the right way,
But when there's no "one right way," don't pretend there is.
Mark Ramm described the relationship between TurboGears and Pylons this way
"TurboGears 2 is to Pylons as Ubuntu is to Debian."
In other words we're focused on user experience, and creating a novice-friendly
environment. We ship a smaller subset of components, and thus are better able
to focus, test, and document things so that new users have the best possible
experience.
Meanwhile Pylons provides the power and flexibility of the underlying core.
And like Ubuntu, we don't intend to hide that power and flexibility from
advanced users, but we know that they want things set up to just work too.
Sensible defaults encourage code re-use within TurboGears because
they make it possible for a group of TurboGears components to share
assumptions about how things will work.
"""
from pylons import app_globals, request, response, tmpl_context, session, cache, translator
from tg.wsgiapp import TGApp
from tg.controllers import TGController, redirect, url, lurl, abort
from tg.configuration import config
from tg.release import version
from tg.decorators import (validate, expose, override_template, use_custom_format,
require, allow_only)
from tg.flash import flash, get_flash, get_status
from tg.jsonify import encode as json_encode
from tg.controllers.util import use_wsgi_app
from tg.controllers.dispatcher import dispatched_controller
__version__ = version
__all__ = ['__version__',
'allow_only', 'app_globals', 'expose', 'override_template', 'request',
'require', 'response', 'session', 'TGApp', 'TGController', 'tmpl_context',
'use_wsgi_app', 'validate', 'i18n','json_encode', 'cache', 'url', 'lurl',
'dispatched_controller', 'use_custom_format']
TurboGears2-2.1.5/tg/jsonify.py 0000664 0001750 0001750 00000005050 11733765036 017152 0 ustar marvin marvin 0000000 0000000 """JSON encoding functions."""
import datetime
import decimal
from simplejson import JSONEncoder
from webob.multidict import MultiDict
class NotExistingImport:
pass
try:
import sqlalchemy
from sqlalchemy.engine.base import ResultProxy, RowProxy
except ImportError:
ResultProxy=NotExistingImport
RowProxy=NotExistingImport
try:
from bson import ObjectId
except ImportError:
ObjectId=NotExistingImport
def is_saobject(obj):
return hasattr(obj, '_sa_class_manager')
class JsonEncodeError(Exception):
"""JSON Encode error"""
class GenericJSON(JSONEncoder):
"""JSON Encoder class"""
def default(self, obj):
if hasattr(obj, '__json__') and callable(obj.__json__):
return obj.__json__()
elif isinstance(obj, (datetime.date, datetime.datetime)):
return str(obj)
elif isinstance(obj, decimal.Decimal):
return float(obj)
elif is_saobject(obj):
props = {}
for key in obj.__dict__:
if not key.startswith('_sa_'):
props[key] = getattr(obj, key)
return props
elif isinstance(obj, ResultProxy):
return dict(rows=list(obj), count=obj.rowcount)
elif isinstance(obj, RowProxy):
return dict(rows=dict(obj), count=1)
elif isinstance(obj, ObjectId):
return str(obj)
elif isinstance(obj, MultiDict):
return obj.mixed()
else:
return JSONEncoder.default(self, obj)
try:
from simplegeneric import generic
_default = GenericJSON()
@generic
def jsonify(obj):
return _default.default(obj)
class GenericFunctionJSON(GenericJSON):
"""Generic Function JSON Encoder class."""
def default(self, obj):
return jsonify(obj)
_instance = GenericFunctionJSON()
except ImportError:
def jsonify(obj):
raise ImportError('simplegeneric is not installed')
_instance = GenericJSON()
# General encoding functions
def encode(obj):
"""Return a JSON string representation of a Python object."""
if isinstance(obj, basestring):
return _instance.encode(obj)
try:
value = obj['test']
except TypeError:
if not hasattr(obj, '__json__') and not is_saobject(obj):
raise JsonEncodeError('Your Encoded object must be dict-like.')
except:
pass
return _instance.encode(obj)
def encode_iter(obj):
"""Encode object, yielding each string representation as available."""
return _instance.iterencode(obj)
TurboGears2-2.1.5/tg/release.py 0000664 0001750 0001750 00000002445 11737457417 017123 0 ustar marvin marvin 0000000 0000000 """TurboGears project related information"""
version = "2.1.5"
description = "Next generation TurboGears built on Pylons"
long_description="""
TurboGears brings together a best of breed python tools
to create a flexible, full featured, and easy to use web
framework.
TurboGears 2 provides an integrated and well tested set of tools for
everything you need to build dynamic, database driven applications.
It provides a full range of tools for front end javascript
develeopment, back database development and everything in between:
* dynamic javascript powered widgets (ToscaWidgets)
* automatic JSON generation from your controllers
* powerful, designer friendly XHTML based templating (Genshi)
* object or route based URL dispatching
* powerful Object Relational Mappers (SQLAlchemy)
The latest development version is available in the
`TurboGears Git repository`_.
.. _TurboGears Git repository:
https://sourceforge.net/p/turbogears2/tg2/
"""
url="http://www.turbogears.org/"
author= "Mark Ramm, Christopher Perkins, Jonathan LaCour, Rick Copland, Alberto Valverde, and the TurboGears community"
email = "mark.ramm@gmail.com, alberto@toscat.net, m.pedersen@icelus.org"
copyright = """Copyright 2005-2011 Kevin Dangoor,
Alberto Valverde, Mark Ramm, Christopher Perkins and contributors"""
license = "MIT"
TurboGears2-2.1.5/tg/dottednames/ 0000775 0001750 0001750 00000000000 11737460177 017430 5 ustar marvin marvin 0000000 0000000 TurboGears2-2.1.5/tg/dottednames/__init__.py 0000664 0001750 0001750 00000000000 11675520537 021525 0 ustar marvin marvin 0000000 0000000 TurboGears2-2.1.5/tg/dottednames/chameleon_genshi_lookup.py 0000664 0001750 0001750 00000001646 11733764570 024672 0 ustar marvin marvin 0000000 0000000 """Chameleon.Genshi template loader that supports dotted names."""
from chameleon.genshi.loader import TemplateLoader
from tg import config
class ChameleonGenshiTemplateLoader(TemplateLoader):
"""Chameleon.Genshi template loader supporting dotted filenames.
Supports zipped applications and dotted filenames as well as path names.
"""
template_extension = '.html'
def get_dotted_filename(self, filename):
if not filename.endswith(self.template_extension):
finder = config['pylons.app_globals'].dotted_filename_finder
filename = finder.get_dotted_filename(
template_name=filename,
template_extension=self.template_extension)
return filename
def load(self, filename, format='xml'):
"""Actual loader function."""
return TemplateLoader.load(
self, self.get_dotted_filename(filename), format)
TurboGears2-2.1.5/tg/dottednames/mako_lookup.py 0000664 0001750 0001750 00000014045 11675520537 022324 0 ustar marvin marvin 0000000 0000000 """Reimplementation of the Mako template loader that supports dotted names."""
import os
import stat
try:
import threading
except ImportError:
import dummy_threading as threading
from mako.template import Template
from paste.deploy.converters import asbool
import tg
class DottedTemplateLookup(object):
"""Mako template lookup emulation that supports
zipped applications and dotted filenames.
This is an emulation of the Mako template lookup that will handle
get_template and support dotted names in Python path notation
to support zipped eggs.
This is necessary because Mako asserts that your project will always
be installed in a zip-unsafe manner with all files somewhere on the
hard drive.
This is not the case when you want your application to be deployed
in a single zip file (zip-safe). If you want to deploy in a zip
file _and_ use the dotted template name notation then this class
is necessary because it emulates files on the filesystem for the
underlying Mako engine while they are in fact in your zip file.
"""
def __init__(self, input_encoding, output_encoding,
imports, default_filters, module_directory=None):
self.input_encoding = input_encoding
self.output_encoding = output_encoding
self.imports = imports
self.default_filters = default_filters
# implement a cache for the loaded templates
self.template_cache = dict()
# implement a cache for the filename lookups
self.template_filenames_cache = dict()
self.module_directory = module_directory
self.auto_reload = asbool(tg.config.get('templating.mako.reloadfromdisk', 'false'))
# a mutex to ensure thread safeness during template loading
self._mutex = threading.Lock()
def adjust_uri(self, uri, relativeto):
"""Adjust the given uri relative to a filename.
This method is used by mako for filesystem based reasons.
In dotted lookup land we don't adjust uri so we just return
the value we are given without any change.
"""
if uri.startswith('local:'):
uri = tg.config['pylons.package'] + '.' + uri[6:]
if '.' in uri:
# We are in the DottedTemplateLookup system so dots in
# names should be treated as a Python path. Since this
# method is called by template inheritance we must
# support dotted names also in the inheritance.
result = tg.config['pylons.app_globals'].\
dotted_filename_finder.get_dotted_filename(template_name=uri, template_extension='.mak')
if not uri in self.template_filenames_cache:
# feed our filename cache if needed.
self.template_filenames_cache[uri] = result
else:
# no dot detected, just return plain name
result = uri
return result
def __check(self, template):
"""private method used to verify if a template has changed
since the last time it has been put in cache...
This method being based on the mtime of a real file this should
never be called on a zipped deployed application.
This method is a ~copy/paste of the original caching system from
the Mako lookup loader.
"""
if template.filename is None:
return template
if not os.path.exists(template.filename):
# remove from cache.
self.template_cache.pop(template.filename, None)
raise exceptions.TemplateLookupException(
"Cant locate template '%s'" % template.filename)
elif template.module._modified_time < os.stat(
template.filename)[stat.ST_MTIME]:
# cache is too old, remove old template
# from cache and reload.
self.template_cache.pop(template.filename, None)
return self.__load(template.filename)
else:
# cache is correct, use it.
return template
def __load(self, filename):
"""real loader function. copy paste from the mako template
loader.
"""
# make sure the template loading from filesystem is only done
# one thread at a time to avoid bad clashes...
self._mutex.acquire()
try:
try:
# try returning from cache one more time in case
# concurrent thread already loaded
return self.template_cache[filename]
except KeyError:
# not in cache yet... we can continue normally
pass
try:
self.template_cache[filename] = Template(
filename=filename,
module_directory=self.module_directory,
input_encoding=self.input_encoding,
output_encoding=self.output_encoding,
default_filters=self.default_filters,
imports=self.imports,
lookup=self)
return self.template_cache[filename]
except:
self.template_cache.pop(filename, None)
raise
finally:
# _always_ release the lock once done to avoid
# "thread lock" effect
self._mutex.release()
def get_template(self, template_name):
"""this is the emulated method that must return a template
instance based on a given template name
"""
if not self.template_cache.has_key(template_name):
# the template string is not yet loaded into the cache.
# Do so now
self.__load(template_name)
if self.auto_reload:
# AUTO RELOADING will be activated only if user has
# explicitly asked for it in the configuration
# return the template, but first make sure it's not outdated
# and if outdated, refresh the cache.
return self.__check(self.template_cache[template_name])
else:
return self.template_cache[template_name]
TurboGears2-2.1.5/tg/dottednames/genshi_lookup.py 0000664 0001750 0001750 00000001733 11733764570 022654 0 ustar marvin marvin 0000000 0000000 """Genshi template loader that supports dotted names."""
from genshi.template import TemplateLoader
from tg import config
class GenshiTemplateLoader(TemplateLoader):
"""Genshi template loader supporting dotted filenames.
Supports zipped applications and dotted filenames as well as path names.
"""
template_extension = '.html'
def get_dotted_filename(self, filename):
if not filename.endswith(self.template_extension):
finder = config['pylons.app_globals'].dotted_filename_finder
filename = finder.get_dotted_filename(
template_name=filename,
template_extension=self.template_extension)
return filename
def load(self, filename, relative_to=None, cls=None, encoding=None):
"""Actual loader function."""
return TemplateLoader.load(
self, self.get_dotted_filename(filename),
relative_to=relative_to, cls=cls, encoding=encoding)
TurboGears2-2.1.5/tg/dottednames/jinja_lookup.py 0000664 0001750 0001750 00000002430 11733765570 022466 0 ustar marvin marvin 0000000 0000000 """Genshi template loader that supports dotted names."""
from os.path import exists, getmtime
from jinja2.exceptions import TemplateNotFound
from jinja2.loaders import FileSystemLoader
from tg import config
class JinjaTemplateLoader(FileSystemLoader):
"""Jinja template loader supporting dotted filenames. Based on Genshi Loader
"""
template_extension = '.html'
def get_source(self, environment, template):
# Check if dottedname
if not template.endswith(self.template_extension):
# Get the actual filename from dotted finder
finder = config['pylons.app_globals'].dotted_filename_finder
template = finder.get_dotted_filename(
template_name=template,
template_extension=self.template_extension)
else:
return FileSystemLoader.get_source(self, environment, template)
# Check if the template exists
if not exists(template):
raise TemplateNotFound(template)
# Get modification time
mtime = getmtime(template)
# Read the source
fd = file(template)
try:
source = fd.read().decode('utf-8')
finally:
fd.close()
return source, template, lambda: mtime == getmtime(template)
TurboGears2-2.1.5/tg/amfify.py 0000664 0001750 0001750 00000001120 11675520537 016736 0 ustar marvin marvin 0000000 0000000 try:
from pyamf.remoting.gateway.wsgi import WSGIGateway
except:
print 'You must easy_install pyamf for to use amf renderer'
raise
from pylons import request
def render_amf(template_name, template_vars, **kwargs):
assert 0
# somehow we need to dummy out the services here, but im not sure how
# yet
def dummy(*args, **kw):
return template_vars
services = {
'something.method': dummy,
}
# setup our server
app = WSGIGateway(services)
def start_request(*args, **kw):pass
r = app(request.environ, start_request)
return r TurboGears2-2.1.5/tg/flash.py 0000664 0001750 0001750 00000002370 11733764765 016600 0 ustar marvin marvin 0000000 0000000 """
Flash messaging system for sending info to the user in a non-obtrusive way
"""
from webflash import Flash
from pylons import response, request
from logging import getLogger
log = getLogger(__name__)
class TGFlash(Flash):
def __call__(self, message, status=None, **extra_payload):
# Force the message to be unicode so lazystrings, etc... are coerced
result = super(TGFlash, self).__call__(
unicode(message), status, **extra_payload
)
if len(response.headers['Set-Cookie']) > 4096:
raise ValueError, 'Flash value is too long (cookie would be >4k)'
return result
@property
def message(self):
return self.pop_payload().get('message')
@property
def status(self):
return self.pop_payload().get('status') or self.default_status
flash = TGFlash(
get_response=lambda: response,
get_request=lambda: request
)
#TODO: Deprecate these?
def get_flash():
"""Get the message previously set by calling flash().
Additionally removes the old flash message.
"""
return flash.message
def get_status():
"""Get the status of the last flash message.
Additionally removes the old flash message status.
"""
return flash.status
TurboGears2-2.1.5/tg/error.py 0000664 0001750 0001750 00000007661 11675520537 016634 0 ustar marvin marvin 0000000 0000000 import pylons
from paste.deploy.converters import asbool
from pylons.error import template_error_formatters
from weberror.evalexception import EvalException
from weberror.errormiddleware import ErrorMiddleware
media_path = pylons.middleware.media_path
report_libs = pylons.middleware.report_libs
if 'tg.devtools' not in report_libs:
report_libs.extend(['tg.devtools'])
header_html = pylons.middleware.head_html
footer_html = """\
\

Pylons version %s
"""
def ErrorHandler(app, global_conf, **errorware):
"""ErrorHandler Toggle
If debug is enabled, this function will return the app wrapped in
the WebError ``EvalException`` middleware which displays
interactive debugging sessions when a traceback occurs.
Otherwise, the app will be wrapped in the WebError
``ErrorMiddleware``, and the ``errorware`` dict will be passed into
it. The ``ErrorMiddleware`` handles sending an email to the address
listed in the .ini file, under ``email_to``.
"""
if asbool(global_conf.get('debug')):
footer = footer_html % (pylons.configuration.config.get('traceback_host',
'pylonshq.com'),
pylons.__version__)
py_media = dict(pylons=media_path)
app = EvalException(app, global_conf,
templating_formatters=template_error_formatters,
media_paths=py_media, head_html=header_html,
footer_html=footer,
libraries=report_libs)
else:
app = ErrorMiddleware(app, global_conf, **errorware)
return app
TurboGears2-2.1.5/tg/configuration.py 0000664 0001750 0001750 00000115125 11736737176 020354 0 ustar marvin marvin 0000000 0000000 """Configuration Helpers for TurboGears 2"""
import atexit
import os
import logging
import warnings
from copy import copy
import mimetypes
from UserDict import DictMixin
from pylons.i18n import ugettext
from pylons.configuration import config as pylons_config
from beaker.middleware import SessionMiddleware, CacheMiddleware
from paste.cascade import Cascade
from paste.registry import RegistryManager
from paste.urlparser import StaticURLParser
from paste.deploy.converters import asbool, asint
import tg
from tg import TGApp
from tg.util import Bunch, get_partial_dict, DottedFileNameFinder
from routes import Mapper
from routes.middleware import RoutesMiddleware
from webob import Request
log = logging.getLogger(__name__)
class TGConfigError(Exception):pass
class PylonsConfigWrapper(DictMixin):
"""Wrapper for the Pylons configuration.
Simple wrapper for the Pylons config object that provides attribute
style access to the Pylons config dictionary.
When used in TG, items with keys like "pylons.response_options" will
be available via config.pylons.response_options as well as
config['pylons.response_options'].
This class works by proxying all attribute and dictionary access to
the underlying Pylons config object, which is an application local
proxy that allows for multiple Pylons/TG2 applicatoins to live
in the same process simultaneously, but to always get the right
config data for the application that's requesting them.
Sites, with seeking to maximize needs may prefer to use the Pylons
config stacked object proxy directly, using just dictionary style
access, particularly whenever config is checked on a per-request basis.
"""
def __init__(self, dict_to_wrap):
"""Initialize the object by passing in pylons config to be wrapped"""
self.__dict__['config_proxy'] = dict_to_wrap
def __getitem__(self, key):
return self.config_proxy.current_conf()[key]
def __setitem__(self, key, value):
self.config_proxy.current_conf()[key] = value
def __getattr__(self, key):
"""Our custom attribute getter.
Tries to get the attribute off the wrapped object first,
if that does not work, tries dictionary lookup, and finally
tries to grab all keys that start with the attribute and
return sub-dictionaries that can be looked up.
"""
try:
return self.config_proxy.__getattribute__(key)
except AttributeError:
try:
return self.config_proxy.current_conf()[key]
except KeyError:
return get_partial_dict(key, self.config_proxy.current_conf())
def __setattr__(self, key, value):
self.config_proxy.current_conf()[key] = value
def __delattr__(self, name):
try:
del self.config_proxy.current_conf()[name]
except KeyError:
raise AttributeError(name)
def keys(self):
return self.config_proxy.keys()
#Create a config object that has attribute style lookup built in.
config = PylonsConfigWrapper(pylons_config)
class AppConfig(Bunch):
"""Class to store application configuration.
This class should have configuration/setup information
that is *necessary* for proper application function.
Deployment specific configuration information should go in
the config files (e.g. development.ini or deployment.ini).
AppConfig instances have a number of methods that are meant to be
overridden by users who wish to have finer grained control over
the setup of the WSGI environment in which their application is run.
This is the place to configure custom routes, transaction handling,
error handling, etc.
"""
def __init__(self):
"""Creates some configuration defaults"""
# Create a few bunches we know we'll use
self.paths = Bunch()
self.render_functions = Bunch()
# And also very often...
self.sa_auth = Bunch()
self.sa_auth.translations = Bunch()
#Set individual defaults
self.auto_reload_templates = True
self.auth_backend = None
self.default_renderer = 'genshi'
self.stand_alone = True
# this is to activate the legacy renderers
# legacy renderers are buffet interface plugins
self.use_legacy_renderer = False
self.use_ming = False
self.use_sqlalchemy = False
self.use_toscawidgets = True
self.use_transaction_manager = True
self.use_toscawidgets2 = False
# Registry for functions to be called on startup/teardown
self.call_on_startup = []
self.call_on_shutdown = []
self.controller_wrappers = []
self.hooks = dict(before_validate=[],
before_call=[],
before_render=[],
after_render=[],
before_render_call=[],
after_render_call=[],
before_config=[],
after_config=[])
# The codes TG should display an error page for. All other HTTP errors are
# sent to the client or left for some middleware above us to handle
self.handle_status_codes = [403, 404]
#override this variable to customize how the tw2 middleware is set up
self.custom_tw2_config = {}
def get_root_module(self):
root_module_path = self.paths['root']
base_controller_path = self.paths['controllers']
controller_path = base_controller_path[len(root_module_path)+1:]
root_controller_module = '.'.join([self.package.__name__] + controller_path.split(os.sep) + ['root'])
return root_controller_module
def register_hook(self, hook_name, func):
if hook_name == 'startup':
self.call_on_startup.append(func)
elif hook_name == 'shutdown':
self.call_on_shutdown.append(func)
elif hook_name == 'controller_wrapper':
self.controller_wrappers.append(func)
else:
self.hooks.setdefault(hook_name, []).append(func)
def setup_startup_and_shutdown(self):
for cmd in self.call_on_startup:
if callable(cmd):
try:
cmd()
except Exception, error:
log.debug("Error registering %s at startup: %s" % (cmd, error ))
else:
log.debug("Unable to register %s for startup" % cmd )
for cmd in self.call_on_shutdown:
if callable(cmd):
atexit.register(cmd)
else:
log.debug("Unable to register %s for shutdown" % cmd )
def setup_paths(self):
root = os.path.dirname(os.path.abspath(self.package.__file__))
# The default paths:
paths = Bunch(root=root,
controllers=os.path.join(root, 'controllers'),
static_files=os.path.join(root, 'public'),
templates=[os.path.join(root, 'templates')])
# If the user defined custom paths, then use them instead of the
# default ones:
paths.update(self.paths)
self.paths = paths
def init_config(self, global_conf, app_conf):
"""Initialize the config object.
tg.config is a proxy for pylons.configuration.config that allows
attribute style access, so it's automatically setup when we create
the pylons config.
Besides basic initialization, this method copies all the values
in base_config into the ``pylons.configuration.config`` and
``tg.config`` objects.
"""
pylons_config.init_app(global_conf, app_conf,
package=self.package.__name__,
paths=self.paths)
self.auto_reload_templates = asbool(config.get('auto_reload_templates', True))
pylons_config['application_root_module'] = self.get_root_module()
config.update(self)
# set up the response options to None. This allows
# you to set the proper content type within a controller method
# if you choose.
pylons_config['pylons.response_options']['headers']['Content-Type'] = None
#see http://trac.turbogears.org/ticket/2247
if asbool(config['debug']):
config['pylons.strict_tmpl_context'] = True
else:
config['pylons.strict_tmpl_context'] = False
self.after_init_config()
def after_init_config(self):
"""
Override this method to set up configuration variables at the application
level. This method will be called after your configuration object has
been initialized on startup. Here is how you would use it to override
the default setting of pylons.strict_tmpl_context ::
from tg.configuration import AppConfig
from pylons import config
class MyAppConfig(AppConfig):
def after_init_config(self):
config['pylons.strict_tmpl_context'] = False
base_config = MyAppConfig()
"""
def setup_routes(self):
"""Setup the default TG2 routes
Override this and setup your own routes maps if you want to use
custom routes.
It is recommended that you keep the existing application routing in
tact, and just add new connections to the mapper above the routes_placeholder
connection. Lets say you want to add a pylons controller SamplesController,
inside the controllers/samples.py file of your application. You would
augment the app_cfg.py in the following way::
from routes import Mapper
from tg.configuration import AppConfig
class MyAppConfig(AppConfig):
def setup_routes(self):
map = Mapper(directory=config['pylons.paths']['controllers'],
always_scan=config['debug'])
# Add a Samples route
map.connect('/samples/', controller='samples', action=index)
# Setup a default route for the root of object dispatch
map.connect('*url', controller='root', action='routes_placeholder')
config['routes.map'] = map
base_config = MyAppConfig()
"""
map = Mapper(directory=config['pylons.paths']['controllers'],
always_scan=config['debug'])
# Setup a default route for the root of object dispatch
map.connect('*url', controller='root', action='routes_placeholder')
config['routes.map'] = map
def setup_helpers_and_globals(self):
"""Add helpers and globals objects to the config.
Override this method to customize the way that ``app_globals``
and ``helpers`` are setup.
"""
config['pylons.app_globals'] = self.package.lib.app_globals.Globals()
g = config['pylons.app_globals']
g.dotted_filename_finder = DottedFileNameFinder()
def setup_sa_auth_backend(self):
"""This method adds sa_auth information to the config."""
if 'beaker.session.secret' not in config:
raise TGConfigError("You must provide a value for 'beaker.session.secret' If this is a project quickstarted with TG 2.0.2 or earlier \
double check that you have base_config['beaker.session.secret'] = 'mysecretsecret' in your app_cfg.py file.")
defaults = {
'form_plugin': None,
'cookie_secret': config['beaker.session.secret']
}
# The developer must have defined a 'sa_auth' section, because
# values such as the User, Group or Permission classes must be
# explicitly defined.
config['sa_auth'] = defaults
config['sa_auth'].update(self.sa_auth)
def setup_mako_renderer(self, use_dotted_templatenames=None):
"""Setup a renderer and loader for mako templates.
Override this to customize the way that the mako template
renderer is setup. In particular if you want to setup
a different set of search paths, different encodings, or
additonal imports, all you need to do is update the
``TemplateLookup`` constructor.
You can also use your own render_mako function instead of the one
provided by tg.render.
"""
from tg.render import render_mako
if not use_dotted_templatenames:
use_dotted_templatenames = asbool(config.get('use_dotted_templatenames', 'true'))
# If no dotted names support was required we will just setup
# a file system based template lookup mechanism.
compiled_dir = tg.config.get('templating.mako.compiled_templates_dir', None)
if not compiled_dir:
# Try each given templates path (when are they > 1 ?) for writability..
for template_path in self.paths['templates']:
if os.access(template_path, os.W_OK):
compiled_dir = template_path
break # first match is as good as any
# Last recourse: project-dir/data/templates (pylons' default directory)
if not compiled_dir:
try:
root = os.path.dirname(os.path.abspath(self.package.__file__))
except AttributeError:
# Thrown during unit tests when self.package.__file__ doesn't exist
root = None
if root:
pylons_default_path = os.path.join(root, '../data/templates')
if os.access(pylons_default_path, os.W_OK):
compiled_dir = pylons_default_path
if not compiled_dir:
if use_dotted_templatenames:
# Gracefully digress to in-memory template caching
pass
else:
raise IOError("None of your templates directory, %s, are "
"writable for compiled templates. Please set the "
"templating.mako.compiled_templates_dir variable in your "
".ini file" % str(self.paths['templates']))
if use_dotted_templatenames:
# Support dotted names by injecting a slightly different template
# lookup system that will return templates from dotted template notation.
from tg.dottednames.mako_lookup import DottedTemplateLookup
config['pylons.app_globals'].mako_lookup = DottedTemplateLookup(
input_encoding='utf-8', output_encoding='utf-8',
imports=['from webhelpers.html import escape'],
module_directory=compiled_dir,
default_filters=['escape'])
else:
from mako.lookup import TemplateLookup
config['pylons.app_globals'].mako_lookup = TemplateLookup(
directories=self.paths['templates'],
module_directory=compiled_dir,
input_encoding='utf-8', output_encoding='utf-8',
imports=['from webhelpers.html import escape'],
default_filters=['escape'],
filesystem_checks=self.auto_reload_templates)
self.render_functions.mako = render_mako
def setup_chameleon_genshi_renderer(self):
"""Setup a renderer and loader for the chameleon.genshi engine."""
from tg.render import RenderChameleonGenshi
if config.get('use_dotted_templatenames', True):
from tg.dottednames.chameleon_genshi_lookup \
import ChameleonGenshiTemplateLoader as TemplateLoader
else:
from chameleon.genshi.loader import TemplateLoader
loader = TemplateLoader(search_path=self.paths.templates,
auto_reload=self.auto_reload_templates)
self.render_functions.chameleon_genshi = RenderChameleonGenshi(loader)
def setup_genshi_renderer(self):
"""Setup a renderer and loader for Genshi templates.
Override this to customize the way that the internationalization
filter, template loader
"""
from tg.render import RenderGenshi
from genshi.filters import Translator
def template_loaded(template):
"""Plug-in our i18n function to Genshi, once the template is loaded.
This function will be called by the Genshi TemplateLoader after
loading the template.
"""
translator = Translator(ugettext)
template.filters.insert(0, translator)
if hasattr(template, 'add_directives'):
template.add_directives(Translator.NAMESPACE, translator)
if config.get('use_dotted_templatenames', True):
from tg.dottednames.genshi_lookup \
import GenshiTemplateLoader as TemplateLoader
else:
from genshi.template import TemplateLoader
loader = TemplateLoader(search_path=self.paths.templates,
max_cache_size=asint(self.get('genshi.max_cache_size', 30)),
auto_reload=self.auto_reload_templates,
callback=template_loaded)
self.render_functions.genshi = RenderGenshi(loader)
def setup_kajiki_renderer(self):
"""Setup a renderer and loader for the fastpt engine."""
from kajiki.loader import PackageLoader
from tg.render import render_kajiki
loader = PackageLoader()
config['pylons.app_globals'].kajiki_loader = loader
self.render_functions.kajiki = render_kajiki
def setup_jinja_renderer(self):
"""Setup a renderer and loader for Jinja2 templates."""
from jinja2 import ChoiceLoader, Environment
from jinja2.filters import FILTERS
from tg.render import render_jinja
if config.get('use_dotted_templatenames', True):
from tg.dottednames.jinja_lookup import JinjaTemplateLoader as TemplateLoader
else:
from jinja2 import FileSystemLoader as TemplateLoader
if not 'jinja_extensions' in self :
self.jinja_extensions = []
if not 'jinja_filters' in self:
self.jinja_filters = {}
loader = ChoiceLoader(
[TemplateLoader(path) for path in self.paths['templates']])
config['pylons.app_globals'].jinja2_env = Environment(loader=loader,
auto_reload=self.auto_reload_templates, extensions=self.jinja_extensions)
# Try to load custom filters module under app_package.lib.templatetools
try:
filter_package = self.package.__name__ + ".lib.templatetools"
autoload_lib = __import__(filter_package, {}, {}, ['jinja_filters'])
autoload_filters = autoload_lib.jinja_filters.__dict__
except (ImportError, AttributeError):
autoload_filters = {}
# Add jinja filters
filters = dict(FILTERS, **autoload_filters)
filters.update(self.jinja_filters)
config['pylons.app_globals'].jinja2_env.filters = filters
# Jinja's unable to request c's attributes without strict_c
warnings.simplefilter("ignore")
config['pylons.strict_c'] = True
warnings.resetwarnings()
config['pylons.strict_tmpl_context'] = True
self.render_functions.jinja = render_jinja
def setup_amf_renderer(self):
from tg.amfify import render_amf
self.render_functions.amf = render_amf
def setup_json_renderer(self):
from tg.render import render_json
self.render_functions.json = render_json
def setup_default_renderer(self):
"""Setup template defaults in the buffed plugin.
This is only used when use_legacy_renderer is set to True
and it will not get deprecated in the next major TurboGears release.
"""
#T his is specific to buffet, will not be needed later
config['buffet.template_engines'].pop()
template_location = '%s.templates' % self.package.__name__
template_location = '%s.templates' % self.package.__name__
from genshi.filters import Translator
def template_loaded(template):
template.filters.insert(0, Translator(ugettext))
# Set some default options for genshi
options = {
'genshi.loader_callback': template_loaded,
'genshi.default_format': 'xhtml',
}
# Override those options from config
config['buffet.template_options'].update(options)
config.add_template_engine(self.default_renderer,
template_location, {})
def setup_mimetypes(self):
lookup = {'.json':'application/json'}
lookup.update(config.get('mimetype_lookup', {}))
for key, value in lookup.iteritems():
mimetypes.add_type(value, key)
def setup_persistence(self):
"""Override this method to define how your application configures it's persistence model.
the default is to setup sqlalchemy from the cofiguration file, but you might choose
to set up a persistence system other than sqlalchemy, or add an additional persistence
layer. Here is how you would go about setting up a ming (mongo) persistence layer::
class MingAppConfig(AppConfig):
def setup_persistence(self):
self.ming_ds = DataStore(config['mongo.url'])
session = Session.by_name('main')
session.bind = self.ming_ds
"""
if self.use_sqlalchemy:
self.setup_sqlalchemy()
elif self.use_ming:
self.setup_ming()
def setup_ming(self):
"""Setup MongoDB database engine using Ming"""
from ming.datastore import DataStore
datastore = DataStore(config['ming.url'], database=config['ming.db'])
config['pylons.app_globals'].ming_datastore = datastore
self.package.model.init_model(datastore)
def setup_sqlalchemy(self):
"""Setup SQLAlchemy database engine.
The most common reason for modifying this method is to add
multiple database support. To do this you might modify your
app_cfg.py file in the following manner::
from tg.configuration import AppConfig, config
from pylons import config as pylons_config
from myapp.model import init_model
# add this before base_config =
class MultiDBAppConfig(AppConfig):
def setup_sqlalchemy(self):
'''Setup SQLAlchemy database engine(s)'''
from sqlalchemy import engine_from_config
engine1 = engine_from_config(pylons_config, 'sqlalchemy.first.')
engine2 = engine_from_config(pylons_config, 'sqlalchemy.second.')
# engine1 should be assigned to sa_engine as well as your first engine's name
config['pylons.app_globals'].sa_engine = engine1
config['pylons.app_globals'].sa_engine_first = engine1
config['pylons.app_globals'].sa_engine_second = engine2
# Pass the engines to init_model, to be able to introspect tables
init_model(engine1, engine2)
#base_config = AppConfig()
base_config = MultiDBAppConfig()
This will pull the config settings from your .ini files to create the necessary
engines for use within your application. Make sure you have a look at :ref:`multidatabase`
for more information.
"""
from sqlalchemy import engine_from_config
engine = engine_from_config(pylons_config, 'sqlalchemy.')
config['pylons.app_globals'].sa_engine = engine
# Pass the engine to initmodel, to be able to introspect tables
self.package.model.init_model(engine)
def setup_auth(self):
"""
Override this method to define how you would like the auth to be set up for your app.
For the standard TurboGears App, this will set up the auth with SQLAlchemy.
"""
if self.auth_backend in ("ming", "sqlalchemy"):
self.setup_sa_auth_backend()
def make_load_environment(self):
"""Return a load_environment function.
The returned load_environment function can be called to configure
the TurboGears runtime environment for this particular application.
You can do this dynamically with multiple nested TG applications
if necessary.
"""
def load_environment(global_conf, app_conf):
"""Configure the Pylons environment via ``pylons.configuration.config``."""
global_conf=Bunch(global_conf)
app_conf=Bunch(app_conf)
self.setup_paths()
self.init_config(global_conf, app_conf)
#Registers functions to be called at startup and shutdown
#from self.call_on_startup and shutdown respectively.
self.setup_startup_and_shutdown()
self.setup_routes()
self.setup_helpers_and_globals()
self.setup_mimetypes()
self.setup_auth()
if not 'json' in self.renderers: self.renderers.append('json')
for renderer in self.renderers:
setup = getattr(self, 'setup_%s_renderer'%renderer, None)
if setup:
setup()
else:
raise Exception('This configuration object does not support the %s renderer'%renderer)
if self.use_legacy_renderer:
self.setup_default_renderer()
self.setup_persistence()
return load_environment
def add_error_middleware(self, global_conf, app):
"""Add middleware which handles errors and exceptions."""
from pylons.middleware import report_libs, StatusCodeRedirect
from tg.error import ErrorHandler
app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
# Display error documents for self.handle_status_codes status codes (and
# 500 when debug is disabled)
if asbool(config['debug']):
app = StatusCodeRedirect(app, self.handle_status_codes)
else:
app = StatusCodeRedirect(app, self.handle_status_codes + [500])
return app
def add_auth_middleware(self, app, skip_authentication):
"""
Configure authentication and authorization.
:param app: The TG2 application.
:param skip_authentication: Should authentication be skipped if
explicitly requested? (used by repoze.who-testutil)
:type skip_authentication: bool
"""
from repoze.what.plugins.pylonshq import booleanize_predicates
# Predicates booleanized:
booleanize_predicates()
# Configuring auth logging:
if 'log_stream' not in self.sa_auth:
self.sa_auth['log_stream'] = logging.getLogger('auth')
# Removing keywords not used by repoze.who:
auth_args = copy(self.sa_auth)
if 'sa_auth' in config:
auth_args.update(config.sa_auth)
if 'password_encryption_method' in auth_args:
del auth_args['password_encryption_method']
if not skip_authentication:
if not 'cookie_secret' in auth_args.keys():
msg = "base_config.sa_auth.cookie_secret is required "\
"you must define it in app_cfg.py or set "\
"sa_auth.cookie_secret in development.ini"
raise TGConfigError(msg)
if self.auth_backend == "sqlalchemy":
from repoze.what.plugins.quickstart import setup_sql_auth
app = setup_sql_auth(app, skip_authentication=skip_authentication, **auth_args)
elif self.auth_backend == "ming":
from tgming import setup_ming_auth
app = setup_ming_auth(app, skip_authentication=skip_authentication, **auth_args)
return app
def add_core_middleware(self, app):
"""Add support for routes dispatch, sessions, and caching.
This is where you would want to override if you wanted to provide your
own routing, session, or caching middleware. Your app_cfg.py might look something
like this::
from tg.configuration import AppConfig
from routes.middleware import RoutesMiddleware
from beaker.middleware import CacheMiddleware
from mysessionier.middleware import SessionMiddleware
class MyAppConfig(AppConfig):
def add_core_middleware(self, app):
app = RoutesMiddleware(app, config['routes.map'])
app = SessionMiddleware(app, config)
app = CacheMiddleware(app, config)
return app
base_config = MyAppConfig()
"""
app = RoutesMiddleware(app, config['routes.map'])
app = SessionMiddleware(app, config)
app = CacheMiddleware(app, config)
return app
def add_tosca_middleware(self, app):
"""Configure the ToscaWidgets middleware.
If you would like to override the way the TW middleware works, you might do something like::
from tg.configuration import AppConfig
from tw.api import make_middleware as tw_middleware
class MyAppConfig(AppConfig):
def add_tosca2_middleware(self, app):
app = tw_middleware(app, {
'toscawidgets.framework.default_view': self.default_renderer,
'toscawidgets.framework.translator': ugettext,
'toscawidgets.middleware.inject_resources': False,
})
return app
base_config = MyAppConfig()
The above example would disable resource injection.
There is more information about the settings you can change
in the ToscaWidgets `middleware. `
"""
from tw.api import make_middleware as tw_middleware
twconfig = {'toscawidgets.framework.default_view': self.default_renderer,
'toscawidgets.framework.translator': ugettext,
'toscawidgets.middleware.inject_resources': True,
}
for k,v in config.iteritems():
if k.startswith('toscawidgets.framework.') or k.startswith('toscawidgets.middleware.'):
twconfig[k] = v
if 'toscawidgets.framework.resource_variant' in config:
import tw.api
tw.api.resources.registry.ACTIVE_VARIANT = config['toscawidgets.framework.resource_variant']
#remove it from the middleware madness
del twconfig['toscawidgets.framework.resource_variant']
app = tw_middleware(app, twconfig)
return app
def add_tosca2_middleware(self, app):
"""Configure the ToscaWidgets2 middleware.
If you would like to override the way the TW2 middleware works,
you might do change your app_cfg.py to add something like::
from tg.configuration import AppConfig
from tw2.core.middleware import TwMiddleware
class MyAppConfig(AppConfig):
def add_tosca2_middleware(self, app):
app = TwMiddleware(app,
default_engine=self.default_renderer,
translator=ugettext,
auto_reload_templates = False
)
return app
base_config = MyAppConfig()
The above example would always set the template auto reloading off. (This is normally an
option that is set within your application's ini file.)
"""
from tw2.core.middleware import Config, TwMiddleware
default_tw2_config = dict( default_engine=self.default_renderer,
translator=ugettext,
auto_reload_templates=asbool(self.get('templating.mako.reloadfromdisk', 'false'))
)
default_tw2_config.update(self.custom_tw2_config)
app = TwMiddleware(app, **default_tw2_config)
return app
def add_static_file_middleware(self, app):
static_app = StaticURLParser(config['pylons.paths']['static_files'])
app = Cascade([static_app, app])
return app
def commit_veto(self, environ, status, headers):
"""Veto a commit.
This hook is called by repoze.tm in case we want to veto a commit
for some reason. Return True to force a rollback.
By default we veto if the response's status code is an error code.
Override this method, or monkey patch the instancemethod, to fine
tune this behaviour.
"""
return not 200 <= int(status.split(None, 1)[0]) < 400
def add_tm_middleware(self, app):
"""Set up the transaction managment middleware.
To abort a transaction inside a TG2 app::
import transaction
transaction.doom()
By default http error responses also roll back transactions, but this
behavior can be overridden by overriding base_config.commit_veto.
"""
from repoze.tm import make_tm
return make_tm(app, self.commit_veto)
def add_ming_middleware(self, app):
"""Set up the ming middleware for the unit of work"""
import ming.orm.middleware
return ming.orm.middleware.MingMiddleware(app)
def add_dbsession_remover_middleware(self, app):
"""Set up middleware that cleans up the sqlalchemy session.
The default behavior of TG 2 is to clean up the session on every
request. Only override this method if you know what you are doing!
"""
def remover(environ, start_response):
try:
return app(environ, start_response)
finally:
log.debug("Removing DBSession from current thread")
self.DBSession.remove()
return remover
def setup_tg_wsgi_app(self, load_environment):
"""Create a base TG app, with all the standard middleware.
``load_environment``
A required callable, which sets up the basic evironment
needed for the application.
``setup_vars``
A dictionary with all special values necessary for setting up
the base wsgi app.
"""
def make_base_app(global_conf, wrap_app=None, full_stack=True, **app_conf):
"""Create a tg WSGI application and return it.
``wrap_app``
a WSGI middleware component which takes the core turbogears
application and wraps it -- inside all the WSGI-components
provided by TG and Pylons. This allows you to work with the
full environment that your TG application would get before
anything happens in the application itself.
``global_conf``
The inherited configuration for this application. Normally
from the [DEFAULT] section of the Paste ini file.
``full_stack``
Whether or not this application provides a full WSGI stack (by
default, meaning it handles its own exceptions and errors).
Disable full_stack when this application is "managed" by
another WSGI middleware.
``app_conf``
The application's local configuration. Normally specified in
the [app:] section of the Paste ini file (where
defaults to main).
"""
# Configure the Pylons environment
load_environment(global_conf, app_conf)
app = TGApp()
if wrap_app:
app = wrap_app(app)
for hook in self.hooks['before_config']:
app = hook(app)
avoid_sess_touch = config.get('beaker.session.tg_avoid_touch', 'false')
config['beaker.session.tg_avoid_touch'] = asbool(avoid_sess_touch)
app = self.add_core_middleware(app)
if self.use_toscawidgets:
app = self.add_tosca_middleware(app)
if self.use_toscawidgets2:
app = self.add_tosca2_middleware(app)
if self.auth_backend:
# Skipping authentication if explicitly requested. Used by
# repoze.who-testutil:
skip_authentication = app_conf.get('skip_authentication', False)
app = self.add_auth_middleware(app, skip_authentication)
if self.use_transaction_manager:
app = self.add_tm_middleware(app)
if self.use_sqlalchemy:
if not hasattr(self, 'DBSession'):
# If the user hasn't specified a scoped_session, assume
# he/she uses the default DBSession in model
self.DBSession = self.model.DBSession
app = self.add_dbsession_remover_middleware(app)
if self.use_ming:
app = self.add_ming_middleware(app)
if pylons_config.get('make_body_seekable'):
app = maybe_make_body_seekable(app)
if 'PYTHONOPTIMIZE' in os.environ:
warnings.warn("Forcing full_stack=False due to PYTHONOPTIMIZE enabled. "+\
"Error Middleware will be disabled", RuntimeWarning, stacklevel=2)
full_stack = False
if asbool(full_stack):
if (self.auth_backend is None
and 401 not in self.handle_status_codes):
# If there's no auth backend configured which traps 401
# responses we redirect those responses to a nicely
# formatted error page
self.handle_status_codes.append(401)
# This should never be true for internal nested apps
app = self.add_error_middleware(global_conf, app)
# Establish the registry for this application
app = RegistryManager(app)
# Static files (if running in production, and Apache or another
# web server is serving static files)
#if the user has set the value in app_config, don't pull it from the ini
if not hasattr(self, 'serve_static'):
self.serve_static = asbool(config.get('serve_static', 'true'))
if self.serve_static:
app = self.add_static_file_middleware(app)
for hook in self.hooks['after_config']:
app = hook(app)
return app
return make_base_app
def maybe_make_body_seekable(app):
def wrapper(environ, start_response):
log.debug("Making request body seekable")
Request(environ).make_body_seekable()
return app(environ, start_response)
return wrapper
TurboGears2-2.1.5/tg/paginate.py 0000664 0001750 0001750 00000004121 11733764570 017261 0 ustar marvin marvin 0000000 0000000 from webhelpers.paginate import Page as WhPage
from webhelpers.html import HTML
from tg import request
from tg.controllers.util import url
class Page(WhPage):
def _pagerlink(self, pagenr, text):
"""
Create a URL that links to another page using url_for().
Parameters:
pagenr
Number of the page that the link points to
text
Text to be printed in the A-HREF tag
"""
# Let the url_for() from webhelpers create a new link and set
# the variable called 'page_param'. Example:
# You are in '/foo/bar' (controller='foo', action='bar')
# and you want to add a parameter 'pagenr'. Then you
# call the navigator method with page_param='pagenr' and
# the url_for() call will create a link '/foo/bar?pagenr=...'
# with the respective page number added.
link_params = {}
# Use the instance kwargs from Page.__init__ as URL parameters
link_params.update(self.kwargs)
# Add keyword arguments from pager() to the link as parameters
link_params.update(self.pager_kwargs)
link_params[self.page_param] = pagenr
# Create the URL to load the page area part of a certain page (AJAX updates)
partial_url = link_params.pop('partial', '') #url_for(**link_params)
# Create the URL to load a certain page
link_url = link_params.pop('link', request.path_info)
link_url = HTML.literal(url(link_url, params=link_params))
if self.onclick: # create link with onclick action for AJAX
try: # if '%s' is used in the 'onclick' parameter (backwards compatibility)
onclick_action = self.onclick % (partial_url,)
except TypeError:
onclick_action = Template(self.onclick).safe_substitute({
"partial_url": partial_url,
"page": pagenr
})
return HTML.a(text, href=link_url, onclick=onclick_action, **self.link_attr)
else: # return static link
return HTML.a(text, href=link_url, **self.link_attr)
TurboGears2-2.1.5/tg/controllers/ 0000775 0001750 0001750 00000000000 11737460177 017467 5 ustar marvin marvin 0000000 0000000 TurboGears2-2.1.5/tg/controllers/__init__.py 0000664 0001750 0001750 00000001050 11736737176 021601 0 ustar marvin marvin 0000000 0000000 from dispatcher import ObjectDispatcher
from decoratedcontroller import DecoratedController, CUSTOM_CONTENT_TYPE
from wsgiappcontroller import WSGIAppController
from tgcontroller import TGController
from restcontroller import RestController
from pylons.controllers.util import abort
from util import redirect, url, lurl, pylons_formencode_gettext
__all__ = ['abort', 'redirect', 'url', 'lurl', 'pylons_formencode_gettext',
'DecoratedController', 'CUSTOM_CONTENT_TYPE',
'RestController', 'TGController', 'WSGIAppController']
TurboGears2-2.1.5/tg/controllers/restcontroller.py 0000664 0001750 0001750 00000037715 11736737176 023144 0 ustar marvin marvin 0000000 0000000 """This module contains the RestController implementation.
Rest controller provides a RESTful dispatch mechanism, and
combines controller decoration for TG-Controller behavior.
"""
import pylons
from pylons.controllers.util import abort
import inspect
from dispatcher import ObjectDispatcher
from decoratedcontroller import DecoratedController
class RestDispatcher(ObjectDispatcher):
"""Defines a restful interface for a set of HTTP verbs.
Please see RestController for a rundown of the controller
methods used.
"""
def _setup_wsgiorg_routing_args(self, url_path, remainder, params):
pylons.request.environ['wsgiorg.routing_args'] = (tuple(remainder), params)
def _handle_put_or_post(self, method, state, remainder):
current_controller = state.controller
if remainder:
current_path = remainder[0]
if self._is_exposed(current_controller, current_path):
state.add_method(getattr(current_controller, current_path), remainder[1:])
return state
if self._is_controller(current_controller, current_path):
current_controller = getattr(current_controller, current_path)
return self._dispatch_controller(current_path, current_controller, state, remainder[1:])
method_name = method
method = self._find_first_exposed(current_controller, [method,])
if method and self._method_matches_args(method, state, remainder):
state.add_method(method, remainder)
return state
return self._dispatch_first_found_default_or_lookup(state, remainder)
def _handle_delete(self, method, state, remainder):
current_controller = state.controller
method_name = method
method = self._find_first_exposed(current_controller, ('post_delete', 'delete'))
if method and self._method_matches_args(method, state, remainder):
state.add_method(method, remainder)
return state
#you may not send a delete request to a non-delete function
if remainder and self._is_exposed(current_controller, remainder[0]):
abort(405)
# there might be a sub-controller with a delete method, let's go see
if remainder:
sub_controller = getattr(current_controller, remainder[0], None)
if sub_controller:
remainder = remainder[1:]
state.current_controller = sub_controller
state.url_path = '/'.join(remainder)
r = self._dispatch_controller(state.url_path, sub_controller, state, remainder)
if r:
return r
return self._dispatch_first_found_default_or_lookup(state, remainder)
def _check_for_sub_controllers(self, state, remainder):
current_controller = state.controller
method = None
for find in ('get_one', 'get'):
if hasattr(current_controller, find):
method = find
break
if method is None:
return
args = self._get_argspec(getattr(current_controller, method))
fixed_args = args[0][1:]
fixed_arg_length = len(fixed_args)
var_args = args[1]
if var_args:
for i, item in enumerate(remainder):
if hasattr(current_controller, item) and self._is_controller(current_controller, item):
current_controller = getattr(current_controller, item)
state.add_routing_args(item, remainder[:i], fixed_args, var_args)
return self._dispatch_controller(item, current_controller, state, remainder[i+1:])
elif fixed_arg_length< len(remainder) and hasattr(current_controller, remainder[fixed_arg_length]):
item = remainder[fixed_arg_length]
if hasattr(current_controller, item):
if self._is_controller(current_controller, item):
state.add_routing_args(item, remainder, fixed_args, var_args)
return self._dispatch_controller(item, getattr(current_controller, item), state, remainder[fixed_arg_length+1:])
def _handle_delete_edit_or_new(self, state, remainder):
method_name = remainder[-1]
if method_name not in ('new', 'edit', 'delete'):
return
if method_name == 'delete':
method_name = 'get_delete'
current_controller = state.controller
if self._is_exposed(current_controller, method_name):
method = getattr(current_controller, method_name)
new_remainder = remainder[:-1]
if method and self._method_matches_args(method, state, new_remainder):
state.add_method(method, new_remainder)
return state
def _handle_custom_get(self, state, remainder):
method_name = remainder[-1]
current_controller = state.controller
if (self._is_exposed(current_controller, method_name) or
self._is_exposed(current_controller, 'get_%s' % method_name)):
method = self._find_first_exposed(current_controller, ('get_%s' % method_name, method_name))
new_remainder = remainder[:-1]
if method and self._method_matches_args(method, state, new_remainder):
state.add_method(method, new_remainder)
return state
def _handle_custom_method(self, method, state, remainder):
current_controller = state.controller
method_name = method
method = self._find_first_exposed(current_controller, ('post_%s' % method_name, method_name))
if method and self._method_matches_args(method, state, remainder):
state.add_method(method, remainder)
return state
#you may not send a delete request to a non-delete function
if remainder and self._is_exposed(current_controller, remainder[0]):
abort(405)
# there might be a sub-controller with a delete method, let's go see
if remainder:
sub_controller = getattr(current_controller, remainder[0], None)
if sub_controller:
remainder = remainder[1:]
state.current_controller = sub_controller
state.url_path = '/'.join(remainder)
r = self._dispatch_controller(state.url_path, sub_controller, state, remainder)
if r:
return r
return self._dispatch_first_found_default_or_lookup(state, remainder)
def _handle_get(self, method, state, remainder):
current_controller = state.controller
if not remainder:
method = self._find_first_exposed(current_controller, ('get_all', 'get'))
if method:
state.add_method(method, remainder)
return state
if self._is_exposed(current_controller, 'get_one'):
method = current_controller.get_one
if method and self._method_matches_args(method, state, remainder):
state.add_method(method, remainder)
return state
return self._dispatch_first_found_default_or_lookup(state, remainder)
#test for "delete", "edit" or "new"
r = self._handle_delete_edit_or_new(state, remainder)
if r:
return r
#test for custom REST-like attribute
r = self._handle_custom_get(state, remainder)
if r:
return r
current_path = remainder[0]
if self._is_exposed(current_controller, current_path):
state.add_method(getattr(current_controller, current_path), remainder[1:])
return state
if self._is_controller(current_controller, current_path):
current_controller = getattr(current_controller, current_path)
return self._dispatch_controller(current_path, current_controller, state, remainder[1:])
if self._is_exposed(current_controller, 'get_one') or self._is_exposed(current_controller, 'get'):
if self._is_exposed(current_controller, 'get_one'):
method = current_controller.get_one
else:
method = current_controller.get
if method and self._method_matches_args(method, state, remainder):
state.add_method(method, remainder)
return state
return self._dispatch_first_found_default_or_lookup(state, remainder)
_handler_lookup = {
'put':_handle_put_or_post,
'post':_handle_put_or_post,
'delete':_handle_delete,
'get':_handle_get,
}
def _dispatch(self, state, remainder):
"""returns: populated DispachState object
"""
if not hasattr(state, 'http_method'):
method = pylons.request.method.lower()
params = state.params
#conventional hack for handling methods which are not supported by most browsers
request_method = params.get('_method', None)
if request_method:
request_method = request_method.lower()
#make certain that DELETE and PUT requests are not sent with GET
if method == 'get' and request_method == 'put':
abort(405)
if method == 'get' and request_method == 'delete':
abort(405)
method = request_method
state.http_method = method
r = self._check_for_sub_controllers(state, remainder)
if r:
return r
if state.http_method in self._handler_lookup.keys():
r = self._handler_lookup[state.http_method](self, state.http_method, state, remainder)
else:
r = self._handle_custom_method(state.http_method, state, remainder)
#clear out the method hack
if '_method' in pylons.request.POST:
del pylons.request.POST['_method']
del state.params['_method']
if '_method' in pylons.request.GET:
del pylons.request.GET['_method']
del state.params['_method']
return r
class RestController(DecoratedController, RestDispatcher):
"""A Decorated Controller that dispatches in a RESTful Manner.
This controller was designed to follow Representational State Transfer protocol, also known as REST.
The goal of this controller method is to provide the developer a way to map
RESTful URLS to controller methods directly, while still allowing Normal Object Dispatch to occur.
Here is a brief rundown of the methods which are called on dispatch along with an example URL.
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| Method | Description | Example Method(s) / URL(s) |
+=================+==============================================================+============================================+
| get_one | Display one record. | GET /movies/1 |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| get_all | Display all records in a resource. | GET /movies/ |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| get | A combo of get_one and get_all. | GET /movies/ |
| | +--------------------------------------------+
| | | GET /movies/1 |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| new | Display a page to prompt the User for resource creation. | GET /movies/new |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| edit | Display a page to prompt the User for resource modification. | GET /movies/1/edit |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| post | Create a new record. | POST /movies/ |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| put | Update an existing record. | POST /movies/1?_method=PUT |
| | +--------------------------------------------+
| | | PUT /movies/1 |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| post_delete | Delete an existing record. | POST /movies/1?_method=DELETE |
| | +--------------------------------------------+
| | | DELETE /movies/1 |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| get_delete | Display a delete Confirmation page. | GET /movies/1/delete |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
| delete | A combination of post_delete and get_delete. | GET /movies/delete |
| | +--------------------------------------------+
| | | DELETE /movies/1 |
| | +--------------------------------------------+
| | | DELETE /movies/ |
| | +--------------------------------------------+
| | | POST /movies/1/delete |
| | +--------------------------------------------+
| | | POST /movies/delete |
+-----------------+--------------------------------------------------------------+--------------------------------------------+
You may note the ?_method on some of the URLs. This is basically a hack because exiting browsers
do not support the PUT and DELETE methods. Just note that if you decide to use a this resource with a web browser,
you will likely have to add a _method as a hidden field in your forms for these items. Also note that RestController differs
from TGController in that it offers no index, _default, or _lookup. It is intended primarily for resource management.
:References:
`Controller <../main/Controllers.html>`_ A basic overview on how to write controller methods.
`CrudRestController <../main/Extensions/Crud/index.html>`_ A way to integrate ToscaWdiget Functionality with RESTful Dispatch.
"""
__all__ = ['RestController']
TurboGears2-2.1.5/tg/controllers/dispatcher.py 0000664 0001750 0001750 00000050076 11736737176 022204 0 ustar marvin marvin 0000000 0000000 """
This is the main dispatcher module.
Dispatch works as follows:
Start at the RootController, the root controller must
have a _dispatch function, which defines how we move
from object to object in the system.
Continue following the dispatch mechanism for a given
controller until you reach another controller with a
_dispatch method defined. Use the new _dispatch
method until anther controller with _dispatch defined
or until the url has been traversed to entirety.
This module also contains the standard ObjectDispatch
class which provides the ordinary TurboGears mechanism.
"""
from inspect import getargspec, isclass, ismethod
import mimetypes
import sys
from warnings import warn
import pylons
from pylons.controllers import WSGIController
from tg.exceptions import HTTPNotFound
from tg.i18n import setup_i18n
from tg.decorators import cached_property
HTTPNotFound = HTTPNotFound().exception
def dispatched_controller():
state = pylons.request.controller_state
for location, cont in reversed(state.controller_path):
if cont.mount_point:
return cont
return None
class DispatchState(object):
"""
This class keeps around all the pertainent info for the state
of the dispatch as it traverses through the tree. This allows
us to attach things like routing args and to keep track of the
path the controller takes along the system.
"""
def __init__(self, url_path, params):
self.url_path = url_path
self.controller_path = []
self.routing_args = {}
self.method = None
self.remainder = None
self.dispatcher = None
self.params = params
self._notfound_stack = []
#remove the ignore params from self.params
remove_params = pylons.config.get('ignore_parameters', [])
for param in remove_params:
if param in self.params:
del self.params[param]
def add_controller(self, location, controller):
"""Add a controller object to the stack"""
self.controller_path.append((location, controller))
def add_method(self, method, remainder):
"""Add the final method that will be called in the _call method"""
self.method = method
self.remainder = remainder
def add_routing_args(self, current_path, remainder, fixed_args, var_args):
"""
Add the "intermediate" routing args for a given controller mounted
at the current_path
"""
i = 0
for i, arg in enumerate(fixed_args):
if i >= len(remainder):
break
self.routing_args[arg] = remainder[i]
remainder = remainder[i:]
if var_args and remainder:
self.routing_args[current_path] = remainder
@property
def controller(self):
"""returns the current controller"""
return self.controller_path[-1][1]
class Dispatcher(WSGIController):
"""
Extend this class to define your own mechanism for dispatch.
"""
def _call(self, controller, params, remainder=None):
"""Override to define how your controller method should be called."""
response = controller(*remainder, **dict(params))
return response
def _get_argspec(self, func):
try:
cached_argspecs = self.__class__._cached_argspecs
except AttributeError:
self.__class__._cached_argspecs = cached_argspecs = {}
try:
argspec = cached_argspecs[func.im_func]
except KeyError:
argspec = cached_argspecs[func.im_func] = getargspec(func)
return argspec
def _get_params_with_argspec(self, func, params, remainder):
params = params.copy()
argspec = self._get_argspec(func)
argvars = argspec[0][1:]
if argvars and enumerate(remainder):
for i, var in enumerate(argvars):
if i >= len(remainder):
break
params[var] = remainder[i]
return params
def _remove_argspec_params_from_params(self, func, params, remainder):
"""Remove parameters from the argument list that are
not named parameters
Returns: params, remainder"""
# figure out which of the vars in the argspec are required
argspec = self._get_argspec(func)
argvars = argspec[0][1:]
# if there are no required variables, or the remainder is none, we
# have nothing to do
if not argvars or not remainder:
return params, remainder
# this is a work around for a crappy api choice in getargspec
argvals = argspec[3]
if argvals is None:
argvals = []
required_vars = argvars
optional_vars = []
if argvals:
required_vars = argvars[:-len(argvals)]
optional_vars = argvars[-len(argvals):]
# make a copy of the params so that we don't modify the existing one
params=params.copy()
# replace the existing required variables with the values that come in
# from params these could be the parameters that come off of validation.
remainder = list(remainder)
for i, var in enumerate(required_vars):
if i < len(remainder):
remainder[i] = params[var]
elif params.get(var):
remainder.append(params[var])
if var in params:
del params[var]
#remove the optional positional variables (remainder) from the named parameters
# until we run out of remainder, that is, avoid creating duplicate parameters
for i,(original,var) in enumerate(zip(remainder[len(required_vars):],optional_vars)):
if var in params:
remainder[ len(required_vars)+i ] = params[var]
del params[var]
return params, tuple(remainder)
def _dispatch(self, state, remainder):
"""override this to define how your controller should dispatch.
returns: dispatcher, controller_path, remainder
"""
raise NotImplementedError
def _get_dispatchable(self, url_path):
"""Return a tuple (controller, remainder, params).
:Parameters:
url
url as string
"""
if not pylons.config.get('disable_request_extensions', False):
pylons.request.response_type = None
pylons.request.response_ext = None
if url_path and '.' in url_path[-1]:
last_remainder = url_path[-1]
mime_type, encoding = mimetypes.guess_type(last_remainder)
if mime_type:
extension_spot = last_remainder.rfind('.')
extension = last_remainder[extension_spot:]
url_path[-1] = last_remainder[:extension_spot]
pylons.request.response_type = mime_type
pylons.request.response_ext = extension
params = pylons.request.params.mixed()
state = DispatchState(url_path, params)
state.add_controller('/', self)
state.dispatcher = self
state = state.controller._dispatch(state, url_path)
pylons.tmpl_context.controller_url = '/'.join(
url_path[:-len(state.remainder)])
state.routing_args.update(params)
if hasattr(state.dispatcher, '_setup_wsgiorg_routing_args'):
state.dispatcher._setup_wsgiorg_routing_args(
url_path, state.remainder, state.routing_args)
#save the controller state for possible use within the controller methods
pylons.request.controller_state = state
return state.method, state.controller, state.remainder, params
def _setup_wsgiorg_routing_args(self, url_path, remainder, params):
"""
This is expected to be overridden by any subclass that wants to set
the routing_args (RestController). Do not delete.
"""
# this needs to get added back in after we understand why it breaks pagination:
# pylons.request.environ['wsgiorg.routing_args'] = (tuple(remainder), params)
def _setup_wsgi_script_name(self, url_path, remainder, params):
pass
def _perform_call(self, func, args):
"""Called from within Pylons and should not be overridden."""
if pylons.config.get('i18n_enabled', True):
setup_i18n()
script_name = pylons.request.environ.get('SCRIPT_NAME', '')
url_path = pylons.request.path
if url_path.startswith(script_name):
url_path = url_path[len(script_name):]
url_path = url_path.split('/')[1:]
if url_path[-1] == '':
url_path.pop()
func, controller, remainder, params = self._get_dispatchable(url_path)
if hasattr(controller, '__before__'
) and not hasattr(controller, '_before'):
warn("Support for __before__ is going to removed"
" in the next minor version, please use _before instead.")
controller.__before__(*args, **args)
if hasattr(controller, '_before'):
controller._before(*args, **args)
self._setup_wsgi_script_name(url_path, remainder, params)
r = self._call(func, params, remainder=remainder)
if hasattr(controller, '__after__'):
warn("Support for __after__ is going to removed"
" in the next minor version, please use _after instead.")
controller.__after__(*args, **args)
if hasattr(controller, '_after'):
controller._after(*args, **args)
return r
def routes_placeholder(self, url='/', start_response=None, **kwargs):
"""Routes placeholder.
This function does not do anything. It is a placeholder that allows
Routes to accept this controller as a target for its routing.
"""
pass
class ObjectDispatcher(Dispatcher):
"""
Object dispatch (also "object publishing") means that each portion of the
URL becomes a lookup on an object. The next part of the URL applies to the
next object, until you run out of URL. Processing starts on a "Root"
object.
Thus, /foo/bar/baz become URL portion "foo", "bar", and "baz". The
dispatch looks for the "foo" attribute on the Root URL, which returns
another object. The "bar" attribute is looked for on the new object, which
returns another object. The "baz" attribute is similarly looked for on
this object.
Dispatch does not have to be directly on attribute lookup, objects can also
have other methods to explain how to dispatch from them. The search ends
when a decorated controller method is found.
The rules work as follows:
1) If the current object under consideration is a decorated controller
method, the search is ended.
2) If the current object under consideration has a "_default" method, keep a
record of that method. If we fail in our search, and the most recent
method recorded is a "_default" method, then the search is ended with
that method returned.
3) If the current object under consideration has a "_lookup" method, keep a
record of that method. If we fail in our search, and the most recent
method recorded is a "_lookup" method, then execute the "_lookup" method,
and start the search again on the return value of that method.
4) If the URL portion exists as an attribute on the object in question,
start searching again on that attribute.
5) If we fail our search, try the most recent recorded methods as per 2 and
3.
"""
def _find_first_exposed(self, controller, methods):
for method in methods:
if self._is_exposed(controller, method):
return getattr(controller, method)
def _is_exposed(self, controller, name):
"""Override this function to define how a controller method is
determined to be exposed.
:Arguments:
controller - controller with methods that may or may not be exposed.
name - name of the method that is tested.
:Returns:
True or None
"""
if ismethod(getattr(controller, name, None)):
return True
def _method_matches_args(self, method, state, remainder):
"""
This method matches the params from the request along with the remainder to the
method's function signiture. If the two jive, it returns true.
It is very likely that this method would go into ObjectDispatch in the future.
"""
argspec = self._get_argspec(method)
#skip self,
argvars = argspec[0][1:]
argvals = argspec[3]
required_vars = argvars
if argvals:
required_vars = argvars[:-len(argvals)]
else:
argvals = []
#remove the appropriate remainder quotient
if len(remainder)