Miguel Herreros Cejas
Miguel Herreros Cejas

Reputation: 674

Print Barcode in PDF with 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.

Upvotes: 0

Views: 2177

Answers (2)

ARKhan
ARKhan

Reputation: 2015

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}}

Upvotes: 0

tim-mccurrach
tim-mccurrach

Reputation: 6845

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}}

Upvotes: 2

Related Questions