How to use template inheritance with Chameleon? - python

I am using latest Pyramid to build a web app. Somehow we have started using Chameleon as the template engine. I have used Mako before and it was extremely simple to create a base template. Is this possible with chameleon as well?
I have tried to look through the docs but I can not seem to find an easy solution.

With Chameleon >= 2.7.0 you can use the "load" TALES expression. Example:
main.pt:
<html>
<head>
<div metal:define-slot="head"></div>
</head>
<body>
<ul id="menu">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<div metal:define-slot="content"></div>
</body>
</html>
my_view.pt:
<html metal:use-macro="load: main.pt">
<div metal:fill-slot="content">
<p>Bonjour tout le monde.</p>
</div>
</html>

Another option, which was used prior Chameleon got an ability to load templates from the filesystem, is to pass the "base" template as a parameter.
To simplify things, I often wrap such stuff into a "theme" object:
class Theme(object):
def __init__(self, context, request):
self.context = context
self.request = request
layout_fn = 'templates/layout.pt'
#property
def layout(self):
macro_template = get_template(self.layout_fn)
return macro_template
#property
def logged_in_user_id(self):
"""
Returns the ID of the current user
"""
return authenticated_userid(self.request)
which can then be used like this:
def someview(context, request):
theme = Theme(context, request)
...
return { "theme": theme }
Which then can be used in the template:
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
metal:use-macro="theme.layout.macros['master']">
<body>
<metal:header fill-slot="header">
...
</metal:header>
<metal:main fill-slot="main">
...
</metal:main>
</body>
</html>

Make a template here:
<proj>/<proj>/templates/base.pt
with contents:
<html>
<body>
<div metal:define-slot="content"></div>
</body>
</html>
Use the template here:
<proj>/<proj>/templates/about_us.pt
by inserting the contents:
<div metal:use-macro="load: base.pt">
<div metal:fill-slot="content">
<p>Hello World.</p>
</div>
</div>

Related

Call python's object method from flask jinja2 html file

I am trying to create youtube video downloader application using pytube and flask. All is done, except that a want to call pytube's stream download method from within the html script tag. How can i do it.
Here's my flask code
from flask import Flask, render_template, request
from pytube import YouTube
app = Flask(__name__)
#app.route("/")
def index():
return render_template("index.html", data=None)
#app.route("/download", methods=["POST", "GET"])
def downloadVideo():
if request.method == "POST":
url = request.form["videourl"]
if url:
yt = YouTube(url)
title = yt.title
thumbnail = yt.thumbnail_url
streams = yt.streams.filter(file_extension='mp4')
data = [title, thumbnail, streams, yt]
return render_template("index.html", data=data)
if __name__ == "__main__":
app.run(debug=True)
and here's my html code
<!DOCTYPE html>
<html>
<head>
<title> Youtube Downloader </title>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="stylesheet" href="static/css/style.css">
</head>
<body>
<div class="main">
<div class="header">
<div>
<img src="static/img/icon.png" width="48" height="48">
<h2> Youtube Downloader </h2>
</div>
<div>
<p> Convert and download youtube videos </p>
<p> in MP4 for free </p>
</div>
</div>
{% if not data %}
<div class="dform">
<form action="http://127.0.0.1:5000/download", method="POST">
<div class="inputfield">
<input type="input" name="videourl" placeholder="Search or Paste youtube link here" autocomplete="off">
<button type="submit"> Download </button>
</div>
</form>
</div>
{% else %}
<div class="videoinfo">
<img src="" class="thumbnail">
<h2> {{data[0]}} </h2>
</div>
<div class="quality">
<select id="streams">
{% for stream in data[2][:3] %}
<option value="{{stream.itag}}"> {{stream.resolution}} </option>
{% endfor %}
</select>
</div>
{% endif %}
</div>
<script type="text/javascript">
const image = document.querySelector(".thumbnail");
const select = document.querySelector("select");
let url = `{{data[1]}}`;
if (image) {
image.src = `${url}`;
window.addEventListener('change', function() {
var option = select.options[select.selectedIndex].value;
console.log(option);
{% set stream = data[3].get_by_itag(option) %}
{% stream.download() %}
});
}
</script>
</body>
</html>
I am trying to download the video using itag when a user clicks an option in the select element by using pytube get_by_itag() method.
From what I understand you want to do two things. You want to create a route on your flask app that will let serve up the youtube video based on an itag, and you want to be able to call that route from javascript.
This answer shows how to create a route to download the video.
To call a url that starts a file download from javascript you'll need to use the fetch method and open that link into an iFrame. This answer covers it.
Let me know if that covers your question.

Ordering query data from html template in flask

