Flask dynamic graphviz and SVG example - flask

I'm trying to create a Graphviz image, however instead of saving the image and loading into the webpage, pass it as SVG.
Its sort of working, here is the example:
from flask import Flask
from graphviz import Graph
app = Flask(__name__)
#app.route('/svgtest')
def svgtest():
chart_data = Graph()
chart_data.node('H', 'Hello')
chart_data.node('W', 'World')
chart_data.edge('H', 'W')
chart_output = chart_data.pipe(format='svg')
return render_template('svgtest.html', chart_output=chart_output)
In My HTML Page I have
<embed type="image/svg+xml" src={{ chart_output|safe }} />
However the output is not right
Any help would really be great, I'm starting to talk to myself, and even that's not helpful.
Thanks.

You can use svg just like this:
{{ chart_output|safe }}
and also, you can use png format:
#app.route('/')
def svgtest():
chart_data = Graph()
chart_data.node('H', 'Hello')
chart_data.node('W', 'World')
chart_data.edge('H', 'W')
chart_output = chart_data.pipe(format='png')
chart_output = base64.b64encode(chart_output).decode('utf-8')
return render_template('svgtest.html', chart_output=chart_output)
and the html like this:
<img src="data:image/png;base64,{{chart_output|safe}}" />
Happy coding!

This should work. I have modified a bit.
from flask import Flask
from graphviz import Graph
app = Flask(__name__)
#app.route('/svgtest')
def svgtest():
chart_data = Graph()
chart_data.node('H', 'Hello')
chart_data.node('W', 'World')
chart_data.edge('H', 'W')
chart_output = chart_data.pipe(format='svg').encode("utf-8")
return render_template('svgtest.html', chart_output=chart_output)
and the modified html will be:
{{chart_output|safe}}

The src attribute of the embed tag should contain an url to the content to embed, not the content itself:
The HTML <embed> element embeds external content at the specified
point in the document. This content is provided by an external
application ...
src The URL of the resource being embedded.
If you want to use embed, you should serve the svg (without html template) under its own url and use that url in the src attribute of the embed tag.
Or you may insert the svg directly in your html template, without the embed tag.

Related

How to include images in xhtml2pdf generated pdf files?

I am running a streamlit app which generates reports containing images and dataframes. I have used jinja2 to generate the html file from a template. Then, I would now like to convert to a pdf file using xhtml2pdf to download.
How to do that?
from jinja2 import Environment, FileSystemLoader
def convert_html_to_pdf(source_html, output_filename="temp/report.pdf"):
result_file = io.BytesIO()
pdf = pisa.CreatePDF(
source_html,
dest=result_file)
return pdf.getvalue()
def load_template():
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template('catAnalysisTemplate.html')
return template
def render_report(data, filename="report"):
template = load_template()
html = template.render(data)
# with open(f'temp/{filename}.html', 'w') as f:
# f.write(html)
pdf = convert_html_to_pdf(html)
return [html, pdf]
This works fine except the images are not included in the pdf file. My static images are stored in
img/
logo.png
and the charts I may generate it in memory as like
def plot_co_attainment(qp):
img = io.BytesIO()
data = qp.co_attainment()[["Level", "Perc_Attainment"]]
plt.figure(dpi=150)
plt.bar(data["Level"], data["Perc_Attainment"], width=0.5, color=colors)
for i, val in enumerate(data["Perc_Attainment"].values):
plt.text(i, val, str(val) + "%",
horizontalalignment='center',
verticalalignment='bottom',
fontdict={'fontweight': 500, 'size': 20})
plt.xlabel("Course Outcomes")
plt.ylabel("Percentage of Attainment")
plt.ylim((0, 110))
plt.savefig(buf, format='jpg')
return buf
How do I connect the dots and get the images in my pdf file?
I am having the same issue. The way I solved it was to use a link_handler and return the data as a data: uri containing the png image data.
This example will take the src attribute and use it to generate a square image in that color, which will be embedded in the PDF. Sadly this doesn't let you modify the image tag itself so you can't change the sizes/classes or anything else.
Using something like this opens the way to embedding just about anything without having to add them to your template directly.
from base64 import b64encode
from io import BytesIO
from xhtml2pdf import pisa
from PIL import Image
html_src = """
<body>
<div>
<img src="red"/>
<img src="green"/>
<img src="blue"/>
</div>
</body>
"""
def link_callback(src_attr, *args):
"""
Returns the image data for use by the pdf renderer
"""
img_out = BytesIO()
img = Image.new("RGB", (100, 100), src_attr)
img.save(img_out, "png")
return f"data:image/png;base64,{b64encode(img_out.getvalue())}"
def main():
with open("one.pdf", "wb") as f:
pizza = pisa.CreatePDF(
html_src,
dest=f,
link_callback=link_callback,
)
if __name__ == "__main__":
main()

