Pyramid: add_static_view with name='directory/subdirectory' - python

I've just started learning Python recently and am using Pyramid as my web framework.
I'm trying to add a static view at localhost/images/misc:
config.add_static_view('images', 'C:/Project/Images/')
config.add_static_view('images/misc', 'C:/Path/To/Other/Images/')
But I get an error: File does not exist: C:/Project/images/misc
So it seems that the second line adding images/misc as a static view doesn't have any effect.
I've been searching for a while for a way to do this, but I haven't found anything. Is it possible to add a static view where the name contains a subdirectory? If so, how?

Under the hood, pyramid turns the name part of the add_static_view() method into a Pyramid route predicate of the form name/*subpath (where name can contain slashes itself). A dedicated static asset view is attached to that route predicate.
In your configuration that means there would be both images/*subpath and images/misc/*subpath route predicates, in that order. When you then request a URL with the path images/misc/foo.png Pyramid finds the images/*subpath predicate first, and tries to look up the misc path element in the 'C:/Project/Images/' folder, which fails.
Lucky for you, you can reverse the order of the registrations, simply register the images/misc path first:
config.add_static_view('images/misc', 'C:/Path/To/Other/Images/')
config.add_static_view('images', 'C:/Project/Images/')
Now the images/misc/*subpath predicate is registered first, and will match any images/misc/ URLs before the other predicate is tested.

Related

Routes with trailing slashes in Pyramid

Let's say I have a route '/foo/bar/baz'.
I would also like to have another view corresponding to '/foo' or '/foo/'.
But I don't want to systematically append trailing slashes for other routes, only for /foo and a few others (/buz but not /biz)
From what I saw I cannot simply define two routes with the same route_name.
I currently do this:
config.add_route('foo', '/foo')
config.add_route('foo_slash', '/foo/')
config.add_view(lambda _,__: HTTPFound('/foo'), route_name='foo_slash')
Is there something more elegant in Pyramid to do this ?
Pyramid has a way for HTTPNotFound views to automatically append a slash and test the routes again for a match (the way Django's APPEND_SLASH=True works). Take a look at:
http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/urldispatch.html#redirecting-to-slash-appended-routes
As per this example, you can use config.add_notfound_view(notfound, append_slash=True), where notfound is a function that defines your HTTPNotFound view. If a view is not found (because it didn't match due to a missing slash), the HTTPNotFound view will append a slash and try again. The example shown in the link above is pretty informative, but let me know if you have any additional questions.
Also, heed the warning that this should not be used with POST requests.
There are also many ways to skin a cat in Pyramid, so you can play around and achieve this in different ways too, but you have the concept now.
Found this solution when I was looking for the same thing for my project
def add_auto_route(config,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')
And then during route configuration,
add_auto_route(config,'events','/events')
Rather than doing config.add_route('events','/events')
Basically it is a hybrid of your methods. A new route with name ending in _auto is defined and its view redirects to the original route.
EDIT
The solution does not take into account dynamic URL components and GET parameters. For a URL like /abc/{def}?m=aasa, using add_auto_route() will throw a key error because the redirector function does not take into account request.matchdict. The below code does that. To access GET parameters it also uses _query=request.GET
def add_auto_route(config,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,_query=request.GET,**request.matchdict))
config.add_view(redirector, route_name=name + '_auto')
I found another solution. It looks like we can chain two #view_config. So this solution is possible:
#view_config(route_name='foo_slash', renderer='myproject:templates/foo.mako')
#view_config(route_name='foo', renderer='myproject:templates/foo.mako')
def foo(request):
#do something
Its behavior is also different from the question. The solution from the question performs a redirect, so the url changes in the browser. In the second form both /foo and /foo/ can appear in the browser, depending on what the user entered. I don't really mind, but repeating the renderer path is also awkward.

Is it a way to check whether all the subpaths of the current request.path have a view callable associated with?

I am working on a breadcrumbs generator.
It uses request.path and then, for each subpath, builds a breadcrumb.
Example:
/blog/articles/view/12345
Then for each of the subpaths:
/blog/articles/view
/blog/articles
/blog
True would be returned, if there's a view callable behind this URL ( allowing GET method without arguments ), otherwise False
So that I could make the subpaths in the breadcrumbs clickable to show that there's something served there.
Any idea which would not call any of the subpaths and generate useless code execution?
No, you have to test all path prefixes; routing allows for many, arbitrary URLs to be possible. Moreover, with path predicates in the mix, multiple routes could match the same URL and choosing between them depends on other information from the request.
To prepare your breadcrumbs, instead loop over the sub-paths once and determine for each if there is a matching view; the easiest way to do this is to reuse the code underlying the pviews command; this code needs the current request:
from pyramid.scripts.pviews import PViewsCommand
pvcomm = PViewsCommand()
urlpath = request.environ['PATH_INFO']
parts = urlpath.split('/')
existing_views = {}
for i in range(1, len(parts)):
path = '/'.join(parts[:i])
view = pvcomm._find_view(path, request.registry)
if view is not None:
existing_views[path] = view
You can now look up path prefixes in the existing_views dictionary.

Locating lower level templates

Hi I am having problems with the template using Bottle
my folder structure is this:
|views
--main.tpl
--|blog
--home.tpl
what I want to do is this:
def home():
return template('blog/home')
but it won't work
I can get it to work just calling the following:
def home():
return template('main')
But I want to be-able to have many different folders
I understand that I will still need to keep unique names because of the caching
and please don't say use a different framework as this is not my choice.
You can try passing the template_lookup argument to the template function. template_lookup overrides the defaults .views path when looking for the template. However I believe this will only work if the name of the tempalte is not in the views folder. So if you had a /views/main.tpl and a /blog/main.tpl it would not work, every template needs a unique name. This is needed because bottle will only lookup search for tempaltes if it hasn't found it before and stores the found ones in a dict with the tempalte name as the key. so if the templates have the same name it would use the first one.
return template("home", template_lookup="full_path_to/views/blog/"

Python routes not working for non-static URLs

I'm trying to write a unit test for an old pylons 0.9.7 application.
The issue we have is that it's very easy to accidentally hard-code URL
paths in our jinja2 templates, which works fine until someone hosts
our application at a different prefix (i.e.- hosts it at
http://localhost/foo/ instead of just http://localhost/). I'm
doing this using the default "functional test" setup that paste
created for me. I query every controller action, and pass in a custom
SCRIPT_NAME like so:
response = self.app.get('/some/url/path',
extra_environ={'SCRIPT_NAME' : '/custom_prefix'})
I then use beautifulsoup to parse the document, iterating over all the
tags that have an href attribute or a src attribute, and ensure
that it starts with '/custom_prefix'.
Here's where things get odd: When we pass an absolute path in to
pylons.url (which is actually a routes.util.URLGenerator)in our templates like this:
{{ h.stylesheet_link(h.url('/css/style.css')) }}
...it works perfectly. However, when we call pylons.url with keyword
arguments like this:
{{ h.link_to('Home', h.url(controller='person', action='home')) }}
...the prefix is not inserted. However, the url() function works
correctly when running this correctly (i.e.- using mod_wsgi or
pastedeploy) with a non-empty SCRIPT_NAME.
I guess that I'm missing a critical step, but I can't think what it is.

Rewriting An URL With Regular Expression Substitution in Routes

In my Pylons app, some content is located at URLs that look like http://mysite/data/31415. Users can go to that URL directly, or search for "31415" via the search page. My constraints, however, mean that http://mysite/data/000031415 should go to the same page as the above, as should searches for "0000000000031415." Can I strip leading zeroes from that string in Routes itself, or do I need to do that substitution in the controller file? If it's possible to do it in routing.py, I'd rather do it there - but I can't quite figure it out from the documentation that I'm reading.
You can actually do that via conditional functions, since they let you modify the variables from the URL in place.
I know I am cheating by introducing a different routing library, since I haven't used Routes, but here's how this is done with Werkzeug's routing package. It lets you specify that a given fragment of the path is an integer. You can also implement a more specialized "converter" by inheriting werkzeug.routing.BaseConverter, if you wanted to parse something more interesting (e.g. a UUID).
Perhaps, Routes has a similar mechanism in place for specialized path-fragment-parsing needs.
import unittest
from werkzeug.routing import Map, Rule
class RoutingWithInts(unittest.TestCase):
m = Map([Rule('/data/<int:record_locator>', endpoint='data_getter')])
def test_without_leading_zeros(self):
urls = self.m.bind('localhost')
endpoint, urlvars = urls.match('/data/31415')
self.assertEquals({'record_locator': 31415}, urlvars)
def test_with_leading_zeros(self):
urls = self.m.bind('localhost')
endpoint, urlvars = urls.match('/data/000031415')
self.assertEquals({'record_locator': 31415}, urlvars)
unittest.main()

Categories