Upload multiple images with Flask-Admin - python

I am trying to get the fileadmin to get multiple files uploaded but can't figure out how to do that.
Currently in the below reference I can only upload one file at a time.
https://github.com/flask-admin/flask-admin/blob/master/examples/file/app.py
(New Link to to file: https://github.com/mrjoes/flask-admin/blob/master/examples/file/app.py )
I tried updating the html template to have multiple="" but that didn't helped to upload multiple files.
Further looking into this I think this html file needs to have multiple=""
Python27\Lib\site-packages\flask_admin\templates\bootstrap3\adminC:\Python27\Lib\site-packages\flask_admin\templates\bootstrap3\admin\lib.html
Although I am not sure where/how to add this tag without actually overwriting the source file.

You should custom ImageUploadInput and ImageUploadField as follows.
import ast
from PIL import Image
from wtforms.widgets import html_params, HTMLString
from wtforms.utils import unset_value
from flask_admin.helpers import get_url
from flask_admin.form.upload import ImageUploadField
from flask_admin._compat import string_types, urljoin
class MultipleImageUploadInput(object):
empty_template = "<input %(file)s multiple>"
# display multiple images in edit view of flask-admin
data_template = ("<div class='image-thumbnail'>"
" %(images)s"
"</div>"
"<input %(file)s multiple>")
def __call__(self, field, **kwargs):
kwargs.setdefault("id", field.id)
kwargs.setdefault("name", field.name)
args = {
"file": html_params(type="file", **kwargs),
}
if field.data and isinstance(field.data, string_types):
attributes = self.get_attributes(field)
args["images"] = " ".join(["<img src='{}' /><input type='checkbox' name='{}-delete'>Delete</input>"
.format(src, filename) for src, filename in attributes])
template = self.data_template
else:
template = self.empty_template
return HTMLString(template % args)
def get_attributes(self, field):
for item in ast.literal_eval(field.data):
filename = item
if field.thumbnail_size:
filename = field.thumbnail_size(filename)
if field.url_relative_path:
filename = urljoin(field.url_relative_path, filename)
yield get_url(field.endpoint, filename=filename), item
class MultipleImageUploadField(ImageUploadField):
widget = MultipleImageUploadInput()
def process(self, formdata, data=unset_value):
self.formdata = formdata # get the formdata to delete images
return super(MultipleImageUploadField, self).process(formdata, data)
def process_formdata(self, valuelist):
self.data = list()
for value in valuelist:
if self._is_uploaded_file(value):
self.data.append(value)
def populate_obj(self, obj, name):
field = getattr(obj, name, None)
if field:
filenames = ast.literal_eval(field)
for filename in filenames[:]:
if filename + "-delete" in self.formdata:
self._delete_file(filename)
filenames.remove(filename)
else:
filenames = list()
for data in self.data:
if self._is_uploaded_file(data):
self.image = Image.open(data)
filename = self.generate_name(obj, data)
filename = self._save_file(data, filename)
data.filename = filename
filenames.append(filename)
setattr(obj, name, str(filenames))
After that, You can use them as image upload example in flask-admin
class ModelHasMultipleImages(db.Model):
id = db.Column(db.Integer(), primary_key=1)
# len of str representation of filename lists may exceed the len of field
image = db.Column(db.String(128))
class ModelViewHasMultipleImages(ModelView):
def _list_thumbnail(view, context, model, name):
if not model.image:
return ''
def gen_img(filename):
return '<img src="{}">'.format(url_for('static',
filename="images/custom/" + form.thumbgen_filename(image)))
return Markup("<br />".join([gen_img(image) for image in ast.literal_eval(model.image)]))
column_formatters = {'image': _list_thumbnail}
form_extra_fields = {'image': MultipleImageUploadField("Images",
base_path="app/static/images/custom",
url_relative_path="images/custom/",
thumbnail_size=(400, 300, 1))}
The image filed of ModelHasMultipleImages store the str represent of filename list.

Dropzone is the best way to do this
Download with
pip install flask flask-uploads flask-dropzone
The Python app.py should look somthhing like this
# app.py
from flask import Flask, render_template
from flask_dropzone import Dropzone
from flask_uploads import UploadSet, configure_uploads, IMAGES, patch_request_class
import os
app = Flask(__name__)
dropzone = Dropzone(app)
# Dropzone settings
app.config['DROPZONE_UPLOAD_MULTIPLE'] = True
app.config['DROPZONE_ALLOWED_FILE_CUSTOM'] = True
app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image/*'
app.config['DROPZONE_REDIRECT_VIEW'] = 'results'
# Uploads settings
app.config['UPLOADED_PHOTOS_DEST'] = os.getcwd() + '/uploads'
photos = UploadSet('photos', IMAGES)
configure_uploads(app, photos)
patch_request_class(app) # set maximum file size, default is 16MB
#app.route('/')
def index():
return render_template('index.html')
#app.route('/results')
def results():
return render_template('results.html')
And result.html looks as follows
# templates/results.html
<h1>Hello Results Page!</h1>
Back<p>
<ul>
{% for file_url in file_urls %}
<li><img style="height: 150px" src="{{ file_url }}"></li>
{% endfor %}
</ul>
index.html looks something like this
# templates/index.html
<!DOCTYPE html>
<html>
<head>
<title>Flask App</title>
{{ dropzone.load() }}
{{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }}
</head>
<body>
<h1>Hello from Flask!</h1>
{{ dropzone.create(action_view='index') }}
</body>
</html>

Related

Generate dynamic div containers on submit in Flask app

I am new in Flask. My goal is to generate dynamic div containers every time I upload a dxf file and next submit happens. For example: one file uploaded- one div shown; two files uploaded- two divs shown and so on.
I can convert uploaded dxf files to .png images and I would like to show these images in div elements displayed after every upload.
I use input tag to upload files (type='file') and Java Script to generate dynamic elements (divs and their child tags).
The problem is that every time I upload file, the template is loading again and no new content is shown except the image of the last uploaded dxf. Please, give me a piece of advice to solve it.
HTML
...
<form enctype="multipart/form-data" id="uploadForm" action="/upload_files" name="uploadForm" method="post">
DXF file: <input type="file" id="dxfUpload" onchange="form.submit(); createConfigure();" name="dxfUpload" />
<div id="calcHolder" name="calcHolder">
<script type="text/javascript">
function createConfigure() {
var div = document.createElement("div");
div.id = "dxf-"+Math.random() * 100000000000000000 + "-"
+ window.performance.now() * 100000000000000000;
id_div=div.id;
div.className = 'border pad';
div.style.width = "640px";
div.style.height = "200px";
document.getElementById("calcHolder").appendChild(div);
var img = document.createElement("img");
img.setAttribute("src", "{{url_for('static', filename=dxfName+'.png')}}");
img.setAttribute("alt", "no image");
img.setAttribute("height", "120px");
img.setAttribute("width", "120px");
document.getElementById(id_div).appendChild(img);
var array = ["Carbon Steel","Stainless Steel","Aluminium"];
var selectMaterial = document.createElement("select");
document.getElementById(id_div).appendChild(selectMaterial);
for (var i = 0; i < array.length; i++) {
var option = document.createElement("option");
option.value = array[i];
option.text = array[i];
selectMaterial.appendChild(option);
}
var selectThickness = document.createElement("select");
document.getElementById(id_div).appendChild(selectThickness);
for (i = 1; i <= 16; i++) {
var opt = document.createElement('option');
//opt.value = i;
opt.innerHTML = i + ' mm';
selectThickness.appendChild(opt);
}
var quantity = document.createElement("input")
quantity.type="number";
quantity.value="1";
quantity.name="quantity";
quantity.min="1";
quantity.max="50";
quantity.onkeyup= function maxReach(){if(quantity.value > 50) quantity.value=50;};
document.getElementById(id_div).appendChild(quantity);
var btn = document.createElement("button");
btn.innerHTML = "Delete";
btn.type = "button";
document.getElementById(id_div).appendChild(btn);
btn.onclick = function() {div.remove();};
}
</script>
{{ html | safe }}
</div>
</form>
...
Python
#app.route('/upload_files', methods=['POST'])
def upload_files():
try:
if request.method == 'POST':
dxf_file = request.files['dxfUpload']
full_filename = os.path.join(app.config['UPLOAD_FOLDER'],dxf_file.filename)
dxf_file.save(full_filename)
first = DXF2IMG()
first.convert_dxf2img([full_filename],img_format='.png')
html="<img src="+url_for('static', filename=dxf_file.filename+'.png' )+" width='120' height='120' />"
return render_template('upload_files.html',dxfName=dxf_file.filename, html=html)
except:
...
#something happens
The result now
Desired result
Once the form.submit() function is executed, the form will be sent as a regular post request. For this reason, the following function is no longer executed and the entire page is reloaded.
In order to submit the form and change the content of the existing page, it is necessary to use AJAX.
This example shows you how to submit the form to the server and receive a JSON response containing the URLs of the received file and the generated image.
As soon as the submit button is pressed, the form data is packed into a FormData object and sent via AJAX using the fetch function. The browser's default behavior for a submit event is suppressed and the form is reset. The received file is processed by the server and the associated URLs are sent back to the client in JSON format. Now the document can be changed with the received data.
Remember this is just a minimal example to help you achieve your goals and implement your concept.
Flask (app.py)
import os
import ezdxf
from ezdxf.addons.drawing import matplotlib
from flask import Flask
from flask import (
jsonify,
make_response,
render_template,
url_for
)
from werkzeug.utils import secure_filename
app = Flask(__name__)
#app.route('/')
def index():
return render_template('index.html')
def dxf2png(source, target):
doc = ezdxf.readfile(source)
msp = doc.modelspace()
auditor = doc.audit()
if auditor.has_errors:
raise Exception('Conversion failed.')
matplotlib.qsave(doc.modelspace(), target)
#app.route('/upload', methods=['POST'])
def upload():
if 'dxf-file' in request.files:
file = request.files['dxf-file']
if file.filename != '':
filename = secure_filename(file.filename)
filepath = os.path.join(app.static_folder, filename)
destname, _ = os.path.splitext(filename)
destname = f'{destname}.png'
destpath = os.path.join(app.static_folder, destname)
file.save(filepath)
try:
dxf2png(filepath, destpath)
except:
os.remove(filepath)
return make_response('', 400)
return make_response(
jsonify(
target=url_for('static', filename=filename),
preview=url_for('static', filename=destname)
),
200
)
return make_response('', 400)
HTML (templates/index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Index</title>
<style media="screen">
.preview {
width: 120px;
height: auto;
}
</style>
</head>
<body>
<form name="dxf-upload" method="post" enctype="multipart/form-data">
<input type="file" name="dxf-file" />
<input type="submit" />
</form>
<div id="dxf-files"></div>
<script type="text/javascript">
((uri) => {
function createPreview(target, preview) {
const divElem = document.createElement('div');
divElem.innerHTML = `<img src="${preview}" class="preview" />`;
const outElem = document.getElementById('dxf-files');
outElem.append(divElem);
}
const form = document.querySelector('form[name="dxf-upload"]');
form.addEventListener('submit', evt => {
evt.preventDefault();
const formData = new FormData(evt.target);
fetch(uri, {
method: 'POST',
body: formData
}).then(resp => resp.json())
.then(data => {
const { target, preview } = data;
createPreview(target, preview);
});
evt.target.reset();
});
})({{ url_for('.upload') | tojson }});
</script>
</body>
</html>

how to connect python backend with flask and html&css

'''
app.py
from flask import Flask, render_template, request
from weather_backend import temperature_condition,clothes,feels_temperature,weather_description
app = Flask(__name__)
app.config["SECRET_KEY"] = "Secret-key"
#app.route("/")
def index():
return render_template("index.html")
#app.route("/dress")
def dress():
cityname = request.form.get("city_name")
temp = str(temperature_condition())
message = str(clothes())
feels = feels_temperature
description= weather_description
return render_template("dress.html", message=message, temp=temp, feels_temperature=feels,
weather_description=description )
if __name__ == "__main__":
app.run(debug=True)
'''
'''
weather_backend.py
import requests, json
import weatherMappingMessage
from app import dress
from keys import *
base_url = "http://api.openweathermap.org/data/2.5/weather?"
city_name =
complete_url = base_url + "appid=" + api_key + "&q=" + city_name + "&units=metric"
response = requests.get(complete_url)
'''
HTML file
'''
<body>
<div class="head">
<form action= "{{ url_for('dress') }}" class="form" method="GET">
<h1>Get Weather and Dresses according to the Weather</h1>
<div class = "form-box">
<input type="text" class="search-field location" name= "city_name" placeholder="Location...">
<button class="search-btn" type="button">Search</button>
</div>
</form>
</div>
</body>
'''
I need to get the form info(search) from HTML to the backend(city_name) and then to the flask(cityname)
I can get a message from the backend if try to get it but I can't get HTML form to the backend for processing
The problem I'm facing is that I can't get the form data from my HTML file to my backend for processing
basically, I need the cityname to the backend for getting my weather description
Short answer:
Because your form submission uses a get request, you can use request.args to get parsed contents of query string (see also):
cityname = request.args.get("city_name")
Long answer:
I'm sure you're asking for more than just this piece of code. I took the code you provided and added the missing pieces in-line (please don't do this for production code) and also passed cityname to render_template:
import logging
from datetime import datetime
from flask import render_template, request
from app import app, forms
#app.route("/")
def index():
return render_template("index.html")
#app.route("/dress")
def dress():
cityname = request.args.get("city_name")
# missing in example code
def temperature_condition():
return 'temp cond'
# missing in example code
def clothes():
return 'clothes'
feels_temperature = 'feels temp' # missing in example code
weather_description = 'weather desc' # missing in example code
temp = str(temperature_condition())
message = str(clothes())
feels = feels_temperature
description = weather_description
return render_template("dress.html", message=message, temp=temp, feels_temperature=feels,
weather_description=description, cityname=cityname) # also pass cityname
I created a minimalistic dress.html:
<html>
<body>
<p>message = {{ message }}</p>
<p>temp = {{ temp }}</p>
<p>feels_temperature = {{ feels_temperature }}</p>
<p>weather_description = {{ weather_description }}</p>
<p>cityname = {{ cityname }}</p>
</body>
</html>
Starting the application via flask run allows me to input a city name into the form field and view the results (for example 'Berlin'):
In order to show the weather description for the chosen city, you could create a function that accepts the city name and retrieves the information from the web (just a rough sketch):
import requests, json
import weatherMappingMessage
from app import dress
from keys import *
def weather_for_city(city_name):
base_url = "http://api.openweathermap.org/data/2.5/weather?"
complete_url = base_url + "appid=" + api_key + "&q=" + city_name + "&units=metric"
response = requests.get(complete_url)
if response.status_code == 200:
return response.json() # assumes your API returns a JSON response
else:
# perform some error handling here, maybe apply a retry strategy
pass
Extract the relevant data from the result of weather_for_city and pass it to render_template like you did for the other variables.

Calling function from request function in views Django

I have simple html template with link, that starts script, in views that retrieves data to the page,in views file I have two functions: render function def output(request):(it retrieves data to the page) and another function def summoner(): that makes postgres quires in cycle and appends results to the list. Separately each of them work fine, but I have to call second function from render function and retrieve the data to the page, but now when I do that all I am getting is empty list.
enter image description here
template:
<html>
<head>
<title>
Python script
</title>
</head>
<body>
Execute Script <hr>
{% if data %}
{{ data }}
{% endif %}
</body>
</html>
views:
from django.shortcuts import render
import pandas as pd
import psycopg2
import os, glob
conn = psycopg2.connect(host='127.0.0.1', database='db',
user='user', password='pass')
cur = conn.cursor()
def insert_data_as_is(file_name):
cur.execute('truncate table test_inv.start_tbl')
with open(file_name, 'r') as file:
cur.execute("insert into test_inv.start_tbl values {0}".format(file.read()))
conn.commit()
def insert_data_to_be(file_name):
cur.execute('truncate table test_inv.res_calc_ratios_t')
with open(file_name, 'r') as file:
cur.execute('insert into test_inv.res_calc_ratios_t (test_no, test_name, hcode_id, '
'hcode_name, hcode_'
'unit_name,'
' org_id, dor_kod, duch_id, nod_id, date_type_id, metric_type_id, cargo_type_id, val_type_id,'
' unit_id, dt, value, ss, dir_id, kato_id, vids_id) values {0}'.format(file.read()))
conn.commit()
path_start = 'files/csv_s/as_is/'
start_list = []
path_finish = 'files/csv_s/to_be'
finish_list = []
for infile in glob.glob(os.path.join(path_start, '*.*')):
start_list.append(infile)
for infile in glob.glob(os.path.join(path_finish, '*.*')):
finish_list.append(infile)
def summoner():
fun_sql_set = []
fun_query = """select * from test_inv.test_ratios('1','15')"""
for i in range(len(finish_list)):
insert_data_as_is(start_list[i])
insert_data_to_be(finish_list[i])
results = pd.read_sql_query(fun_query, conn)
fun_sql_set.append(results)
return fun_sql_set
def index(request):
return render(request,'calculus/index.html')
def output(request):
data = summoner()
print(data)
return render(request,'calculus/index.html',{'data':data})
The answer is creating absolute file path:
cur_dir = os.path.dirname(os.path.abspath(__file__))
path_start = '{}/static/csv_s/as_is/'.format(cur_dir)
path_finish = '{}/static/csv_s/to_be/'.format(cur_dir)

How to do validation for Two or more CSV file in python flask

Please Before Dislike this Question ask me what you don't understand Hello Guys i have data generation program which will do lots of computation so i cannot paste my whole program here so only talking about my program All the computation of the program starts with reading the file so when i'm selecting multiple CSV file in web page in "choose file" option i need to validate the columns numbers(should be same) of the all csv file and columns headers name should also match.. program i have written is like this:
from flask import Flask, render_template
import os
import csv
import pandas as pd
import numpy as np
app = Flask(__name__)
APP_ROOT = os.path.dirname(os.path.abspath(__file__))
#app.route("/")
def index():
print("Loading the root file")
return render_template("upload.html")
#app.route("/upload", methods=['POST'])
def upload():
target = os.path.join(APP_ROOT, 'input/')
print("target-",target)
if not os.path.isdir(target):
os.mkdir(target)
for file in request.files.getlist("source_fileName"):
print("file-",file)
filename = file.filename
print("filename-",filename)
destination = "/".join([target, filename])
print("destination-",destination)
file.save(destination)
print("file>",file)
global tempFile
tempFile = destination
print("tempFile - " + tempFile)
return redirect("/compute", )
def compute():
readerForRowCheck = pd.read_csv(tempFile)
for row in readerForRowCheck:
if (len(row) != 8):
return render_template("Incomplete.html")
headerColumn1 = row[0];
headerColumn2 = row[1];
headerColumn3 = row[2];
headerColumn4 = row[3];
headerColumn5 = row[4];
headerColumn6 = row[5];
headerColumn7 = row[6];
headerColumn8 = row[7];
if (headerColumn1 != "Asset_Id") or (headerColumn2 != "Asset Family") \
or (headerColumn3 != "Asset Name") or (headerColumn4 != "Location")or (headerColumn5 != "Asset Component") \
or (headerColumn6 != "Keywords") or (headerColumn7 != "Conditions") or (headerColumn8 != "Parts") :
return render_template("incomplete.html")
.....................................so on to then it will go to perform other task
HTML program:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title> upload </title>
</head>
<body>
<div class="container">
<h1>Large Data Generation</h1>
<form id = "upload-form" action="{{ url_for('upload') }}" method="POST" enctype="multipart/form-data">
<div id="file-selector">
<p>
<strong>Source File: </strong>
<input id="source_fileName" type="file" name="source_fileName" accept="csv/*" multiple />
</p>
</div>
<input type="submit" value="Generate Data" id="upload-button" >
</form>
</div>
</body>
Note:**I have only given lines of code which important otherwise its contain lots of code **Here i'm getting that how should i validate the csv file on the columns numbers and name should be same i know my validation for reading csv file is not correct that why i'm here please Help me.....thanks
I You are having multiple files then you need to create instance of dataframe for each file
Upload function will look like this:
def upload():
target = os.path.join(APP_ROOT, 'input/')
print("target-",target)
if not os.path.isdir(target):
os.mkdir(target)
abs_path_files=[]
for file in request.files.getlist("source_fileName"):
print("file-",file)
filename = file.filename
print("filename-",filename)
destination = "/".join([target, filename])
print("destination-",destination)
file.save(destination)
print("file>",file)
tempFile = os.path.abspath(destination)
abs_path_files.append(tempfile)
print("tempFile - " + tempFile)
return redirect(url_for("compute", files_list=abs_path_files))
Compute Function will look like this:
def compute(files_list):
dataFrames=[]
for f in files_list:
dataFrame=pd.read_csv(f)
dataFrames.append(dataFrame)
col_in_files = set([",".join(list(f.column.values)) for f in dataFrames])
if len(col_in_files)==1:
#then process your data here

Render HTML to PDF in Django site

For my django powered site, I am looking for an easy solution to convert dynamic html pages to pdf.
Pages include HTML and charts from Google visualization API (which is javascript based, yet including those graphs is a must).
Try the solution from Reportlab.
Download it and install it as usual with python setup.py install
You will also need to install the following modules: xhtml2pdf, html5lib, pypdf with easy_install.
Here is an usage example:
First define this function:
import cStringIO as StringIO
from xhtml2pdf import pisa
from django.template.loader import get_template
from django.template import Context
from django.http import HttpResponse
from cgi import escape
def render_to_pdf(template_src, context_dict):
template = get_template(template_src)
context = Context(context_dict)
html = template.render(context)
result = StringIO.StringIO()
pdf = pisa.pisaDocument(StringIO.StringIO(html.encode("ISO-8859-1")), result)
if not pdf.err:
return HttpResponse(result.getvalue(), content_type='application/pdf')
return HttpResponse('We had some errors<pre>%s</pre>' % escape(html))
Then you can use it like this:
def myview(request):
#Retrieve data or whatever you need
return render_to_pdf(
'mytemplate.html',
{
'pagesize':'A4',
'mylist': results,
}
)
The template:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>My Title</title>
<style type="text/css">
#page {
size: {{ pagesize }};
margin: 1cm;
#frame footer {
-pdf-frame-content: footerContent;
bottom: 0cm;
margin-left: 9cm;
margin-right: 9cm;
height: 1cm;
}
}
</style>
</head>
<body>
<div>
{% for item in mylist %}
RENDER MY CONTENT
{% endfor %}
</div>
<div id="footerContent">
{%block page_foot%}
Page <pdf:pagenumber>
{%endblock%}
</div>
</body>
</html>
Try wkhtmltopdf with either one of the following wrappers
django-wkhtmltopdf or python-pdfkit
This worked great for me,supports javascript and css or anything for that matter which a webkit browser supports.
For more detailed tutorial please see this blog post
https://github.com/nigma/django-easy-pdf
Template:
{% extends "easy_pdf/base.html" %}
{% block content %}
<div id="content">
<h1>Hi there!</h1>
</div>
{% endblock %}
View:
from easy_pdf.views import PDFTemplateView
class HelloPDFView(PDFTemplateView):
template_name = "hello.html"
If you want to use django-easy-pdf on Python 3 check the solution suggested here.
I just whipped this up for CBV. Not used in production but generates a PDF for me. Probably needs work for the error reporting side of things but does the trick so far.
import StringIO
from cgi import escape
from xhtml2pdf import pisa
from django.http import HttpResponse
from django.template.response import TemplateResponse
from django.views.generic import TemplateView
class PDFTemplateResponse(TemplateResponse):
def generate_pdf(self, retval):
html = self.content
result = StringIO.StringIO()
rendering = pisa.pisaDocument(StringIO.StringIO(html.encode("ISO-8859-1")), result)
if rendering.err:
return HttpResponse('We had some errors<pre>%s</pre>' % escape(html))
else:
self.content = result.getvalue()
def __init__(self, *args, **kwargs):
super(PDFTemplateResponse, self).__init__(*args, mimetype='application/pdf', **kwargs)
self.add_post_render_callback(self.generate_pdf)
class PDFTemplateView(TemplateView):
response_class = PDFTemplateResponse
Used like:
class MyPdfView(PDFTemplateView):
template_name = 'things/pdf.html'
I tried the best answer in this thread and it didn't work for python3.8, hence I had to do some changes as follows ( for anyone working on python3.8 ) :
import io
from xhtml2pdf import pisa
from django.http import HttpResponse
from html import escape
from django.template.loader import render_to_string
def render_to_pdf(template_src, context_dict):
html = render_to_string(template_src, context_dict)
result = io.BytesIO()
pdf = pisa.pisaDocument(io.BytesIO (html.encode("utf-8")), result)
if not pdf.err:
return HttpResponse(result.getvalue(), content_type='application/pdf')
return HttpResponse('We had some errors<pre>%s</pre>' % escape(html))
I had to change cgi to html since cgi.escape is depricated, and I replaced StringIO with io.ByteIO() as for the rendering I used render_to_string instead of converting the dict to context which was throwing an error.
After trying to get this to work for too many hours, I finally found this:
https://github.com/vierno/django-xhtml2pdf
It's a fork of https://github.com/chrisglass/django-xhtml2pdf that provides a mixin for a generic class-based view. I used it like this:
# views.py
from django_xhtml2pdf.views import PdfMixin
class GroupPDFGenerate(PdfMixin, DetailView):
model = PeerGroupSignIn
template_name = 'groups/pdf.html'
# templates/groups/pdf.html
<html>
<style>
#page { your xhtml2pdf pisa PDF parameters }
</style>
</head>
<body>
<div id="header_content"> (this is defined in the style section)
<h1>{{ peergroupsignin.this_group_title }}</h1>
...
Use the model name you defined in your view in all lowercase when populating the template fields. Because its a GCBV, you can just call it as '.as_view' in your urls.py:
# urls.py (using url namespaces defined in the main urls.py file)
url(
regex=r"^(?P<pk>\d+)/generate_pdf/$",
view=views.GroupPDFGenerate.as_view(),
name="generate_pdf",
),
You can use iReport editor to define the layout, and publish the report in jasper reports server. After publish you can invoke the rest api to get the results.
Here is the test of the functionality:
from django.test import TestCase
from x_reports_jasper.models import JasperServerClient
"""
to try integraction with jasper server through rest
"""
class TestJasperServerClient(TestCase):
# define required objects for tests
def setUp(self):
# load the connection to remote server
try:
self.j_url = "http://127.0.0.1:8080/jasperserver"
self.j_user = "jasperadmin"
self.j_pass = "jasperadmin"
self.client = JasperServerClient.create_client(self.j_url,self.j_user,self.j_pass)
except Exception, e:
# if errors could not execute test given prerrequisites
raise
# test exception when server data is invalid
def test_login_to_invalid_address_should_raise(self):
self.assertRaises(Exception,JasperServerClient.create_client, "http://127.0.0.1:9090/jasperserver",self.j_user,self.j_pass)
# test execute existent report in server
def test_get_report(self):
r_resource_path = "/reports/<PathToPublishedReport>"
r_format = "pdf"
r_params = {'PARAM_TO_REPORT':"1",}
#resource_meta = client.load_resource_metadata( rep_resource_path )
[uuid,out_mime,out_data] = self.client.generate_report(r_resource_path,r_format,r_params)
self.assertIsNotNone(uuid)
And here is an example of the invocation implementation:
from django.db import models
import requests
import sys
from xml.etree import ElementTree
import logging
# module logger definition
logger = logging.getLogger(__name__)
# Create your models here.
class JasperServerClient(models.Manager):
def __handle_exception(self, exception_root, exception_id, exec_info ):
type, value, traceback = exec_info
raise JasperServerClientError(exception_root, exception_id), None, traceback
# 01: REPORT-METADATA
# get resource description to generate the report
def __handle_report_metadata(self, rep_resourcepath):
l_path_base_resource = "/rest/resource"
l_path = self.j_url + l_path_base_resource
logger.info( "metadata (begin) [path=%s%s]" %( l_path ,rep_resourcepath) )
resource_response = None
try:
resource_response = requests.get( "%s%s" %( l_path ,rep_resourcepath) , cookies = self.login_response.cookies)
except Exception, e:
self.__handle_exception(e, "REPORT_METADATA:CALL_ERROR", sys.exc_info())
resource_response_dom = None
try:
# parse to dom and set parameters
logger.debug( " - response [data=%s]" %( resource_response.text) )
resource_response_dom = ElementTree.fromstring(resource_response.text)
datum = ""
for node in resource_response_dom.getiterator():
datum = "%s<br />%s - %s" % (datum, node.tag, node.text)
logger.debug( " - response [xml=%s]" %( datum ) )
#
self.resource_response_payload= resource_response.text
logger.info( "metadata (end) ")
except Exception, e:
logger.error( "metadata (error) [%s]" % (e))
self.__handle_exception(e, "REPORT_METADATA:PARSE_ERROR", sys.exc_info())
# 02: REPORT-PARAMS
def __add_report_params(self, metadata_text, params ):
if(type(params) != dict):
raise TypeError("Invalid parameters to report")
else:
logger.info( "add-params (begin) []" )
#copy parameters
l_params = {}
for k,v in params.items():
l_params[k]=v
# get the payload metadata
metadata_dom = ElementTree.fromstring(metadata_text)
# add attributes to payload metadata
root = metadata_dom #('report'):
for k,v in l_params.items():
param_dom_element = ElementTree.Element('parameter')
param_dom_element.attrib["name"] = k
param_dom_element.text = v
root.append(param_dom_element)
#
metadata_modified_text =ElementTree.tostring(metadata_dom, encoding='utf8', method='xml')
logger.info( "add-params (end) [payload-xml=%s]" %( metadata_modified_text ) )
return metadata_modified_text
# 03: REPORT-REQUEST-CALL
# call to generate the report
def __handle_report_request(self, rep_resourcepath, rep_format, rep_params):
# add parameters
self.resource_response_payload = self.__add_report_params(self.resource_response_payload,rep_params)
# send report request
l_path_base_genreport = "/rest/report"
l_path = self.j_url + l_path_base_genreport
logger.info( "report-request (begin) [path=%s%s]" %( l_path ,rep_resourcepath) )
genreport_response = None
try:
genreport_response = requests.put( "%s%s?RUN_OUTPUT_FORMAT=%s" %(l_path,rep_resourcepath,rep_format),data=self.resource_response_payload, cookies = self.login_response.cookies )
logger.info( " - send-operation-result [value=%s]" %( genreport_response.text) )
except Exception,e:
self.__handle_exception(e, "REPORT_REQUEST:CALL_ERROR", sys.exc_info())
# parse the uuid of the requested report
genreport_response_dom = None
try:
genreport_response_dom = ElementTree.fromstring(genreport_response.text)
for node in genreport_response_dom.findall("uuid"):
datum = "%s" % (node.text)
genreport_uuid = datum
for node in genreport_response_dom.findall("file/[#type]"):
datum = "%s" % (node.text)
genreport_mime = datum
logger.info( "report-request (end) [uuid=%s,mime=%s]" %( genreport_uuid, genreport_mime) )
return [genreport_uuid,genreport_mime]
except Exception,e:
self.__handle_exception(e, "REPORT_REQUEST:PARSE_ERROR", sys.exc_info())
# 04: REPORT-RETRIEVE RESULTS
def __handle_report_reply(self, genreport_uuid ):
l_path_base_getresult = "/rest/report"
l_path = self.j_url + l_path_base_getresult
logger.info( "report-reply (begin) [uuid=%s,path=%s]" %( genreport_uuid,l_path) )
getresult_response = requests.get( "%s%s/%s?file=report" %(self.j_url,l_path_base_getresult,genreport_uuid),data=self.resource_response_payload, cookies = self.login_response.cookies )
l_result_header_mime =getresult_response.headers['Content-Type']
logger.info( "report-reply (end) [uuid=%s,mime=%s]" %( genreport_uuid, l_result_header_mime) )
return [l_result_header_mime, getresult_response.content]
# public methods ---------------------------------------
# tries the authentication with jasperserver throug rest
def login(self, j_url, j_user,j_pass):
self.j_url= j_url
l_path_base_auth = "/rest/login"
l_path = self.j_url + l_path_base_auth
logger.info( "login (begin) [path=%s]" %( l_path) )
try:
self.login_response = requests.post(l_path , params = {
'j_username':j_user,
'j_password':j_pass
})
if( requests.codes.ok != self.login_response.status_code ):
self.login_response.raise_for_status()
logger.info( "login (end)" )
return True
# see http://blog.ianbicking.org/2007/09/12/re-raising-exceptions/
except Exception, e:
logger.error("login (error) [e=%s]" % e )
self.__handle_exception(e, "LOGIN:CALL_ERROR",sys.exc_info())
#raise
def generate_report(self, rep_resourcepath,rep_format,rep_params):
self.__handle_report_metadata(rep_resourcepath)
[uuid,mime] = self.__handle_report_request(rep_resourcepath, rep_format,rep_params)
# TODO: how to handle async?
[out_mime,out_data] = self.__handle_report_reply(uuid)
return [uuid,out_mime,out_data]
#staticmethod
def create_client(j_url, j_user, j_pass):
client = JasperServerClient()
login_res = client.login( j_url, j_user, j_pass )
return client
class JasperServerClientError(Exception):
def __init__(self,exception_root,reason_id,reason_message=None):
super(JasperServerClientError, self).__init__(str(reason_message))
self.code = reason_id
self.description = str(exception_root) + " " + str(reason_message)
def __str__(self):
return self.code + " " + self.description
I get the code to generate the PDF from html template :
import os
from weasyprint import HTML
from django.template import Template, Context
from django.http import HttpResponse
def generate_pdf(self, report_id):
# Render HTML into memory and get the template firstly
template_file_loc = os.path.join(os.path.dirname(__file__), os.pardir, 'templates', 'the_template_pdf_generator.html')
template_contents = read_all_as_str(template_file_loc)
render_template = Template(template_contents)
#rendering_map is the dict for params in the template
render_definition = Context(rendering_map)
render_output = render_template.render(render_definition)
# Using Rendered HTML to generate PDF
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename=%s-%s-%s.pdf' % \
('topic-test','topic-test', '2018-05-04')
# Generate PDF
pdf_doc = HTML(string=render_output).render()
pdf_doc.pages[0].height = pdf_doc.pages[0]._page_box.children[0].children[
0].height # Make PDF file as single page file
pdf_doc.write_pdf(response)
return response
def read_all_as_str(self, file_loc, read_method='r'):
if file_exists(file_loc):
handler = open(file_loc, read_method)
contents = handler.read()
handler.close()
return contents
else:
return 'file not exist'
This is for Django >=3
This code converts HTML template to pdf file for any page. For example: post/1/new1, post/2/new2
pdf file name is last part in url. For example for post/2/new2, file name is new2
First install xhtml2pdf
pip install xhtml2pdf
urls.py
from .views import generatePdf as GeneratePdf
from django.urls import re_path
urlpatterns = [
#...
re_path(r'^pdf/(?P<cid>[0-9]+)/(?P<value>[a-zA-Z0-9 :._-]+)/$', GeneratePdf, name='pdf'),
#...
]
views.py
from django.template.loader import get_template
from .utils import render_to_pdf
# pdf
def generatePdf(request,cid,value):
print(cid,value)
pdf = render_to_pdf('myappname/pdf/your.html',cid)
return HttpResponse(pdf, content_type='application/pdf')
utils.py
from io import BytesIO #A stream implementation using an in-memory bytes buffer
# It inherits BufferIOBase
from django.http import HttpResponse
from django.template.loader import get_template
#pisa is a html2pdf converter using the ReportLab Toolkit,
#the HTML5lib and pyPdf.
from xhtml2pdf import pisa
#difine render_to_pdf() function
from .models import myappname
from django.shortcuts import get_object_or_404
def render_to_pdf(template_src,cid, context_dict={}):
template = get_template(template_src)
node = get_object_or_404(myappname, id =cid)
context = {'node':node}
context_dict=context
html = template.render(context_dict)
result = BytesIO()
#This part will create the pdf.
pdf = pisa.pisaDocument(BytesIO(html.encode("ISO-8859-1")), result)
if not pdf.err:
return HttpResponse(result.getvalue(), content_type='application/pdf')
return None
Structure:
myappname/
|___views.py
|___urls.py
|___utils.py
|___templates/myappname/your.html
If you have context data along with css and js in your html template.
Than you have good option to use pdfjs.
In your code you can use like this.
from django.template.loader import get_template
import pdfkit
from django.conf import settings
context={....}
template = get_template('reports/products.html')
html_string = template.render(context)
pdfkit.from_string(html_string, os.path.join(settings.BASE_DIR, "media", 'products_report-%s.pdf'%(id)))
In your HTML you can link extranal or internal css and js, it will generate best quality of pdf.

Categories

Resources