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()
Related
I have a Flask app that handles a range of URLs. I'm splitting it up into multiple handler modules, where the handler depends on the first element of the path. The mapping between path/URL prefixes and handlers is a bit like this:
/one/... => Handler A
/two/... => Handler A
/three/... => Handler B
/four/... => Handler B
Calling a given URL under /one/... gets you something very similar (though not identical) to the same URL under /two/ - hence the desire to use the same handler for both those sets of URLs. At the same time Handler A does something very different to Handler B, therefore the desire to implement a clear separation, with separate modules for each.
Blueprints seem to be a great way to do this - and for the most part appear to work well. Where I'm struggling is in setting up differentiated behavior for /one/ vs /two/ (and /three/ vs /four/). In other words, exposing the actual URL prefix to the handler.
As an example handler A looks like
# handler_A.py
from flask import Blueprint
handler_A = Blueprint('handler_A', __name__)
#handler_A('/somepage', methods=['GET'])
def get_page():
return "You've reached somepage provided to you by handler A"
And handler B has a similar structure (but does something very different).
Then the app looks like
# app.py
from flask import Flask
from handler_A import handler_A
from handler_B import handler_B
app = Flask(__name__)
app.register_blueprint(handler_A, url_prefix='/one')
app.register_blueprint(handler_A, url_prefix='/two')
app.register_blueprint(handler_B, url_prefix='/three')
app.register_blueprint(handler_B, url_prefix='/four')
The part that I can't seem to do "nicely" is figuring out whether an endpoint within Handler A (for example) was called from a URL prefixed with /one/ or /two/. This is an important distinction for me, though as soon as the handler is called that information is obscured. I've looked through the docs but can't find a clean way to do this.
The following are the options I've thought of/attempted so far:
Grabbing request.path from within the handler and pulling out the prefix from the string. This is simple and it works, but seems awkward
Setting up a separate blueprint for each top-level path, and "merging" the execution flow from four blueprints into the two handlers I have
Making the top-level of the URL be a parameter that's recorded in the context (like in https://flask.palletsprojects.com/en/1.1.x/patterns/urlprocessors/#internationalized-blueprint-urls). However I think this also requires me to write a custom URL processor, if I want "one" and "two" to match one url_prefix, while "two" and "three" match another.
Is there smart way to do this that I'm missing?
To me the request.path is a good solution. It's awkward in that it's an implicit parameter of the function; the only difference between this and the solution you're looking for is this explicit vs. implicit parameters. I think this method albeit imperfect is more readable than the complexities required to make this explicit.
If you want, you could extract the core function and pass it the results of the request.path s.t. it's more explicit.
#handler_A('/somepage', methods=['GET'])
def getPage():
return pageForPath(request.path)
def pageForPath(path):
return '<html> .... path ... </html>
I am rather new to django, and am looking at where to define a slug in django when creating a backend without models. the url is created as such:
url(r'^main/(?P<slug>[-\w]+)/', include('main.urls')),
I have slugs within my main.urls which I define inside of each view function. Im not exactly sure where to define this slug(link, whatever you may call it). On other django slug examples, the common way is in a model, and I am currently talking to a program rather then creating my own models.
Would this be in the urls.py, or views.py (in the project, not app)?
Thank you so much. Hopefully this is understandable.
It's not hard. Really.
In url-configs each entry is simply a regular expression which has to match a url that is visited by an end user. r'^main/(?P<slug>[-\w]+)/' will for example match with: http://localhost:8000/main/some-slug/
You can use a special kind of syntax in your regular expression to extract matched data and pass that data as a variable to your view function.
The bit that does that is (?P<slug>[-\w]+) it puts matched words (in this case a slug) into a variable called slug (the <slug> part, it defines the variable name). In this humble example the slug variable will be set to "some-slug".
The variable will be accessible in your view like this:
from django.http import HttpResponse
def handle_my_view(request, slug=homepage):
# do stuff with slug
return HttpResponse("I did stuff with slug: {}".format(slug))
Learn more about, and fiddle with regular expressions
At http://www.regexr.com
But why do i see slugs used in models?:
A slug (or named variable, coming from a url 'interception') can be used for anything. Commonly the slug variable itself will be used to retrieve a database record of some sorts... And that involves using models.
You can do whatever you want with them; add stuff, subtract stuff, capitalize, whatever. The sky is the limit.
From the Django docs:
https://docs.djangoproject.com/en/1.10/topics/http/urls/#named-groups
Named groups
The above example used simple, non-named regular-expression groups (via parenthesis) to capture bits of the URL and pass them as positional arguments to a view. In more advanced usage, it’s possible to use named regular-expression groups to capture URL bits and pass them as keyword arguments to a view.
In Python regular expressions, the syntax for named regular-expression groups is (?Ppattern), where name is the name of the group and pattern is some pattern to match.
Here’s the above example URLconf, rewritten to use named groups:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]
This accomplishes exactly the same thing as the previous example, with one subtle difference: The captured values are passed to view functions as keyword arguments rather than positional arguments. For example:
A request to /articles/2005/03/ would call the function views.month_archive(request, year='2005', month='03'), instead of views.month_archive(request, '2005', '03').
A request to /articles/2003/03/03/ would call the function views.article_detail(request, year='2003', month='03', day='03').
In practice, this means your URLconfs are slightly more explicit and less prone to argument-order bugs – and you can reorder the arguments in your views’ function definitions. Of course, these benefits come at the cost of brevity; some developers find the named-group syntax ugly and too verbose.
You can add a 'view' for a content type in kotti by doing something along these lines:
from kotti_mysite.views import poll_view
config.add_view(
poll_view,
context=Poll,
name='view',
permission='view',
renderer='kotti_mysite:templates/poll.pt',
)
(more details: http://kotti.readthedocs.org/en/latest/first_steps/tut-2.html)
You can also have multiple views, and use the 'set default view', but sometimes it's convenient to have several similar views with very similar urls.
For example, in plone, its trivial to have a url structure like this:
/blah/item/ <--- Normal view
/blah/item/json <--- Json version of item
/blah/item/pdf <--- PDF download of item
You can... sort of, do a similar thing in kotti by screwing with the view you create and rendering different content based on get/post params, but it's messy, and frankly, rather rubbish.
The only solution I've found is to have a custom content type 'JsonView' that has a json renderer, and add it as a child of the parent object, and it's renderer looks for the parent content, and renders that.
However, doing this requires you to manually create a 'JsonView' child for every instance of the type you want, which is also rather cumbersome.
Is there a better way of doing this?
--
Nb. Specifically note that having a custom view /blah/item/json isn't any use at all; any type of item, in any parent folder should be able to render in the way described above; using a single static route isn't the right solution.
You can register a json view for all your content like this:
from kotti.interfaces import IContent
config.add_view(
my_json_view,
context=IContent,
name='json',
permission='view',
renderer='json',
)
This way, when you open /blah/json, where /blah points to some content, it will call your my_json_view view.
SQLAlchemy's new class object inspection system might help you write a useful generic json view that works for more than one content type. Alternatively, you can register your view for specific content types only (by use of a more specific context argument in config.add_view).
Using renderer='json' you tell Pyramid that you want to use its json renderer.
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.
I am looking at moving a web app from pylons to pyramid (formally repoze.bfg) because traversal will fit my app much better than url dispatch.
Currently, when I have a obj with a number of views, I have the view names prefixed with a '+' in the url. e.g.:
/path/to/obj/ (default view)
/path/to/obj/+custom_view1
/path/to/obj/+custom_view2
/path/to/obj/+edit
/path/to/obj/+delete
/path/to/obj/sub_obj/
Pyramid has a feature to handle this is a nice way, but it uses the prefix "##". Is there a way to change this to "+" so that I can keep my urls the same (you know what they say about cool urls,) and use this feature.
Yes and no, you can change view prefix from "##" but the new prefix should also be 2-symbold length, see sources for traverser.
Subclassing ResourceTreeTraverser and then registering it instead of the default one should do the trick:
from pyramid.traversal import ResourceTreeTraverser as BaseResourceTreeTraverser
class ResourceTreeTraverser(BaseResourceTreeTraverser):
VIEW_SELECTOR = "++"
from pyramid.config import Configurator
from pyramid.interfaces import ITraverser
from zope.interface import Interface
config = Configurator()
config.registry.registerAdapter(ResourceTreeTraverser, (Interface,), ITraverser)
Personally, I think VIEW_SELECTOR should be refactored to allow any length view prefixes.