Flask app -how to display an image generated from within the app?

so I have some python code that generates an image inside my Flask app (can do it in JPG,PNG,etc..) and I want to display that image on my Flask app. the problem is that it needs to live in the 'static' folder,
and it is 'read only' - so I can't create the image inside it.
can I make the 'url_for' look inside other directories? or somehow write my image file into the 'static' folder in runtime?
I had a similar issue on one of my pet projects. I'm not sure if there's a better way to do it but I managed to get around it by encoding the image in base64 and passing the image tag to the html file directly via render_template(). Essentially:
import io
def serve_pil_image(pil_img):
img_io = io.BytesIO()
pil_img.save(img_io, 'jpeg', quality=100)
img_io.seek(0)
img = base64.b64encode(img_io.getvalue()).decode('ascii')
img_tag = f'<img src="data:image/jpg;base64,{img}" class="img-fluid"/>'
return img_tag
And in your flask app:
from PIL import Image
#app.route('/')
def index():
my_image = Image.open(image_file)
img_tag=serve_pil_image(my_image)
return render_template('index.html', image=img_tag)
And in your html:
{{image|safe}}

How can I serve an image in django?

I have a binary field to save images
photograph = models.BinaryField(default=None)
In my form, I save the image
photograph = cd['photograph'].file.getvalue(),
)
In My view
f = open('my.jpeg', 'bw')
myfile = File(f)
myfile.write(student.photograph)
filepath = os.path.abspath(os.path.realpath('my.jpeg'))
context['urls'] = filepath
return render(request, 'dashboard.html', context)
The image is saved to the database, it is being retrieved successfully.
Screenshot of the image being saved successfully
My template
The HTML in the template renders well.
If I copy the HTML into a local file, the image appears well and good.
However, the image doesn't load properly when I use django.
Right click > copy image address gives me this: about:blank#blocked
Is it a security or a permissions issue?
After much research, this is what I found.
in HTML
<img src = "data/image:jpeg;base64, {{base64_string}}/>
in view
from django.http import urlsafe_b64encode
return render(request, 'template.html', {'base64_string' : urlsafe_b64encode(myobject.photograph)
This works for development. For production, I guess static files could be served the django way.

Matplotlib image not displaying in Django template

I have a seaborn barplot image that I would like to display inside a django template.
I have looked at a few solutions and I managed to display the image with HttpResponse. However, the image it displays is just an image. What I want is an image of the plot on a webpage and you can still click the navbar and home button etc.
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.backends.backend_agg import FigureCanvasAgg
from io import BytesIO
from django.template.loader import render_to_string
In views.py
def top_causal(request):
# code for data manipulation
f = plt.figure(figsize=(10, 5))
ax = sns.barplot(data)
# labels, title etc...
FigureCanvasAgg(f)
buf = BytesIO()
plt.savefig(buf, format='png)
plt.close(f)
# this works
response = HttpResponse(buf.getvalue(), content_type='image/png')
return response
# What I want which is to display the image inside a template(does not work)
buffer = buf.getvalue()
content_type="image/png"
graphic = (buffer, content_type)
rendered = render_to_string('top_causal.html', {'graphic': graphic})
return HttpResponse(rendered)
in top_causal.html
<img src="data:image/png;base64,{{ graphic|safe }}" alt="Not working.">
HttpResponse displays the image as a page.
The response using render_to_string returns this instead of an image inside the webpage. I can still see the navbar.
\x06\x1d\x00\x80kT\xb7n]eee\x95\x18\xff\xe1\x87\x1f$I\xf5\xea\xd5\xb3\x19\xcf\xce\xce.\x11\x9b\x9d\x9d-WWW999U\xb8~\xa7N\x9d$\xfd\xf6\x02\xb2\xb0\xb00IR\x9b6mt\xe4\xc8\x91\x12\xb1G\x8e\x1c\x91\xc5b\xb9\xea\xe7\xe1\xda\xb5k\xa7\xf7\xdf\x7f_\xc5\xc5\xc5:|\xf8\xb0V\xadZ\xa5\xe7\x9f\x7f^\xb5j\xd5\xd2\xd4\xa9S\xafx\xee\xdbo\xbf\xad\x81\x03\x07j\xc9\x92%6\xe3\xb9\xb9\xb9\xe5\xba\x9e\xbau\xeb\xcab\xb1h\xc2\x84\t\x9a0aB\xa91M\x9b6-W\xae\xab\xa9W\xaf\x9e\xaaU\xab\xa6\xbd{\xf7\xaaj\xd5\xaa%\xe6]\\\\\xecR\xe7\xb2;\xef\xbcSqqq\x92~{Q\xde\xbb\xef\xbe\xaby\xf3\xe6\xa9\xb8\xb8X\x8b\x16-\xb2k-\x00\x80\xe3b\x07\x1d\x00\x80k\xd4\xb7o_\xa5\xa4\xa4\x94x\x13w||\xbc\xaaT\xa9\xa2\x90\x90\x10\x9b\xf1\xf7\xdf\x7f\xdfz\x8b\xb5$\x9d;wN[\xb7n-\x11W^\x97o\x8do\xd6\xac\x99ul\xc8\x90!JIIQJJ\x8au\xec\xd7 ...
You need to convert the image to byte array, encode it to base64 and embed it to your template. Here is how you do it:
<!DOCTYPE html>
<!--my_template.html-->
<html>
<div class='section'>
<div class='flex column plot mt15 mlr15 justify-left align-left'>
<img src='{{ imuri }}'/>
</div>
</div>
</html>
from django.template.loader import get_template
def top_causal(request):
# .......................
# Get your plot ready ...
# .......................
figure = plt.gcf()
buf = io.BytesIO()
figure.savefig(buf, format='png', transparent=True, quality=100, dpi=200)
buf.seek(0)
imsrc = base64.b64encode(buf.read())
imuri = 'data:image/png;base64,{}'.format(urllib.parse.quote(imsrc))
context = { 'plot': imuri}
# Now embed that to your template
template = get_template('my_template.html')
html = template.render(context=context)
# Your template is ready to go...
I discarded the idea of using matplotlib and went to use Highcharts https://www.highcharts.com/ instead to plot my graph and display it on the webpage.
The data displayed was done by using Django's ORM and Query API.

How to include Google Chart inside Pisa Document xhtml2pdf with Django

I'm trying to embed the Google Chart url below into a Pisa xhtml2pdf PDF, but can't seem to make it work.
http://chart.apis.google.com/chart?cht=bvs&chd=t:425.9,550.6&chs=400x125&chds=0,600&chl=Aug%2009%7CSep%2009&chco=8BC542&chbh=32,24
I was hoping this post would solve my problem but no luck.
I'm using the following code (taken from the aforementioned post)
html = render_to_string('reporting/pdf.html', keys,
context_instance=RequestContext(request))
result = StringIO.StringIO()
pdf = pisa.pisaDocument(
StringIO.StringIO(html.encode('ascii', 'xmlcharrefreplace')),
result, link_callback=link_callback)
return HttpResponse(result.getvalue(), mimetype='application/pdf')
and
def link_callback(uri, rel):
if uri.find('chart.apis.google.com') != -1:
return uri
if uri.find('mydomain.com') != -1:
return uri
return os.path.join(settings.MEDIA_ROOT, uri.replace(settings.MEDIA_URL, ""))
It works when I embed images from mydomain.com but I get nothing when trying to link to chart.apis.google.com
This works:
<img src="http://mydomain.com/foo.jpg>
This is broken:
<img src="http://chart.apis.google.com/chart?cht=bvs&chd=t:425.9,550.6&chs=400x125&chds=0,600&chl=Aug%2009|">
Any help would be greatly appreciated. Cheers.