Print Barcode in PDF with Django - django

I am using render_to_string in django for parse an HTML and export to PDF.
html = render_to_string("etiquetaTNT.html", {
'context': context,
'barcode': b,
'barcodeimg': barcodeimg,
})
font_config = FontConfiguration()
HTML(string=html).write_pdf(response, font_config=font_config)
return response
I am trying to insert a barcode in PDF. I generate this barcode in a PNG.
br = barcode.get('code128', b, writer=ImageWriter())
filename = br.save(b)
barcodeimg = filename
But the PDF in template, not show the image.
<img class="logo" src="{{barcodeimg}}" alt="Barcode" />
I do not know the way to save the filename in the template that I want, and I do not know to show in the PDF, because any image is showed. For example, the logo, it is showed in HTML template but not in the PDF.
<img class="logo" src="{{logo}}" alt="TNT Logo" />
The libraries that I am using:
import barcode
from barcode.writer import ImageWriter
from django.http import HttpResponse
from django.template.loader import render_to_string
from weasyprint import HTML
from weasyprint.fonts import FontConfiguration
I do not want to use Reportlab, because I need to render a HTML, not a Canvas.

Understanding the problem:
Think about what happens when you load a webpage. There is the initial request where the document is loaded, and then subsequent requests are made to fetch the images / other assets.
When you want to print some HTML to PDF using weasyprint, weasyprint has to fetch all of the other images. Checking out the python-barcode docs, br.save(b) is just going to return literally just the filename, (which will be saved in your current working directory). So your html will look something like this:
<img class="logo" src="some_filename.svg" alt="Barcode" />
Quite how it fetches this will depend on how you have weasyprint set up. You can check out django-weasyprint which has a custom URL fetcher. But as things stand, weasyprint can't fetch this file.
A solution
There are a few ways you can fix this. But it depends alot on how you are deploying this. For example, heroku (as I understand it) doesn't have a local file system you can write to, so you would need to write the file to an external service like s3, and then insert the url for that into your template, which weasyprint will then be able to fetch. However, I think there is probably a simpler solution we can use in this case.
A better (maybe) Solution
Taking a look at the python-barcode docs it looks like you can write using SVG.
This is good because we can insert SVG straight into our HTML template (and avoid having to fetch any other assets). I would suggest something like the following
from io import BytesIO
from barcode.writer import SVGWriter
# Write the barcode to a binary stream
rv = BytesIO()
code = barcode.get('code128', b, writer=SVGWriter())
code.write(rv)
rv.seek(0)
# get rid of the first bit of boilerplate
rv.readline()
rv.readline()
rv.readline()
rv.readline()
# read the svg tag into a string
svg = rv.read()
Now you'll just need to insert that string into your template. Just add it to your context, and render it as follows:
{{svg}}

Enhancing the solution provided by #tim-mccurrach, I have created a templatetag for it.
/app/templatetags/barcode_tags.py
from django import template
from io import BytesIO
import barcode
register = template.Library()
#register.simple_tag
def barcode_generate(uid):
rv = BytesIO()
# code = barcode.get('code128', b, writer=SVGWriter())
code = barcode.get('code128', uid,
writer=barcode.writer.SVGWriter())
code.write(rv)
rv.seek(0)
# get rid of the first bit of boilerplate
rv.readline()
rv.readline()
rv.readline()
rv.readline()
# read the svg tag into a string
svg = rv.read()
return svg.decode("utf-8")
And then In the template.html:
{% load barcode_tags %}
{% barcode_generate object.uid as barcode_svg %}
{{barcode_svg | safe}}

Related

Show Image stored in s3 on Web page using flask

