It may be useful for you to use a hybrid approach to load the site.
def groupfinder(userid, request): user = request.db.query(User).filter_by(id=userid).first() if user is not None:
We will use groupfinder to map users to principals. We have chosen here to display them only on sites on which they have a membership. Our simple workaround requires only the root object. It updates the loaded site with __acl__ , which uses the same principles as the created groupfinder .
You need to configure request.db data patterns in the Pyramid Cookbook.
def site_pregenerator(request, elements, kw):
The regenerator can find the site_id and automatically add it to the URLs.
def add_site_route(config, name, pattern, **kw): kw['traverse'] = '/{site_id}' kw['factory'] = SiteFactory kw['pregenerator'] = site_pregenerator if pattern.startswith('/'): pattern = pattern[1:] config.add_route(name, '/site/{site_id}/' + pattern, **kw) def main(global_conf, **settings): config = Configurator(settings=settings) authn_policy = AuthTktAuthenticationPolicy('seekrit', callback=groupfinder) config.set_authentication_policy(authn_policy) config.set_authorization_policy(ACLAuthorizationPolicy()) config.add_directive(add_site_route, 'add_site_route') config.include(site_routes) config.scan() return config.make_wsgi_app() def site_routes(config): config.add_site_route('site_users', '/user') config.add_site_route('site_items', '/items')
Here we configure our application. We also moved the routes to a function that allows us to test routes more easily.
@view_config(route_name='site_users', permission='view') def users_view(request): site = request.context
Our views are then simplified. They are called only if the user has permission to access the site, and the site object has already been downloaded for us.
Hybrid traverse
A custom add_site_route directive has been add_site_route to enhance your config object with a wrapper around add_route , which will automatically add traverse support to the route. When this route is mapped, it will take the {site_id} cluster from the route template and use it as your crawl path ( /{site_id} is the path we define based on how our crawl tree is structured).
Traversal occurs on the path /{site_id} , where the first step is to search for the root of the tree ( / ). The route is configured to crawl using SiteFactory as the root of the crawl path. This class is created as the root, and __getitem__ is called using the key, which is the next segment in the path ( {site_id} ). Then we find the site object corresponding to this key and load it, if possible. The site object is then updated with __parent__ and __name__ to allow find_interface to work. It is also expanded with the __acl__ granting permissions mentioned below.
Pregenerator
Each route is updated with a regenerator that tries to find the site instance in the crawl hierarchy for the request. This may fail if the current request did not resolve the site URL. The regenerator then updates the keywords sent to route_url with the site identifier.
Authentication
This example shows how you can have an authentication policy that displays a user in principals that indicate that this user is in the "site:" group. Then the site ( request.context ) is updated to have an ACL saying that if site.id == 1 someone from the group "site: 1" must have permission "view". Then users_view updated to require "view" permission. This will throw an HTTPForbidden exception if the user is denied access to the view. You can write an exception view to conditionally translate it to 404 if you want.
The goal of my answer is to show how a hybrid approach can make your views a little nicer by processing the common parts of the URL in the background. NTN.