Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pyramid: Inverse to add_notfound_view(append_slash=True)

Tags:

pylons

pyramid

In Pyramid, add_notfound_view(append_slash=True) will cause a request which does not match any view, but which would match a view if a trailing slash existed on the end, to be redirected to the matching view.

Does an inverse to this exist? That is: If I have a route configured as

config.add_route('list_reports', '/reports')

and a user requests /reports/, is there a simple way to cause them to be redirected appropriately?

like image 268
Charles Duffy Avatar asked Mar 23 '12 19:03

Charles Duffy


2 Answers

The non-global solution

Add a second route for each view that you want redirected.

config = Configurator()
def add_auto_route(name, pattern, **kw):
    config.add_route(name, pattern, **kw)
    if not pattern.endswith('/'):
        config.add_route(name + '-auto', pattern + '/')
        def redirector(request):
            return HTTPMovedPermanently(request.route_url(name))
        config.add_view(redirector, route_name=name + '-auto')

add_auto_route('list_reports', '/reports')

Globally redirect all routes (never support slash-appended routes)

Simply rewrite the URLs. This can be done via pyramid_rewrite, or externally by your web server.

config = Configurator()
config.include('pyramid_rewrite')
config.add_rewrite_rule(r'/(?P<path>.*)/', r'/%(path)s')

Attempt to redirect if a route is not found

Rip the AppendSlashNotFoundFactory out of pyramid's source and invert it. Sorry, not doing that one for you here, but just as easy.

like image 21
Michael Merickel Avatar answered Nov 18 '22 00:11

Michael Merickel


Michael's stuff is correct. Here's some code for the last case he didn't put code in for:

from pyramid.httpexceptions import default_exceptionresponse_view, HTTPFound
from pyramid.interfaces import IRoutesMapper

class RemoveSlashNotFoundViewFactory(object):
    def __init__(self, notfound_view=None):
        if notfound_view is None:
            notfound_view = default_exceptionresponse_view
        self.notfound_view = notfound_view

    def __call__(self, context, request):
        if not isinstance(context, Exception):
            # backwards compat for an append_notslash_view registered via
            # config.set_notfound_view instead of as a proper exception view
            context = getattr(request, 'exception', None) or context
        path = request.path
        registry = request.registry
        mapper = registry.queryUtility(IRoutesMapper)
        if mapper is not None and path.endswith('/'):
            noslash_path = path.rstrip('/')
            for route in mapper.get_routes():
                if route.match(noslash_path) is not None:
                    qs = request.query_string
                    if qs:
                        noslash_path += '?' + qs
                    return HTTPFound(location=noslash_path)
        return self.notfound_view(context, request)

Then in your main configuration:

config.add_notfound_view(RemoveSlashNotFoundViewFactory())
like image 164
Chris McDonough Avatar answered Nov 17 '22 22:11

Chris McDonough