Im trying to get images from an s3 bucket, and show them on a web page using flask (and boto3 to access the bucket).
I currently have a list of all the pictures from the bucket, but cant get the html to show them(gives me 404 error).
How do I do this without downloading the files?
this is what I have so far:
def list_files(bucket):
contents = []
for image in bucket.objects.all():
contents.append(image.key)
return contents
def files():
list_of_files = list_files(bucket)
return render_template('index.html', my_bucket=bucket, list_of_files=list_of_files)
and this is the html snippet:
<table class="table table-striped">
<br>
<br>
<tr>
<th>My Photos</th>
{% for f in list_of_files %}
<td> <img src="{{ f }}"></td>
{% endfor %}
Thanks a lot!
since loading an image to a html page requires a real image which exists in the directory. images from AWS S3 can be loaded onto a html page if you download them first in the directory, then use its url as a source in html <image> tag.
i found a solution to this but you need to modify it as your needs.
define a function that loads the image from S3 as:
import matplotlib.image as mpimg
import numpy as np
import boto3
import tempfile
s3 = boto3.resource('s3', region_name='us-east-2')
bucket = s3.Bucket('bucketName')
object = bucket.Object('dir/subdir/2015/12/7/img01.jpg')
tmp = tempfile.NamedTemporaryFile()
def imageSource(bucket, object, tmp):
with open(tmp.name, 'wb') as f:
object.download_fileobj(f)
src = tmp.name #dir/subdir/2015/12/7/img01.jpg
retrun src
Just ran into this problem as well, seems like this hasn't been updated for a while so will try to add it.
Your current approach below is right. The only issue is that in order to render an image that is not going to be downloaded to your server, you have to have a direct url to your S3 file. Currently, you only have the image name, not the full url.
def list_files(bucket):
contents = []
for image in bucket.objects.all():
contents.append(image.key)
return contents
def files():
list_of_files = list_files(bucket)
return render_template('index.html', my_bucket=bucket, list_of_files=list_of_files)
Currently, your items in the list of files will look like this:
['file_name1', 'file_name2', 'file_name3']
In order for them to render in your browser directly you need them to look like this:
['file_url1', 'file_url2', 'file_url3']
s3 file urls look something like this: https://S3BUCKETNAME.s3.amazonaws.com/file_name1.jpg
Therefore, instead of the line below
contents.append(image.key)
you need to replace the image.key with something that makes the URL
contents.append(f'https://{S3BUCKETNAME}.s3.amazonaws.com/{image.key})
That should do it, the html you have should work correctly as is. The only other big risk is the files you uploaded are not public, for that you'll need to look at the settings of your bucket on AWS.
Additional Resources and Sources:
Adding a public policy to your AWS S3 Bucket: https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-bucket-policies.html
Uploading and downloading files with Flask & S3: https://stackabuse.com/file-management-with-aws-s3-python-and-flask/

Detect URLs and #tags in Django CharField and add style to it

For now on I have in my template a paragraph like this <p class="...">{{ post.content }}</p> and if this Post's content contains a link or #hashtag it is rendered as a normal text with the rest of the post. How can I customize it? For example change text-color and add tag around it?
As I said in comment, you can use custom tag filter to wrap your content, and use Regular Expression to generate links and hashtags
Create your tags file, and name it as you want:
tag_filter_name.py
If you're not familiar with custom tag filter creation, you can learn more about it in the Official Documentation
from django import template
import re
register = template.Library()
def generate_link(link):
return '<a class="link" href="{}">{}</a>'.format(link, link)
def generate_hashtag_link(tag):
# Free to configuree the URL the way adapted your project
url = "/tags/{}/".format(tag)
return '<a class="hashtag" href="{}">#{}</a>'.format(url, tag)
And then, you create the function what will be used as tag filter
#register.filter
def render_content(obj):
text = re.sub(r"#(\w+)", lambda m: generate_hashtag_link(m.group(1)),obj)
return re.sub(r"(?P<url>https?://[^\s]+)", lambda m: generate_link(m.group(1)),text)
If you want Django to mark it as safe content, you can do the following:
from django.utils.safestring import mark_safe # import function
''' function codes here '''
return mark_safe(re.sub(r"(?Phttps?://[^\s]+)",
lambda m: generate_link(m.group(1)),text))
And finally, to use it in your template, don't forget to load it
{% load tag_filter_name %}
{{ post.content|render_content }}
Best way: custom tag filters here is the docs URL
https://docs.djangoproject.com/en/2.2/ref/templates/builtins/
Good way: If you know JS create a function that handles the formatting on the front end
in HTML:
onload="myfunction("{{post.content}}")"
in JS sort for the string containing the # wrap it in a span or other element and style away. then replace the inner HTML with your new formatted piece. This will save rendering time on the server and also frees you up from having to loop thru the list of posts in the view
Ok way: not preferred but if you hate js and only want to work in python (understandable). You need to loop through the list of posts separate out the items of the post format them the way you like with inline style. then add them to a new object that you will append to the end of a new list of posts that you will then pass thru to context. This is a real pain please don't do this if you can help it at all.
the tag filters are awsome take advantage but if they won't work for your use case I would highly advise using vanilla JS

Possible to use multiple strainers with one BeautifulSoup document?

I'm using Django and Python 3.7 . I want to speed up my HTML parsing. Currently, I'm looking for three types of elements in my document, like so
req = urllib2.Request(fullurl, headers=settings.HDR)
html = urllib2.urlopen(req).read()
comments_soup = BeautifulSoup(html, features="html.parser")
score_elts = comments_soup.findAll("div", {"class": "score"})
comments_elts = comments_soup.findAll("a", attrs={'class': 'comments'})
bad_elts = comments_soup.findAll("span", text=re.compile("low score"))
I have read that SoupStrainer is one way to improve performacne -- https://www.crummy.com/software/BeautifulSoup/bs4/doc/#parsing-only-part-of-a-document . However, all the examples only talk about parsing an HTML doc with a single strainer. In my case, I have three. How can I pass three strainers into my parsing, or would that actually create worse performance that just doing it the way I'm doing it now?
I don't think you can pass multiple Strainers into the BeautifulSoup constructor. What you can instead do is to wrap all your conditions into one Strainer and pass it to the BeautifulSoup Constructor.
For simple cases such as just the tag names, you can pass a list into the SoupStrainer
html="""
<a>yes</a>
<p>yes</p>
<span>no</span>
"""
from bs4 import BeautifulSoup
from bs4 import SoupStrainer
custom_strainer = SoupStrainer(["a","p"])
soup=BeautifulSoup(html, "lxml", parse_only=custom_strainer)
print(soup)
Output
<a>yes</a><p>yes</p>
For specifying some more logic, you can also pass in a custom function(you may have to do this).
html="""
<html class="test">
<a class="wanted">yes</a>
<a class="not-wanted">no</a>
<p>yes</p>
<span>no</span>
</html>
"""
from bs4 import BeautifulSoup
from bs4 import SoupStrainer
def my_function(elem,attrs):
if elem=='a' and attrs['class']=="wanted":
return True
elif elem=='p':
return True
custom_strainer= SoupStrainer(my_function)
soup=BeautifulSoup(html, "lxml", parse_only=custom_strainer)
print(soup)
Output
<a class="wanted">yes</a><p>yes</p>
As specified in the documentation
Parsing only part of a document won’t save you much time parsing the
document, but it can save a lot of memory, and it’ll make searching
the document much faster.
I think you should check out the Improving performance section of the documentation.

how to embed standalone bokeh graphs into django templates

I want to display graphs offered by the bokeh library in my web application via django framework but I don't want to use the bokeh-server executable because it's not the good way. so is that possible? if yes how to do that?
Using the Embedding Bokeh Plots documentation example as suggested by Fabio Pliger, one can do this in Django:
in the views.py file, we put:
from django.shortcuts import render
from bokeh.plotting import figure
from bokeh.resources import CDN
from bokeh.embed import components
def simple_chart(request):
plot = figure()
plot.circle([1,2], [3,4])
script, div = components(plot, CDN)
return render(request, "simple_chart.html", {"the_script": script, "the_div": div})
in the urls.py file we can put :
from myapp.views import simple_chart
...
...
...
url(r'^simple_chart/$', simple_chart, name="simple_chart"),
...
...
in the template file simple_chart.html we'll have :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Experiment with Bokeh</title>
<script src="http://cdn.bokeh.org/bokeh/release/bokeh-0.8.1.min.js"></script>
<link rel="stylesheet" href="http://cdn.bokeh.org/bokeh/release/bokeh-0.8.1.min.css">
</head>
<body>
{{ the_div|safe }}
{{ the_script|safe }}
</body>
</html>
And it works.
You don't need to use bokeh-server to embed bokeh plots. It just means you'll not be using (and probably don't need) the extra features it provides.
In fact you can embed bokeh plots in many ways like generating standalone html, by generating bokeh standalone components that you can then embed in you django app when rendering templates or with the method we call "autoloading" which makes bokeh return a tag that will replace itself with a Bokeh plot. You'll find better details looking at the documentation.
Another good source of inspiration is the embed examples you can find in the repository.
It is also possible to have it work with AJAX requests. Let's say we have a page loaded and would like to show a plot on button click without reloading the whole page. From Django view we return Bokeh script and div in JSON:
from django.http import JsonResponse
from bokeh.plotting import figure
from bokeh.resources import CDN
from bokeh.embed import components
def simple_chart(request):
plot = figure()
plot.circle([1,2], [3,4])
script, div = components(plot, CDN)
return JsonResponse({"script": script, "div": div})
When we get AJAX response in JS (in this example Jquery is used) the div is first appended to the existing page and then the script is appended:
$("button").click(function(){
$.ajax({
url: "/simple_chart",
success: function(result){
var bokeh_data = JSON.parse(result);
$('#bokeh_graph').html(bokeh_data.div);
$("head").append(bokeh_data.script);
}});
});
It must put {{the_script|safe}} inside the head tag
Here's a flask app that uses jquery to interract with a bokeh plot. Check out the templates/ for javascript you can reuse. Also search for bokeh-demos on github.

django xml parsing -parse img src attribute inside the xml tag

I need the URL inside the description tag of RSS file. I am trying to parse the images in the following link.
"ibnlive.in.com/ibnrss/rss/shows/worldview.xml"
I need the image link in that. I am using urllib and beautiful soup to parse details.
I am trying to parse the title,description,link and images inside the item tag. I can parse the title, description and link. But I can't parse image inside the description tag.
XML:
<item>
<title>World View: US shutdown ends, is the relief only temporary?</title>
<link>http://ibnlive.in.com/videos/429157/world-view-us-shutdown-ends-is-the-relief-only-temporary.html</link>
<description><img src='http://static.ibnlive.in.com/ibnlive/pix/sitepix/10_2013/worldview_1810a_90x62.jpg' width='90' height='62'>The US Senate overwhelmingly approved a deal on Wednesday to end a political crisis that partially shut down the federal government and brought the world's biggest economy to the edge of a debt default that could have threatened financial calamity.</description>
<pubDate>Fri, 18 Oct 2013 09:34:32 +0530</pubDate>
<guid>http://ibnlive.in.com/videos/429157/world-view-us-shutdown-ends-is-the-relief-only-temporary.html</guid>
<copyright>IBNLive</copyright>
<language>en-us</language>
</item>
views.py
from django.conf import settings
from django.shortcuts import render
from django.http import HttpResponse
from django.utils.html import strip_tags
from os.path import basename, splitext
import os
import urllib
from bs4 import BeautifulSoup
def international(request):
arr=[]
#asianage,oneinindia-papers
a=["http://news.oneindia.in/rss/news-international-fb.xml","http://www.asianage.com/rss/37"]
for i in a:
source_txt=urllib.urlopen(i)
b=BeautifulSoup(source_txt.read())
for q in b.findAll('item'):
d={}
d['desc']=strip_tags(q.description.string).strip('&nbsp')
if q.guid:
d['link']=q.guid.string
else:
d['link']=strip_tags(q.comments)
d['title']=q.title.string
for r in q.findAll('description'):
d['image']=r['src']
arr.append(d)
return render(request,'feedpars.html',{'arr':arr})
HTML
<html>
<head></head>
<body>
{% for i in arr %}
<p>{{i.title}}</p>
<p>{{i.desc}}</p>
<p>{{i.guid}}</p>
<img src="{{i.image}}" style="width:100px;height:100px;"><hr>
{% endfor %}
</body>
</html>
Nothing gets displayed in my output.
1/ as I already told you here How to get the url of image in descripttion tag of xml file while parsing? : this is a rss feed so use the appropriate tool: https://pypi.python.org/pypi/feedparser
2/ there's no proper "img" tag in the description, the html markup has been entity-encoded. To get the url, you have to either decode the description's content (to get the tag back) and pass the resulting html fragment to your HTML parser or - since it will probably not be as complex as a full html doc - just use a plain regexp on the encoded content.