2012-11-02 39 views
6

我正在写一个管理网站,它可以控制具有相同程序和数据库模式但内容不同的多个网站。我设计的URL如下:如何以优雅的方式处理复杂的URL?

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 

正如您所看到的,几乎所有的URL都需要site_id。在几乎所有视图中,我都必须使用site_id对数据库执行一些常见工作,例如查询Site模型。另外,每当我调用request.route_path时,我都必须传递site_id。

那么...有没有我为了让我的生活更轻松?

回答

5

对您来说,使用混合方法来获取网站可能很有用。

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 

我们将使用groupfinder用户映射到主体。我们在这里选择仅将它们映射到其拥有会员资格的网站。我们简单的遍历只需要一个根对象。它更新加载的site__acl__,使用相同的主体groupfinder安装创建。

您需要在Pyramid Cookbook中设置request.db给定模式。

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 

前辈可以找到site_id并自动将其添加到URL。

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') 

我们在这里设置我们的应用程序。我们还将这些路线转移到一个可包含的功能中,这可以让我们更轻松地测试路线。

@view_config(route_name='site_users', permission='view') 
def users_view(request): 
    site = request.context 

我们的意见然后简化。只有当用户有权访问网站时,才会调用它们,并且网站对象已经为我们加载。

混合穿越

自定义指令add_site_route被添加,以提高您config物体周围add_route的包装,它会自动对添加路由遍历支持。当该路由匹配时,它将从路由模式获取{site_id}占位符,并将其用作遍历路径(/{site_id}是我们根据遍历树的结构来定义的路径)。

遍历发生在路径/{site_id}其中第一步是找到树的根(/)。路由被设置为使用SiteFactory作为遍历路径的根来执行遍历。这个类被实例化为根,并且__getitem__被作为路径中下一个段的键({site_id})调用。然后我们找到一个与该键匹配的网站对象,并尽可能加载它。然后使用__parent____name__更新站点对象以允许find_interface工作。它也增强了后面提到的提供权限的__acl__

程序pregenerator

每个路由与试图找到的Site实例在遍历层次结构的请求的程序pregenerator更新。如果当前请求没有解析为基于站点的URL,这可能会失败。预生成器然后用站点ID更新发送到route_url的关键字。

认证

的例子显示,你怎么能有这一个用户进入,表明该用户是在“网站:”校长映射认证策略组。该站点(request.context)随后更新为具有ACL,表示如果site.id == 1“site:1”组中的某个用户应具有“查看”权限。 users_view然后更新为需要“查看”权限。如果用户被拒绝访问视图,这将引发HTTPForbidden异常。如果需要,你可以编写一个异常视图来有条件地将其转换为404。

我回答的目的只是为了说明混合方法如何通过处理背景中的URL的常见部分来使您的视图更美观。 HTH。

+0

因为我的Pyramid的遍历知识泄漏,我完全不能理解你的例子,但我会阅读文档并尝试理解它::-)顺便说一下:当我调用请求时,你的解决方案是否可以消除'site_id'参数.route_path? –

+1

通过向生成路由时自动填充site_id的路由添加预生成器,可以消除“site_id”。这并不明显,这就是为什么我张贴它来激发你的胃口。但预先配置一些声明式配置后,您的观点可能变得非常简单。 –

+0

我仍然陷入混乱.....你介意更新你提到的这个预生产者的例子吗?谢谢!顺便说一下,如果我想为不同的Site.type添加不同的路由,我该怎么办?例如,如果site1.type是'passport'且site1.id是1,site2.type是'www'且site2.id是2,那么/ site/1/user应映射到视图,但是/ site/2 /用户应该引发404错误。 –

3

对于意见,你可以使用一个类,以便共同作业可以在__init__方法(docs)进行:

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) 

而且你可以使用路由前缀处理的URL(docs) 。不确定这是否会对您的情况有所帮助。

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}') 
+0

感谢您的帮助!路由前缀是我已经用来组织我的URL的方式。将课堂观点放在课堂上真的是一个好主意。但是如何route_path/route_url?我仍然必须每次都传递site_id。 –

+0

您可以通过在类中添加一个方法来使其更容易。我用一个例子更新了我的例子。 – grc