Implementing breadcrumbs in Python using Flask? - python

I want breadcrumbs for navigating my Flask app. An option could be to use a general Python module like bread.py:
The bread object accepts a url string and grants access to the url
crumbs (parts) or url links (list of hrefs to each crumb) .
bread.py generates the breadcrumb from the url path, but I want the elements of the breadcrumb to be the title and link of the previously visited pages.
In Flask, maybe this can be done using a decorator or by extending the #route decorator.
Is there a way to have each call of a route() add the title and link of the page (defined in the function/class decorated with #route) to the breadcrumb? Are there other ways to do it? Any examples of breadcrumbs implemented for Flask?

So you're after "path/history" breadcrumbs, rather than "location" breadcrumbs to use the terminology from the wikipedia article?
If you want to have access to the user's history of visited links, then you're going to have to save them in a session. I've had a go at creating a decorator to do this.
breadcrumb.py:
import functools
import collections
import flask
BreadCrumb = collections.namedtuple('BreadCrumb', ['path', 'title'])
def breadcrumb(view_title):
def decorator(f):
#functools.wraps(f)
def decorated_function(*args, **kwargs):
# Put title into flask.g so views have access and
# don't need to repeat it
flask.g.title = view_title
# Also put previous breadcrumbs there, ready for view to use
session_crumbs = flask.session.setdefault('crumbs', [])
flask.g.breadcrumbs = []
for path, title in session_crumbs:
flask.g.breadcrumbs.append(BreadCrumb(path, title))
# Call the view
rv = f(*args, **kwargs)
# Now add the request path and title for that view
# to the list of crumbs we store in the session.
flask.session.modified = True
session_crumbs.append((flask.request.path, view_title))
# Only keep most recent crumbs (number should be configurable)
if len(session_crumbs) > 3:
session_crumbs.pop(0)
return rv
return decorated_function
return decorator
And here's a test application that demonstrates it. Note that I've just used Flask's built-in client side session, you'd probably want to use a more secure server-side session in production, such as Flask-KVsession.
#!/usr/bin/env python
import flask
from breadcrumb import breadcrumb
app = flask.Flask(__name__)
#app.route('/')
#breadcrumb('The index page')
def index():
return flask.render_template('page.html')
#app.route('/a')
#breadcrumb('Aardvark')
def pagea():
return flask.render_template('page.html')
#app.route('/b')
#breadcrumb('Banana')
def pageb():
return flask.render_template('page.html')
#app.route('/c')
#breadcrumb('Chimp')
def pagec():
return flask.render_template('page.html')
#app.route('/d')
#breadcrumb('Donkey')
def paged():
return flask.render_template('page.html')
if __name__ == '__main__':
app.secret_key = '83cf5ca3-b1ee-41bb-b7a8-7a56c906b05f'
app.debug = True
app.run()
And here's the contents of templates/page.html:
<!DOCTYPE html>
<html>
<head><title>{{ g.title }}</title></head>
<body>
<h1>{{ g.title }}</h1>
<p>Breadcrumbs:
{% for crumb in g.breadcrumbs %}
{{ crumb.title }}
{% if not loop.last %}»{% endif %}
{% endfor %}
</p>
<p>What next?</p>
<ul>
<li>Aardvark?</li>
<li>Banana?</li>
<li>Chimp?</li>
<li>Donkey?</li>
</ul>
</body>
</html>

i was trying to use the breadcrumb.py , but i was need to check:
if the new item "item = (flask.request.path, view title) " is already exist in the session crumbs, then i will delete all other items frome the index to the end, i do this for Avoid repetition in my session crumds.
flask.session.modified = True
item = (flask.request.path, view_title)
try:
if not item in session_crumbs:
session_crumbs.append(item)
else:
index = session_crumbs.index(item)
session_crumbs = session_crumbs[:index+1]
except:
pass
return rv
return decorated_function
return decorator

Related

Flask: redirecting nonexistent URL's

I was given instructions to do the following: modify an app.py file so that my website responds to all possible URLs (aka nonexistent extension like '/jobs', meaning that if an invalid URL is entered, it is redirect to the home index.html page. Here is a copy of my app.py as it stands, any ideas on how to do this?
from flask import Flask, render_template #NEW IMPORT!!
app = Flask(__name__) #This is creating a new Flask object
#decorator that links...
#app.route('/') #This is the main URL
def index():
return render_template("index.html", title="Welcome",name="home")
#app.route('/photo')
def photo():
return render_template("photo.html", title="Home", name="photo-home")
#app.route('/about')
def photoAbout():
return render_template("photo/about.html", title="About", name="about")
#app.route('/contact')
def photoContact():
return render_template("photo/contact.html", title="Contact", name="contact")
#app.route('/resume')
def photoResume():
return render_template("photo/resume.html", title="Resume", name="resume")
if __name__ == '__main__':
app.run(debug=True) #debug=True is optional
I think that what you are looking for is probably just error handling. The Flask documentation has a section that shows how to do error handling.
But to summarize the important points from there:
from flask import render_template
#app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
You have an app instance, so you can just add this to your code. It's pretty clear that anytime there is a 404 or page does not exist, 404.html will be rendered.
Assuming you are working with jinja templates 404.htmls contents could be:
{% extends "layout.html" %}
{% block title %}Page Not Found{% endblock %}
{% block body %}
<h1>Page Not Found</h1>
<p>What you were looking for is just not there.
<p>go somewhere nice
{% endblock %}
This requires a base template (here layout.html). Say for now you don't want to work with jinja templating, just use this as a 404.html:
<h1>Page Not Found</h1>
<p>What you were looking for is just not there.
<p>go somewhere nice
In your case because you want to see the home page (index.html probably):
#app.errorhandler(404)
def page_not_found(e):
return render_template('index.html'), 404

pythonanywhere + flask: website just says 'unhandled exception'. How to get debugger to print stack trace?

Be forewarned of a triple-newbie threat - new to python, new to python anywhere, new to flask.
[pythonanywhere-root]/mysite/test01.py
# A very simple Flask Hello World app for you to get started with...
from flask import Flask
from flask import render_template # for templating
#from flask import request # for handling requests eg form post, etc
app = Flask(__name__)
app.debug = True #bshark: turn on debugging, hopefully?
#app.route('/')
#def hello_world():
# return 'Hello from Flask! wheee!!'
def buildOrg():
orgname = 'ACME Inc'
return render_template('index.html', orgname)
And then in [pythonanywhere-root]/templates/index.html
<!doctype html>
<head><title>Test01 App</title></head>
<body>
{% if orgname %}
<h1>Welcome to {{ orgname }} Projects!</h1>
{% else %}
<p>Aw, the orgname wasn't passed in successfully :-(</p>
{% endif %}
</body>
</html>
When I hit up the site, I get 'Unhandled Exception' :-(
How do I get the debugger to at least spit out where I should start looking for the problem?
The problem is render_template only expects one positional argument, and rest of the arguments are passed as keyword only arguments.So, you need to change your code to:
def buildOrg():
orgname = 'ACME Inc'
return render_template('index.html', name=orgname)
For the first part, you can find the error logs under the Web tab on pythonanywhere.com.
You need to also pass your name of orgname variable that is used in your template to render_template.
flask.render_template:
flask.render_template(template_name_or_list, **context)
Renders a template from the template folder with the given context.
Parameters:
template_name_or_list – the name of the template to be rendered,
or an iterable with template names the first one existing will be rendered
context – the variables that should be available in the context of the template.
So, change this line:
return render_template('index.html', orgname)
To:
return render_template('index.html', orgname=orgname)

Google App Engine: 404 Resource not found

I am trying to build a basic blog model using Google App Engine in Python. However, something's wrong with my code I suppose, and I am getting a 404 error when I try to display all the posted blog entries on a single page. Here's the python code:
import os
import re
import webapp2
import jinja2
from string import letters
from google.appengine.ext import db
template_dir = os.path.join(os.path.dirname(__file__), 'templates')
jinja_env = jinja2.Environment(loader = jinja2.FileSystemLoader(template_dir), autoescape=True)
class Handler(webapp2.RequestHandler):
def write(self, *a, **kw):
self.response.out.write(*a, **kw)
def render_str(self, template, **params):
t = jinja_env.get_template(template)
return t.render(params)
def render(self, template, **kw):
self.write(self.render_str(template, **kw))
def post_key(name = "dad"):
return db.Key.from_path('blog', name)
class Blogger(db.Model):
name = db.StringProperty()
content = db.TextProperty()
created = db.DateTimeProperty(auto_now_add = True)
def render(self):
self._render_text = self.content.replace('\n', '<br>')
return render_str("post.html", p = self)
class MainPage(Handler):
def get(self):
self.response.write("Visit our blog")
class BlogHandler(Handler):
def get(self):
posts = db.GqlQuery("SELECT * FROM Blogger order by created desc")
self.render("frontblog.html", posts = posts)
class SubmitHandler(Handler):
def get(self):
self.render("temp.html")
def post(self):
name = self.request.get("name")
content = self.request.get("content")
if name and content:
a = Blogger(name = name, content = content, parent = post_key())
a.put()
self.redirect('/blog/%s' % str(a.key().id()))
else:
error = "Fill in both the columns!"
self.render("temp.html", name = name, content = content, error = error)
class DisplayPost(Handler):
def get(self, post_id):
po = Blogger.get_by_id(int(post_id))
if po:
self.render("perma.html", po = po)
else:
self.response.write("404 Error")
app = webapp2.WSGIApplication([('/', MainPage),
('/blog', BlogHandler),
('/blog/submit', SubmitHandler),
('/blog/([0-9]+)', DisplayPost)], debug=True)
After posting my content, it gets redirected to a permalink. However, this is the error I am getting on submitting my post:
404 Not Found
The resource could not be found
Here's the frontblog.html source code, in case that would help:
<!DOCTYPE html>
<html>
<head>
<title>CS 253 Blog</title>
</head>
<body>
<a href="/blog">
CS 253 Blog
</a>
<div id="content">
{% block content %}
{%for post in posts%}
{{post.render() | safe}}
<br></br>
{%endfor%}
{% endblock %}
</div>
</body>
</html>
So basically, I am not being redirected to the permalink page. What seems to be the problem?
When you create your post, you're giving it a parent (not sure why). But when you get it, you do so by the ID only, and don't take into account the parent ID. In the datastore, a key is actually a path consisting of all the parent kinds and IDs/names and then those of the current entity, and to get an object you need to pass the full path.
Possible solutions here:
Drop the parent key, since it isn't doing anything here as you're always setting it to the same value;
Use it when you get the object: Blogger.get_by_id(post_id, parent=parent_key()) - obviously this only works if the parent is always the same;
Use the full stringified key in the path, rather than just the ID, and do Blogger.get(key) - you'll also need to change the route regex to accept alphanumeric chars, eg '/blog/(\w+)', and change the redirect to '/blog/%s' % a.key().

Dynamic navigation in Flask

I have a pretty simple site working in Flask that's all powered from an sqlite db. Each page is stored as a row in the page table, which holds stuff like the path, title, content.
The structure is hierarchical where a page can have a parent. So while for example, 'about' may be a page, there could also be 'about/something' and 'about/cakes'. So I want to create a navigation bar with links to all links that have a parent of '/' (/ is the root page). In addition, I'd like it to also show the page that is open and all parents of that page.
So for example if we were at 'about/cakes/muffins', in addition to the links that always show, we'd also see the link to 'about/cakes', in some manner like so:
- About/
- Cakes/
- Muffins
- Genoise
- Pies/
- Stuff/
- Contact
- Legal
- Etc.[/]
with trailing slashes for those pages with children, and without for those that don't.
Code:
#app.route('/')
def index():
page = query_db('select * from page where path = "/"', one=True)
return render_template('page.html', page=page, bread=[''])
#app.route('/<path>')
def page(path=None):
page = query_db('select * from page where path = "%s"' % path, one=True)
bread = Bread(path)
return render_template('page.html', page=page, crumbs=bread.links)
I already feel like I'm violating DRY for having two functions there. But doing navigation will violate it further, since I also want the navigation on things like error pages.
But I can't seem to find a particularly Flasky way to do this. Any ideas?
The "flasky" and pythonic way will be to use class-based view and templates hierarchy
First of all read documentation on both, then you can refactor your code based on this approach:
class MainPage(MethodView):
navigation=False
context={}
def prepare(self,*args,**kwargs):
if self.navigation:
self.context['navigation']={
#building navigation
#in your case based on request.args.get('page')
}
else:
self.context['navigation']=None
def dispatch_request(self, *args, **kwargs):
self.context=dict() #should nullify context on request, since Views classes objects are shared between requests
self.prepare(self,*args,**kwargs)
return super(MainPage,self).dispatch_request(*args,**kwargs)
class PageWithNavigation(MainPage):
navigation = True
class ContentPage(PageWithNavigation):
def get(self):
page={} #here you do your magic to get page data
self.context['page']=page
#self.context['bread']=bread
#self.context['something_Else']=something_Else
return render_template('page.html',**self.context)
Then you can do following:
create separate pages, for main_page.html and page_with_navigation.html
Then your every page "error.html, page.html, somethingelse.html" based on one of them.
The key is to do this dynamically:
Will modify prepare method a bit:
def prepare(self):
if self.navigation:
self.context['navigation']={
#building navigation
#in your case based on request.args.get('page')
}
else:
self.context['navigation']=None
#added another if to point on changes, but you can combine with previous one
if self.navigation:
self.context['extends_with']="templates/page_with_navigation.html"
else:
self.context['extends_with']="templates/main_page.html"
And your templates:
main_page.html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
{% block navigation %}
{% endblock %}
{% block main_content %}
{% endblock %}
</body>
</html>
page_with_navigation.html
{% extends "/templates/main_page.html" %}
{% block navigation %}
here you build your navigation based on navigation context variable, which already passed in here
{% endblock %}
page.html or any other some_page.html. Keep it simple!
Pay attention to first line. Your view sets up which page should go in there and you can easily adjust it by setting navigation= of view-class.
{% extends extends_with %}
{% block main_content %}
So this is your end-game page.
Yo do not worry here about navigation, all this things must be set in view class and template should not worry about them
But in case you need them they still available in navigation context variable
{% endblock %}
You can do it in one function by just having multiple decorators :)
#app.route('/', defaults={'path': '/'})
#app.route('/<path>')
def page(path):
page = query_db('select * from page where path = "%s"' % path, one=True)
if path == '/':
bread = Bread(path)
crumbs = bread.links
else:
bread = ['']
crumbs = None
return render_template('page.html', page=page, bread=bread, crumbs=crumbs)
Personally I would modify the bread function to also work for the path / though.
If it's simply about adding variables to your context, than I would recommend looking at the context processors: http://flask.pocoo.org/docs/templating/#context-processors

Pass variables to Flask's render_template

I want to pass multiple variables from my Flask view to my Jinja template. Right now, I can only pass one. How do I pass multiple variable when rendering a template?
#app.route("/user/<user_id>/post/<post_id>", methods=["GET", "POST"])
def im_research(user_id, post_id):
user = mongo.db.Users.find_one_or_404({'ticker': user_id})
return render_template('post.html', user=user)
The render_template function takes any number of keyword arguments. Query for each of the things you need in the template, then pass the results of each query as another argument to render_template.
#app.route("/user/<user_id>/post/<post_id>")
def im_research(user_id, post_id):
user = get_user_by_id(id)
post = get_user_post_by_id(user, id)
return render_template("post.html", user=user, post=post)
Python also has a built-in locals() function that will return a dict of all locally defined variables. This is not recommended as it may pass too much and obscures what specifically is being passed.
#app.route("/user/<user_id>/post/<post_id>")
def im_research(user_id, post_id):
user = get_user_by_id(id)
post = get_user_post_by_id(user, id)
return render_template("post.html", **locals())
return render_template('im.html', user= None, content = xxx, timestamp = xxx)
You can pass as many variables as you need.
The api
excerpt:
flask.render_template(template_name_or_list, **context)
Renders a
template from the template folder with the given context.
Parameters: template_name_or_list – the name of the template to be
rendered, or an iterable with template names the first one existing
will be rendered context – the variables that should be available in
the context of the template.
It is also possible to pass a list to render_template's context variables, and refer to its elements with Jinja's syntax in HTML.
example.py
mylist = [user, content, timestamp]
return render_template('exemple.html', mylist=l)
exemple.html
...
<body>
{% for e in mylist %}
{{e}}
{% endfor %}
</body>
...

Categories