Is it possible to define a route that will be redirected to if Django could not find a match in the defined URLs?
For example, let's say I have:
urlpatterns = [
path('ajaxlogin', views.ajax_login, name='ajaxlogin'),
path('ajaxprofile', views.ajax_profile, name='ajaxprofile'),
]
Can I define a "dynamic" URL of a specific view that will be redirected to if the path doesn't exist? Suppose I enter the URLs /ajaxsignup and /ajaxdelete, which are not defined, but have them redirected to a URL of a certain view?
In other words:
urlpatterns = [
path('ajaxlogin', views.ajax_login, name='ajaxlogin'),
path('ajaxprofile', views.ajax_profile, name='ajaxprofile'),
path('every-other-url', views.another_view, name='path-everything-else'),
]
I know there's Django's error handlers, but are there any other ways to achieve this? I have my frontend based separately on a React application, so I'd very much appreciate a "dynamic" URL rather than using Django's default 404 responses.
I suspect that if path couldn't do it, I could use the old url with regex -- but I still have no clue.
Thanks in advance.
Just add a wildcard re_path at the end of your urlpatterns:
from django.http import HttpResponse
from django.urls import re_path
def http_response(request):
return HttpResponse('<h1>Hello HttpResponse</h1>')
urlpatterns = [
path('ajaxlogin', views.ajax_login, name='ajaxlogin'),
path('ajaxprofile', views.ajax_profile, name='ajaxprofile'),
path('every-other-url', views.another_view, name='path-everything-else'),
re_path(r'.*', http_response), # only if the above routes don't trigger a match
]
It processes routes in order, so if it doesn't find a route before it hits the wildcard, it will return that one.
Related
I have setup my website whereby the main parental/tree structure is Home > Shop > Category > Product" with > denoting parent of.
This works fine, however when accessing a Product(Page) the url is automatically (and correctly) configured at /shop/test-category/test-product by Wagtail.
I would like to change it so that the product is actually displayed as being at the root level (even though it isn't). So if a user accesses Test Product it would be at /test-product/.
Looking through the docs, the RoutablePageMixin seems like it might do the trick but I have no idea how to go about implementing it. Any ideas?
This solution will make the product available at both URLS:
/shop/test-category/test-product/
/test-product/
Approach:
You are correct that you will need to use RoutablePageMixin, be sure to install it in your installed_apps as per the instructions before importing.
The example below adds RoutablePageMixin to your HomePage, as this is the page that will be located at the root / URL. We do a regex check and match for a single slug before a trailing /.
Then we see if we can find a ProductPage with that slug, and serve (or redirect) to that page. Finally, if there is no match, we call the home_page's serve method with the current request to handle anything else. This may be an incorrect URL or a correct sub-page URL.
Caveats:
If you have a sub-page at the same slug as a product page, the sub-page will never load, there are no smarts in this code to work around that. You could put some logic in the validation of product names and sub-page names if this becomes an issue in the future.
This does not consider SEO issues, search engines will treat these as different pages and hence you may want to think about declaring your canonical URL in your meta tags.
This does not redirect back from /shop/test-category/test-product/ to /test-product/ - that can be done by overriding the serve method on your ProductPage and redirecting to something like home_page.url + '/' + self.slug.
Example Code:
# models.py - assuming all your models are in one file
from django.db import models
from django.shortcuts import redirect # only needed if redirecting
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.contrib.wagtailroutablepage.models import RoutablePageMixin, route
from wagtail.core.models import Page
class ProductPage(Page):
price = models.DecimalField(max_digits=5, decimal_places=2)
content_panels = Page.content_panels + [
FieldPanel('price')
]
class HomePage(RoutablePageMixin, Page):
#route(r'^(?P<product_slug>[\w-]+)/$')
def default_view(self, request, product_slug=None):
"""Route will match any `my-product-slug/` after homepage route."""
product_page = Page.objects.exact_type(ProductPage).filter(slug=product_slug).first()
if product_page:
# option 1 - redirect to the product's normal URL (non-permanent redirect)
# return redirect(product_page.specific.url)
# option 2 - render the product page at this URL (no redirect)
return product_page.specific.serve(request)
else:
# process to normal handling of request so correct sub-pages work
return self.serve(request)
I'm looking for functionality similar to flask frameworks url_for for appengine. It takes a classes name redirects to url that's been associated with it in webapp2.WSGIApplication.
So if I have this.
app = webapp2.WSGIApplication([
("/", PostsPage),
("/login", LoginPage),
], debug=True)
So redirect(url_for(LoginPage)) would redirect me to /login.
This is relatively easy to build if you have access to the argument that was passed to the WSGIApplication constructor:
class PostsPage:
pass
class LoginPage:
pass
urls = [
("/", PostsPage),
("/login", LoginPage),
]
def url_for(cls):
return [x[0] for x in urls if x[1] == cls][0]
print url_for(LoginPage)
You can name the different 'routes' like
app = webapp2.WSGIApplication([
webapp2.Route(r'/', handler=PostsPage, name ='main'),
webapp2.Route(r'/login', handler=LoginPage, name ='login'),
], debug = True)
Now you can redirect using the name either as you suggest or using
return self.redirect_to('login')or return self.redirect_to(main`)
I have included a return statement as the redirect first happens after you return.
I am learning the webapp2 framework with its powerful Route mechanism.
My application is supposed to accept URIs like these:
/poll/abc-123
/poll/abc-123/
/poll/abc-123/vote/ # post new vote
/poll/abc-123/vote/456 # view/update a vote
Polls may optionally be organized into categories, so all the above should work also like this:
/mycategory/poll/abc-123
/mycategory/poll/abc-123/
/mycategory/poll/abc-123/vote/
/mycategory/poll/abc-123/vote/456
My incorrect configuration:
app = webapp2.WSGIApplication([
webapp2.Route('/<category>/poll/<poll_id><:/?>', PollHandler),
webapp2.Route('/<category>/poll/<poll_id>/vote/<vote_id>', VoteHandler),
], debug=True)
Question: How could I fix my configuration?
If possible it should be optimized for GAE CPU-time/hosting fee. For example, it may be faster if I add two lines for each entry: one line with category and another one without category...
webapp2 has a mechanism to reuse common prefixes, but in this case they vary, so you can't avoid duplicating those routes, as in:
app = webapp2.WSGIApplication([
webapp2.Route('/poll/<poll_id><:/?>', PollHandler),
webapp2.Route('/poll/<poll_id>/vote/<vote_id>', VoteHandler),
webapp2.Route('/<category>/poll/<poll_id><:/?>', PollHandler),
webapp2.Route('/<category>/poll/<poll_id>/vote/<vote_id>', VoteHandler),
], debug=True)
You should not worry about adding many routes. They are really cheap to build and match. Unless you have tens of thousands, reducing the number of routes won't matter.
A small note: the first route accepts an optional end slash. You could instead use the RedirectRoute to accept only one and redirect if the other is accessed, using the option strict_slash=True. This is not well documented but has been around for a while. See the explanation in the docstring.
I am going to add my solution to this as a complimentary answer on top of #moraes.
So other people having problems like below can get a more complete answer.
Trailing Slash Problem
Optional Parameter Problem
In addition, I figured out how to route both /entity/create and /entity/edit/{id} in one regex.
Below are my routes that supports the following url patterns.
/
/myentities
/myentities/
/myentities/create
/myentities/create/
/myentities/edit/{entity_id}
SITE_URLS = [
webapp2.Route(r'/', handler=HomePageHandler, name='route-home'),
webapp2.Route(r'/myentities/<:(create/?)|edit/><entity_id:(\d*)>',
handler=MyEntityHandler,
name='route-entity-create-or-edit'),
webapp2.SimpleRoute(r'/myentities/?',
handler=MyEntityListHandler,
name='route-entity-list'),
]
app = webapp2.WSGIApplication(SITE_URLS, debug=True)
Below is my BaseHandler that all my handlers inherit from.
class BaseHandler(webapp2.RequestHandler):
#webapp2.cached_property
def jinja2(self):
# Sets the defaulte templates folder to the './app/templates' instead of 'templates'
jinja2.default_config['template_path'] = s.path.join(
os.path.dirname(__file__),
'app',
'templates'
)
# Returns a Jinja2 renderer cached in the app registry.
return jinja2.get_jinja2(app=self.app)
def render_response(self, _template, **context):
# Renders a template and writes the result to the response.
rv = self.jinja2.render_template(_template, **context)
self.response.write(rv)
Below is my MyEntityHandler python class with the get() method signature for the Google App Engine Datastore API.
class MyEntityHandler(BaseHandler):
def get(self, entity_id, **kwargs):
if entity_id:
entity = MyEntity.get_by_id(int(entity_id))
template_values = {
'field1': entity.field1,
'field2': entity.field2
}
else:
template_values = {
'field1': '',
'field2': ''
}
self.render_response('my_entity_create_edit_view_.html', **template_values)
def post(self, entity_id, **kwargs):
# Code to save to datastore. I am lazy to write this code.
self.redirect('/myentities')
I am having problems getting the logout link work in GAE (Python).
This is the page I am looking at.
In my template, I create a link
<p>Logout</p>
But when I click on it I get "broken link" message from Chrome. The url for the link looks like this:
http://localhost:8085/users.create_logout_url(
My questions:
Can anybody explain how this works in general?
What is the correct url for the dev server?
What is the correct url for the app server?
What is the ("/") in the logout url?
Thanks.
EDIT
This link works; but I don't know why:
<p>Logout</p>
What sort of templates are you using? It's clear from the output that you're not escaping your code correctly.
Seems to me that you want to do this instead:
self.response.out.write("This is the url: %s", users.create_logout_url("/"))
You could also pass it to your template, using GAEs implemented django templates.
from google.appengine.ext.webapp import template
...
...
(inside your request handler)
class Empty: pass
data = Empty()
data.logout = users.create_logout_url("/")
self.response.out.write(template.render(my_tmpl, {'data': data})
A useful approach is to add all sorts of info to a BaseRequestHandler and then use this as base class for all of your other request handler classes.
from google.appengine.ext import webapp
...
class BaseRequestHandler(webapp.RequestHandler):
def __init__(self):
webapp.RequestHandler.__init__(self) # extend the base class
class Empty: pass
data = Empty()
data.foo = "bar"
Then your new classes will have access to all the data you provided in the base class.
class OtherHandler(BaseRequestHandler):
def get(self):
self.response.out.write("This is foo: %s" % self.data.foo) # passes str "bar"
Hope it helps.
A.
Hi following more or less what this article is showing for the user account stuff. In gwt I store server side the logout/login url and I pass them to the client
http://www.dev-articles.com/article/App-Engine-User-Services-in-JSP-3002
Using tipfy, how does one express a catch-all route in urls.py if more specific routes do not match?
Tipfy uses Werkzeug-like routing, so there's this (in urls.py):
def get_rules(app):
rules = [
Rule('/<any>', endpoint='any', handler='apps.main.handlers.MainHandler'),
Rule('/', endpoint='main', handler='apps.main.handlers.MainHandler'),
]
This will match most random entry points into the application (app.example.com/foo, app.example.com/%20 etc) but does not cover the app.example.com/foo/bar case which results in a 404.
Alternatively, is there a graceful way to handle 404 in Tipfy that I'm missing?
I think you want:
Rule('/<path:any>', endpoint='any', handler='apps.main.handlers.MainHandler')
The path matcher also matches slashes.
Maybe you could write custom middle ware:
class CustomErrorPageMiddleware(object):
def handle_exception(self, e):
return Response("custom error page")
To enable it add somewhere to tipfy config:
config['tipfy'] = {
'middleware': [
'apps.utils.CustomErrorPageMiddleware',
]
}
It gives you quite a flexibility - you could for example send mail somewhere to inform that there was a problem. This will intercept all exceptions in your application