Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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())
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top