I am ordering the query result using time but I want to give the user the ability to choose the way he wants to order data lets say he wants to use alphabetical order or he wants to order with ascending or descending order I want to know how to that in flask, sorry for my bad English
this is my code
#routes.route('/posts')
def show_posts():
page=request.args.get('page',1,type=int)
posts=Posts.query.order_by(Posts.timestamp.desc).paginate(page,per_page=10)
return render_template('routes/show_posts.html',posts=posts)
ok, so this is my first flask answer (I only yesterday finished tutorials):
EDIT/INFO: at the end I didn't use any forms to accomplish this, just simple routes, but forms could be used too, this just seemed a bit more simple (more because I couldn't figure out how to use those forms for this :))
EDIT2/INFO: also I don't think for this particular code there is any need for that methods argument in decorators so that could probably be removed for this specific code
the main file (it contains everything because that was easier than to show each module I would have used)
from flask import Flask, render_template, url_for
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
#app.route('/posts/filter_by/<string:key>')
#app.route('/posts', methods=['GET', 'POST'])
#app.route('/')
def all_posts(key=None):
posts = Post.query.order_by(Post.title.asc()).all()
if key == 'ZA':
posts = Post.query.order_by(Post.title.desc()).all()
return render_template('posts.html', posts=posts)
if __name__ == '__main__':
app.run(debug=True)
and here is the template (uses bootstrap stuff):
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body>
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle"
type="button" id="dropdownMenu1" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
Sort by
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenu1">
<a class="dropdown-item" href="{{ url_for('all_posts', key='AZ') }}">A-Z</a>
<a class="dropdown-item" href="{{ url_for('all_posts', key='ZA') }}">Z-A</a>
</div>
</div>
{% for post in posts %}
<div class="card">
<div class="card-body">
<h4 class="card-title">{{ post.title }}</h4>
<p class="card-text">{{ post.content }}</p>
</div>
</div>
{% endfor %}
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>
</html>
So the first thing (top-down):
import all that is needed
then initialize the Flask and SQLAlchemy classes and also config the app to the database
Create the post model for storing posts and stuff (that I don't exactly know)
So then comes the main part - the route.
So first of I create a route with a variable:
#app.route('/posts/filter_by/<string:key>')
this will allow to request this URL and be able to get additional information (from that variable) that will help with handling the response
then add more routes so that the url looks cleaner and that is pretty much the only reason (as far as I know)
then define the function:
name it what You feel like but You have to pass the variable as an argument and since multiple routes are used and probably for other reasons too set a default value for that argument so that it is not necessary to be provided
then using simple if statements and using that variable handle how posts are ordered
then return the template including those posts in that order
Now about the template
First of it uses bootstrap stuff and I got the most code from there (just copy pasted from their website) or here which is a nice cheatsheet.
So I copied the dropdown menu from that cheatsheet and just changed some values, most notably the href attribute:
Those I replaced with url_for functions and passed the variable too as You can see so that the request sent is with that variable that will allow to handle how posts are ordered
Hope all is clear, if You have any more questions, ask them.
first thing first Matiiss your answer is very good thanks for the help, now I have found another way it has some similarities to Matiiss answer i want to share it so if anyone found this question now you have two answers to choose from.
#routes.route('/posts')
def show_posts():
posts= Posts.query.order_by(Posts.name.desc()).all()
filter_rule = request.args.get('filter')
if filter_rule == 'A-Z':
posts= Posts.query.order_by(Posts.name).all()
return render_template('posts.html',posts=posts)
HTML part
<div class="row">
<div class="col-md-3">
<div class="nav nav-pills flex-column" role="tablist" aria-orientation="vertical">
<a class="nav-item nav-link"
href="{{ url_for('.show_posts', filter='A-Z') }}">
A-Z
</a>
<a class="nav-item nav-link"
href="{{ url_for('.show_posts', filter='Z-A') }}">
Z-A
</a>
</div>
</div>

django render_to_response on google app engine

I am experiencing a weird issue using Django on Google App Engine. I have a file upload form defined within a django-app like this:
class ConvertForm(forms.Form):
from = forms.ChoiceField(choices=choices_from,
label='from:')
to = forms.ChoiceField(choices=choices_to,
label='to:')
class Meta:
fields = ('from','to')
And then I have in my app.views file the following:
def convert(request):
if request.POST:
form = ConvertForm(request.POST,request.FILES)
if form.is_valid():
from = form.cleaned_data['from']
to = form.cleaned_data['to']
# Redirect to a result page after post to avoid duplicates
return HttpResponseRedirect('/convert/results')
else:
form = ConvertForm()
args = {}
args.update(csrf(request))
args['form']=form
return render_to_response('convert.html',args)
The form-part of my convert.html template looks like this:
<form action="/convert/convert/" method="post" enctype="multipart/form-data">{%\
csrf_token %}
<ul>
{{ form.as_ul }}
</ul>
<input type="submit" name="submit" value="Convert">
</form>
It's supposed to be a file-upload form (hence the multipart), but I edited the form-contents for brevity.
Now, when I browse the proper url, nothing happens. I can clearly see that the correct functions are being called, since replacing the body of the convert() function with a simple
return render_to_response('convert_test.html',{'some_text':some_text})
displays the value of some_text in the browser window. Is there any additional quirks when dealing with forms within GAE that I am missing, or why isn't convert.html being rendered with the form? I should mention that all of this is on localhost, I haven't deployed it yet.
Edit: After some more fiddling around with this, it seems that maybe the source of the error is in the inheritance of templates. If I take out all django-tags {} in the convert.html, I can get the form to render correctly.
So, the question is now how do I properly set up template inheritance within GAE?
My full convert.html template looks like this:
{% extends "base.html" %}
{% block content %}
<h2>[ convert ]</h2>
<form action="/convert/convert/" method="post" enctype="multipart/form-data">{% \
csrf_token %}
<ul>
{{ form.as_ul }}
</ul>
<input type="submit" name="submit" value="Convert">
</form>
{% endblock %}
So basically redefining the content block of the base.html template. This was working perfectly before I tested it on GAE, so I can't shake the feeling that's involved somehow.
If it is relevant, my django settings.py looks like this for the templates:
TEMPLATE_DIRS = (
os.path.join(os.path.dirname(__file__),'..','convert','templates'),
os.path.join(os.path.dirname(__file__),'..','templates'),
)
And as I said, taking out the {}-tags from convert.html gives me the form, but rendered on its own in an otherwise completely white and empty page.
This is the contents of my base.html template
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/\
DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="description" content="Your description goes here" />
<meta name="keywords" content="your,keywords,goes,here" />
<meta name="author" content="Your Name" />
<link rel="stylesheet" type="text/css" href="/static/css/basic-minimal.css" ti\
tle="Basic Minimal" media="all" />
<title>Basic Minimal v1.2</title>
</head>
<body class="light">
<div id="wrap">
<div id="header">
<h1>[ snazzy title ]</h1>
</div>
<div id="sidebar">
<ul>
<li>[ Convert ]</li>
<li>[ Transform ]</li>
<li>[ Help ]</li>
<li>[ References ]</li>
</ul>
</div>
<div id="content">
<h2>[ more title ]</h2>
<p>Text</p>
<p>More text</p>
</div>
</div>
</body>
</html>
This works beautifully for the "title" page (even the css gets loaded), but rendering other templates as convert.html above using this as a base does not work.

Pyramid Chameleon base template orientation

I'm very new at Pyramid, I have used Django in the past, but I can't find a clean explanation of how to use base templating in Pyramid Chameleon templates.
I have a very simple .pt file which I want to be my base.pt template it's something like this:
<link href="static/bootstrap/css/bootstrap.css" rel="stylesheet">
<head>
</head>
<body>
<header class="navbar navbar-inverse navbar-fixed-top bs-docs-nav" role="banner">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" type="button" data-toggle="collapse" data-target=".bs-navbar-collapse">
<span class="sr-only">Toggle navigation</span>
</button>
My project
</div>
</div>
</header>
</body>
</html>
As you can see I try to have bootstrap header in all the following templates of my project, so what do I need to have so that all templates inherit or have base.pt as base template ? In Django I will just use {% include base.html %}
Chameleon and Mako are the two templating languages with support currently bundled within Pyramid. However, Jinja2 is officially supported by the pyramid_jinja2 addon and is easily activated. Jinja2 provides a syntax very similar to Django's if you do not wish to learn Chameleon.
config.include('pyramid_jinja2')
#view_config(..., renderer='myapp:templates/home.jinja2')
def view(request):
return {}

Python Pyramid - Add multiple chameleon base templates

I am using this procedure to use a base template which the other templates can derive from.
How can I create multiple base templates?
Just register them both:
from pyramid.renderers import get_renderer
def add_base_template(event):
base = get_renderer('templates/base.pt').implementation()
base2 = get_renderer('templates/base2.pt').implementation()
event.update({'base': base, 'base2': base2})
And then choose which to use in your template for each page:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
metal:use-macro="base">
<tal:block metal:fill-slot="content">
My awesome content.
</tal:block>
</html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
metal:use-macro="base2">
<tal:block metal:fill-slot="content">
Content on a totally different page.
</tal:block>
I believe a template doesn't have to be the whole HTML element, so you could instead expand 2 macros into the same final template
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal">
<body>
<div metal:use-macro="section1">
<tal:block metal:fill-slot="content">
Content for template "section1".
</tal:block>
</div>
<div metal:use-macro="section2">
<tal:block metal:fill-slot="content">
Content for template "section2".
</tal:block>
</div>
</body>

Categories