How to handle a complex url in an elegant way?

I am writing an admin site that manages several sites with the same program scheme and database, but with different content. The url that I constructed as follows:

http://example.com/site A list of all sites which under control http://example.com/site/{id} A brief overview of select site with ID id http://example.com/site/{id}/user User list of target site http://example.com/site/{id}/item A list of items sold on target site http://example.com/site/{id}/item/{iid} Item detailed information # ...... something similar 

As you can see, the whole URL requires site_id. And in almost all views, I have to perform some general tasks, such as querying a site model against a database with site_id. In addition, I have to pass site_id whenever I call request.route_path.

So, is there a way to make my life easier?

+6
source share
2 answers

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: # somehow get the list of sites they are members sites = user.allowed_sites return ['site:%d' % s.id for s in sites] class SiteFactory(object): def __init__(self, request): self.request = request def __getitem__(self, key): site = self.request.db.query(Site).filter_by(id=key).first() if site is None: raise KeyError site.__parent__ = self site.__name__ = key site.__acl__ = [ (Allow, 'site:%d' % site.id, 'view'), ] return site 

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): # request.route_url(route_name, *elements, **kw) from pyramid.traversal import find_interface # we use find_interface in case we improve our hybrid traversal process # to take us deeper into the hierarchy, where Site might be context.__parent__ site = find_interface(request.context, Site) if site is not None: kw['site_id'] = site.id return 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.

+5
source

For views, you can use the class so that common tasks are executed in the __init__ ( docs ) method

 from pyramid.view import view_config class SiteView(object): def __init__(self, request): self.request = request self.id = self.request.matchdict['id'] # Do any common jobs here @view_config(route_name='site_overview') def site_overview(self): # ... @view_config(route_name='site_users') def site_users(self): # ... def route_site_url(self, name, **kw): return self.request.route_url(name, id=self.id, **kw) 

And you can use the route prefix to handle URLs ( docs ). Not sure if this will be useful for your situation or not.

 from pyramid.config import Configurator def site_include(config): config.add_route('site_overview', '') config.add_route('site_users', '/user') config.add_route('site_items', '/item') # ... def main(global_config, **settings): config = Configurator() config.include(site_include, route_prefix='/site/{id}') 
+3
source

All Articles