I am trying to create pdf from html page using pdfkit inside Flask application, but I have troubles to load the static files (stylesheets) when using pdfkit.
I've tried to come up with minimal example. I've got this file structure
App
|_ static
| |- style.css
|_ templates
| |- base.html
|_ pdfs
| |- file.pdf
|_ application.py
Inside application.py:
import flask
import pdfkit
app = flask.Flask(__name__)
#app.route('/')
def index():
page = flask.render_template('base.html')
pdfkit.from_string(page, 'pdfs/file.pdf')
return page
#app.route('/download', methods=['POST'])
def download():
if flask.request.method == 'POST':
flask.send_from_directory(
directory='pdfs', filename='file.pdf', as_attachment=True)
else:
flask.redirect(flaks.url_for('index'))
if __name__ == '__main__':
app.run()
Stylesheet just adds red background to the td elements:
td {
background-color: red;
}
And finally the base.html:
<!DOCTYPE html>
<html>
<head>
<title>Test App</title>
<link href={{ url_for('static', filename='style.css') }} rel='stylesheet'>
</head>
<body>
<div id='to_pdf'>
<table>
<tr>
<th>Firstname</th>
</tr>
<tr>
<td>Jill</td>
</tr>
<tr>
<td>Eve</td>
</tr>
</table>
<form action='/download' method='POST'>
<button type="submit" value="Print" />PRINT</form>
</form>
</div>
</body>
</html>
Sorry for the loads of code, but it is actually pretty basic.
All I want is to have button, which once clicked downloads a pdf file (this pdf is html file converted to pdf.
It sort of works, but the console output is the following:
Loading pages (1/6)
Warning: Failed to load file:///static/style.css (ignore)
Counting pages (2/6)
Resolving links (4/6)
Loading headers and footers (5/6)
Printing pages (6/6)
Done
The problem
pdfkit, which basically invokes wkhtmltopdf cannot find the stylesheet files inside static folder. The application, where I have this problem is more robust, uses boostrap etc. and having the pdf output badly formatted is very undesirable..
Single CSS file
css = 'example.css'
pdfkit.from_file('file.html', css=css)
Multiple CSS files
css = ['example.css', 'example2.css']
pdfkit.from_file('file.html', css=css)
In your case try this:
css = "static/style.css"
page = flask.render_template('base.html')
pdfkit.from_string(page, 'pdfs/file.pdf', css=css)
return page
Your code results in a link that wkhtmltopdf tries to open as a regular file:
{{ url_for('static', filename='style.css') }}
becomes
/static/style.css
which wkhtmltopdf will look for at
file://static/style.css
Instead, add the _external=True flag to point it towards the file on the server:
{{ url_for('static', filename='style.css', _external=True) }}
results in (something like)
http://localhost:5000/static/style.css
Assuming your app has config.py file you can add this config value.
ABS_PATH_STATIC_FOLDER = "var/www/.." #here is an absolute path to static dir
Then specify css file by:
css = os.path.join(app.config['ABS_PATH_STATIC_FOLDER'], 'pdfstyle.css')
This will work if the css file is placed in static folder or any other folder
Then pass on css to pdfkit fromstring function
pdfkit.from_string(page, 'pdfs/file.pdf', css=css)
Related
I'm trying do develop a very simple web app using flask, following the example from this link.
My problem is: I want to show images from my file system in that page but it seems like browsers are protected against that. I have to use something like:
<img src="http://aMessyURL.png">
Instead of being able to use something like:
<img src="images/myImageName.png">
So, I can I (dynamically) show images from my file system?
You should be able to do it when hosting a simple server:
https://docs.python.org/2/library/simplehttpserver.html
This should at least allow you to load files in the same folder as your code.
Flask interprets <img src="images/myImageName.png"> as:
app/images/myImageName.png
Normally, Flask projects have a static folder inside app (app/static) which contains your CSS, JS and images. You can either render them like above or use url_for:
<link rel="shortcut icon" href="{{ url_for('static', filename='img/favicon.png') }}">
Which renders fully as:
<link rel="shortcut icon" href="/static/img/favicon.png">
You Have to specify static root path and then use code in html below
app = Flask(__name__, static_url_path='')
In HTML
<img src="/static/images/myImageName.png">
I have a flask app which has both front and back ends. I am using flask_assets to serve both css and js assets.
assets = Environment()
assets.init_app(app)
js = Bundle(common_blueprint.name + '/dist/javascripts/scout.js', output='dist/javascripts/scout.js')
css = Bundle(common_blueprint.name + '/dist/stylesheets/base.css', output='dist/stylesheets/scout.css')
assets.register("js_all", js)
assets.register("css_all", css)
app.register_blueprint(common_blueprint)
Now are are running in a weird issue, every time I deploy the app and hit the url, the application doesn't load the css file.
After few browser hard refreshes the css file is served correctly.
Any help is appreciated.
Thanks
If there is no specific reason to use flask_assets I suggest you to store both js and css files under the folder static in your flask project folder with the structure:
FLASK_PROJECT/static/javascripts/ # for your js files
FLASK_PROJECT/static/stylesheets/ # for your css files
Then in your index.html / base.html file you would load the files as follows:
<link rel="stylesheet" type="text/css" href="{{ url_for('static',filename='stylesheets/scout.css') }}">
<script type="text/javascript" src="{{ url_for('static',filename='javascripts/scout.js') }}"></script>
For more information you can take a look here
This question already has answers here:
CSS file not refreshing in browser
(15 answers)
Closed 5 years ago.
Here is my project hierarchy:
Project
Here is my HTML file :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='custom.css') }}"/>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
My .py flask file :
from flask import Flask, render_template, redirect, json, url_for, request, abort
from pymongo import MongoClient
client = MongoClient()
db = client.web_zoo
app = Flask(__name__)
#app.route('/')
def index():
return render_template('home.html')
if __name__ == "__main__":
app.run(debug=True)
And my CSS file :
body {
color: green;
}
h1 {
color: orange;
}
The HTML and .py files work fine and the HTML is rendered and displayed on the page. However the CSS doesn't change. It just displays it in normal black text.
NOTE : All those imports are there as I was doing something else. I stripped off everything else and still have this problem.
I can link bootstrap to it, and the CSS works, but not for my own CSS files.
I have looked at and tried these following solutions :
CSS Problems with Flask Web App
Application not picking up .css file (flask/python)
best way to override bootstrap css
It's probably quite obvious but I'm stuck. Any help? Thanks in advance!
EDIT - I have added a picture of the project hierarchy now instead of just a list. As you can see there are some other HTML files in there too, but none are used nor referenced.
Try
CTRL+SHIFT+R
in Chrome to reload the CSS.
On Mac try:
CMD+SHIFT+R
Otherwise right-click on the page in Chrome, select Inspect and select the Console tab to see what errors you get.
I use web.py Templator for my project and using the render('templates', base="") I combine a base layout with a page specific layout (simplified).
view.py
render = web.template.render('templates',base='layout')
return render.index()
shared layout file
layout.html
$def with (content)
<html>
<head>
<title>$content.title</title>
</head>
<body>
$:content
</body>
</html>
page specific template
index.html
$def with (values)
$var title: Hello Kitty
<p>Hello $values, how are you doin?</p>
The solution I'm looking for is how to achieve the following
login.html
$def with (values)
$var title: Enter credentials
<form>
<p><input type="text" name="user_name"></p>
<p><input type="password" name="user_pwd"></p>
<p><button type="submit">Open the gates</button></p>
</form>
$block_begin
<script>
// When the form is submitted, check the required fields and inform user
// if any data is missing or looks weird
</script>
$block_end
</body>
</html>
My question is, how do I add the script to the login.html template but not the index.html template? I'm not interested in having to add all JS logic to all pages, I would like to add this $block_begin/$block_end so that it appears at the bottom of the layout.html like this
layout.html
$def with (content)
<html>
<head>
<title>$content.title</title>
</head>
<body>
$:content
$block_begin
$block_end
</body>
</html>
The $block_begin/$block_end was just something I came up with to better explain myself.
Just to be clear, template -> defwith sections is a grammar, not an example. To use templates, check http://webpy.org/docs/0.3/templetor for examples.
At a high level, you create templates similar to
=== templates/index.html ===
$def with(values)
$var title: Hello Kitty
<p>Hello $values, how are you doin?</p>
=== templates/layout.html ===
$def with(content)
<html>
<head>
<title>$content.title</title>
</head>
<body>
$:content
</body>
</html>
Then in your python you render the template, passing in any parameters specified in the template. ("values" in this example.) The named template (index.html) is render using the base (layout.html), and as you've discovered, content contains the rendered internal bit (the results of index) and is inserted into the base template, layout.
You're asking how to get some script into login.html, but not in index.html & that's easy: just add the javascript code into the login.html template.
=== login.html ===
$def with (values)
$var title: Enter credentials
<form>
...
</form>
<script>
window.addEventListener('load', function () {
// whatever javascript you want to execute on load.
// If using jQuery, you'll have to use $$('form') or jQuery('form') rather
// than $('form'), as dollar signs are special within the template.
});
</script>
Something more clever? Use content more fully. Anything you define using $var in your template gets put into $content in the base layout.
If you want to include login.js only when your login.html page is rendered, you could simple create a new content attribute. In login.html:
$var extra_js: js/login.js
Then, in your layout file conditionally load the value at the bottom (where we like to load scripts).
=== templates/layout.html ===
...
<body>
$:content
$if content.get('extra_js', None):
<script type="text/javascript" src="$content.extra_js"></script>
</body>
</html>
You can make your layout.html more and more powerful, parameterizing meta data, scripts, css files, etc. Just like you did with $content.title, and let your individual template files drive different parts of the overall layout.
I have set up a website using webpy.
I have my main page called layout.html. I load foo1.html into layout
$def with (content)
<html>
<head>
<title>Foo</title>
</head>
<body>
$:content
</body>
</html>
And the content inside is foo1.html
<div> Hello </div>
Is it possible to change foo1.html to also load another webpage:
$def with (secondarycontent)
<div> $:secondarycontent </div>
Just define render as template global
template_globals = {}
render_partial = template.render(template_dir, globals=template_globals)
render = template.render(template_dir, globals=template_globals,
base='layout')
template_globals.update(render=render_partial)
So now you can call it from templates
$:render.nested.template()