Blobstore Python API Download URL using Django HTML template - python

I'm creating a dropbox service using BlobStore, however I can't find a good way to implement the file download option from my Django HTML template.
Here is my app.yaml
application: my_application
version: 1
runtime: python27
api_version: 1
threadsafe: false
handlers:
- url: /favicon\.ico
static_files: favicon.ico
upload: /favicon\.ico
- url: (/.*)*
script: main.app
libraries:
- name: django
version: latest
Here is the main.py
#!/usr/bin/env python
from django.core.management import setup_environ
import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext import blobstore
from google.appengine.ext.blobstore import BlobInfo
from google.appengine.ext.webapp import blobstore_handlers
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp import util
import django
import webapp2
import wsgiref.handlers
class UserUpload(db.Model):
blob_key = blobstore.BlobReferenceProperty(required=True)
user = db.UserProperty(required=True)
user_id = db.StringProperty(required=True)
creation = db.DateTimeProperty(required=True, auto_now_add=True)
filename = db.StringProperty()
content_type = db.StringProperty()
size = db.IntegerProperty()
MD5 = db.StringProperty()
# Start Dumpbox login
class MainPage(webapp2.RequestHandler):
#util.login_required
def get(self):
logout_url = users.create_logout_url('/')
upload_url = blobstore.create_upload_url('/upload')
user = users.get_current_user()
q = UserUpload.all()
q.filter('user_id =', users.get_current_user().user_id())
q.order('-creation')
if user:
html = template.render('templates/header.html', {'title': 'Dumpbox', 'nickname': user.nickname(), 'url': upload_url})
html = html + template.render('templates/table.html', { 'file_name': 'File Name',
'content_type': 'Content Type',
'size': 'Size',
'timestamp': 'Creation Date',
'q': q
})
html = html + template.render('templates/footer.html', {'logout_link': logout_url})
self.response.out.write(html)
else:
self.redirect(users.create_login_url(self.request.uri))
class LogoutHandler(webapp2.RequestHandler):
def get(self):
self.auth.unset_session()
self.redirect(self.uri_for('home'))
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
upload_files = self.get_uploads('file') # 'file' is file upload field in the form
blob_info = upload_files[0]
user_upload = UserUpload( user=users.get_current_user(),
user_id=users.get_current_user().user_id(),
blob_key=blob_info.key(),
filename=blob_info.filename,
content_type=blob_info.content_type,
size=blob_info.size,
creation=blob_info.creation,
MD5=blob_info.md5_hash
)
db.put(user_upload)
self.redirect('/')
class DownloadHandler(blobstore_handlers.BlobstoreDownloadHandler):
def get(self, resource):
resource = str(urllib.unquote(resource))
blob_info = blobstore.BlobInfo.get(resource)
self.send_blob(blob_info)
app = webapp2.WSGIApplication([('/', MainPage),('/upload', UploadHandler),('/download([^/]+)?', DownloadHandler)],debug=True)
wsgiref.handlers.CGIHandler().run(app)
def main():
run_wsgi_app(app)
if __name__ == '__main__':
main()
The Django template uses a table with a checkbox to select file to be downloaded, but I can't find a good way to implement it.
How can tick the checkbox and submit a form to download a blobstore file?
<form method="GET" action="">
<div id="table-wrapper">
<div id="table-scroll">
<table id="file_table" class="display" style="width:100%">
<tr>
<th style="width:10px"><input type="checkbox" onclick="checkAll(this)"/></th>
<th>{{file_name}}</th>
<th>{{content_type}}</th>
<th>{{size}}</th>
<th>{{timestamp}}</th>
</tr>
{% for file in q %}
{% if file.size > 0 %}
<tr>
<td><input id="{{file.blob_key}}" type="checkbox"/></td>
<td>{{file.filename}}</td>
<td>{{file.content_type}}</td>
<td>{{file.size}} Bytes</td>
<td>{{file.creation}}</td>
</tr>
{% endif %}
{% endfor %}
</table>
</div>
</div>
<p><input type="submit" value="Download"></p>
</form>
Appreciate any help.

Here's my solution:
In your template change to line
<td>{{file.filename}}</td>
into
<td>{{file.filename}}</td>
In your views add two method:
def download(request,client):
if 'key' in request.GET:
blob_key_str = request.GET['key']
if not blob_key_str:
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
blob_key = str(urllib.unquote(blob_key_str))
blob = blobstore.BlobInfo.get(blob_key)
try:
return send_blob(blob, save_as=True)
except ValueError, error:
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
and
import cgi
import logging
import urllib
def send_blob(blob_key_or_info, content_type=None, save_as=None):
CONTENT_DISPOSITION_FORMAT = 'attachment; filename="%s"'
if isinstance(blob_key_or_info, blobstore.BlobInfo):
blob_key = blob_key_or_info.key()
blob_info = blob_key_or_info
else:
blob_key = blob_key_or_info
blob_info = None
logging.debug(blob_info)
response = HttpResponse()
response[blobstore.BLOB_KEY_HEADER] = str(blob_key)
if content_type:
if isinstance(content_type, unicode):
content_type = content_type.encode('utf-8')
response['Content-Type'] = content_type
else:
del response['Content-Type']
def send_attachment(filename):
if isinstance(filename, unicode):
filename = filename.encode('utf-8')
response['Content-Disposition'] = (CONTENT_DISPOSITION_FORMAT % filename)
if save_as:
if isinstance(save_as, basestring):
send_attachment(save_as)
elif blob_info and save_as is True:
send_attachment(blob_info.filename)
else:
if not blob_info:
raise ValueError('The specified file is not found.')
else:
raise ValueError(
'Unexpected value for the name in which file is '
'expected to be downloaded')
return response
Change this code according to your needs :)

Related

Upload multiple images with Flask-Admin

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>

Retrieving Submitted Greetings in the Tutorial

I'm working though the GAE Tutorial and I'm getting the following error.
File "/Users/cparrish/bin/guestbook/guestbook.py", line 62, in get
for greeting in greetings:
NameError: global name 'greetings' is not defined
So I think the problem is here somewhere.
greetings_query = Greeting.query(
ancestor = guestbook_key(guestbook_name)).order(-Greeting.date)
greeetings = greetings_query.fetch(10)
for greeting in greetings:
if greeting.author:
self.response.write(
'<b>%s</b> wrote:' % greeting.author.nickname())
Which are lines 58 - 66
So I'm wondering if anyone else can see what I'm apparently missing here.
Full code base below.
import cgi
import urllib
from google.appengine.api import users
from google.appengine.ext import ndb
import webapp2
MAIN_PAGE_FOOTER_TEMPLATE = """\
<form action="/sign?%s" method="post">
<div><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Sign Guestbook"></div>
</form>
<hr>
<form>Guestbook name:
<input value="%s" name="guestbook_name">
<input type="submit" value="switch">
</from>
%s
</body>
</html>
"""
DEFAULT_GUESTBOOK_NAME = 'default_guestbook'
# We set a parent key on the 'Greetings' to ensure that they are all in the same
# entity group. Queries across the signle entity group will be consistent.
# However, the write rate should be limited to -1/second.
def guestbook_key(guestbook_name="DEFAULT_GUESTBOOK_NAME"):
""" Constructs a Datastore key for a Guestbook entity with guestbook_name. """
return ndb.Key('Guestbook', guestbook_name)
class Greeting(ndb.Model):
"""Models an individual Guestbook entry with author, content, and date. """
author = ndb.UserProperty()
content = ndb.StringProperty( indexed = False )
date = ndb.DateTimeProperty( auto_now_add = True )
class MainPage(webapp2.RequestHandler):
def get(self):
self.response.write('<html><body>')
guestbook_name = self.request.get('guestbook_name', DEFAULT_GUESTBOOK_NAME)
# Ancestor Queries, as shown here, are strongly consisten with the High
# Replication Datastore. Queries that span entity groups are eventually
# consisten. If we omitted the ancestor from this query there would be
# a slight chance that Greetings that had just been written would not
# show up in a query.
greetings_query = Greeting.query(
ancestor = guestbook_key(guestbook_name)).order(-Greeting.date)
greeetings = greetings_query.fetch(10)
for greeting in greetings:
if greeting.author:
self.response.write(
'<b>%s</b> wrote:' % greeting.author.nickname())
else:
self.response.write('An anonymous person wrote:')
self.response.write('<blockquote>%s</blockquote' %
cgi.escape(greeting.content))
if users.get_current_user():
url = users.create_logout_url(self.request.uri)
url_linktext = 'Logout'
else:
url = users.create_login_url(self.request.uri)
url_linktext = 'Login'
# Write the submission form and the footer of the page
sign_query_parms = urllib.urlencode({'guestbook_name': guestbook_name})
self.response.write(MAIN_PAGE_FOOTER_TEMPLATE % (sign_query_parms, cgi.escape(guestbook_name), url, url_linktext))
class Guestbook(webapp2.RequestHandler):
def post(self):
# We set the same parent key on the 'Greeting' to ensure each getting
# is in the same entity group. Queries across the single entity group
# will be consistent. However, the write reate to a sing entity groupo
# should be limited to ~1/second.
guestbook_name = self.request.get('guestbook_name',
DEFAULT_GUESTBOOK_NAME)
greeting = Greeting(parent=guestbook_key(guestbook_name))
if users.get_current_user():
greeting.author = users.get_current_user()
greeting.content = self.request.get('content')
greeting.put()
query_params = {'guestbook_name': guestbook_name}
self.redirect('/?' + urllib.urlencode(query_params))
application = webapp2.WSGIApplication([
('/', MainPage),
('/sign', Guestbook),
], debug=True)
There are three e's in your assignment of greeetings.
greeetings = greetings_query.fetch(10)
for greeting in greetings:
One of these es is not like the others, one of these es just doesn't belong...

How To Get Blob Key with Python?

I am having difficulties in storing and then retrieving the blobstore key. It looks like I am able to get the database row key # instead of the blob key#....
1) I am not sure how to store the key
2) Also, I am not sure how the data currently gets stored into the UserPhoto datastructure
set up
3) How do I retrieve the Blob's key related to the current user, so I can display a link to i?
any help would be greatly appreciated.
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import blobstore
from google.appengine.ext.webapp import blobstore_handlers
from google.appengine.ext import db
import urllib
class UserPhoto(db.Model):
user = db.StringProperty()
user1 = db.EmailProperty()
blob_prop = blobstore.BlobKey
blob_key = blobstore.BlobReferenceProperty()
class MainPage(webapp.RequestHandler):
def get(self):
user = users.get_current_user()
upload_url = blobstore.create_upload_url('/upload')
existing_data = "<br>"
if user:
data = UserPhoto.all()
#filter data by user e-mail
results = data.filter("user1 = ", user.email())
for dt in results:
existing_data +='<a href="/serve/%s' % dt.key() + '">'
existing_data += '%s' % dt.key() + '<br>'
self.response.out.write(
'<div align=center>Hello %s <a align="center" href="%s">Sign out</a><br>Is administrator: %s' %
(user.nickname(), users.create_logout_url("/"), users.is_current_user_admin())
+'<form action="%s" method="POST" enctype="multipart/form-data">' % upload_url+
"""<br>Upload File: <input type="file" name="file"><br> <input type="submit"
name="submit" value="Submit"> </form>
<br>"""+existing_data + "</div>"
)
#the code below is used for testing...purposes only...
for b in blobstore.BlobInfo.all():
self.response.out.write('<li>' + str(b.filename) + '')
else:
self.redirect(users.create_login_url(self.request.uri))
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
upload_files = self.get_uploads('file') # 'file' is file upload field in the form
blob_info = upload_files[0]
user = users.get_current_user()
if user:
data = UserPhoto()
data.user1 = users.get_current_user().email()
data.blob_key = str(blob_info.key())
# data.user2[0] = self.get_uploads()
data.blob_prop = blob_info.key()
data.put()
#self.redirect('/serve/%s' % blob_info.key())
self.redirect('/')
class ViewPhotoHandler(blobstore_handlers.BlobstoreDownloadHandler):
def get(self, photo_key):
blob_key = str(urllib.unquote(photo_key))
if not blobstore.get(photo_key):
self.error(404)
else:
#self.send_blob(photo_key)
#self.send_blob('/serve/%s' % photo_key)
self.send_blob(blobstore.BlobInfo.get(blob_key), save_as=True)
class ServeHandler(blobstore_handlers.BlobstoreDownloadHandler):
def get(self, resource):
resource = str(urllib.unquote(resource))
blob_info = blobstore.BlobInfo.get(resource)
self.send_blob(blob_info)
application = webapp.WSGIApplication([('/', MainPage),
('/upload', UploadHandler),
('/serve/([^/]+)?', ServeHandler),
('/view_photo/([^/]+)?', ViewPhotoHandler)
],debug=True)
def main():
run_wsgi_app(application)
if __name__ == "__main__":
main()
You're asking for the UserPhoto entity key when you construct the output, not the blobstore reference within that entity. It should be:
existing_data +='<a href="/serve/%s">' % dt.blob_key

How to display an image in GAE datastore?

I read the tutorial and all the sources I could find about displaying an image saved in datastore and still I could not make it work. I appreciate any help. This is my previous question.
The code below, for /displayimage shows broken link for the image; and for /image it gives BadKeyError: Invalid string key . According to Nick Johnson reply here I must be passing an empty string for img_id but logging.info in /display image shows this key: (((result.key():)))) agpkZXZ-dGluZy0xcg8LEghIb21lUGFnZRjOCAw. Thanks for your help.
class HomePage(db.Model):
thumbnail = db.BlobProperty()
firm_name = db.StringProperty()
class ImageUpload(webapp.RequestHandler):
def get(self):
...
self.response.out.write("""
<form action="/imagesave" enctype="multipart/form-data" method="post">
<div><label>firm name:</label> <input type="text" name="firm_name" size=40></div>
<div><input type="file" name="img" /></div>
<div><input type="submit" value="Upload image"></div>
</form>
""")
class ImageSave(webapp.RequestHandler):
def post(self):
homepage = HomePage()
thumbnail = self.request.get("img")
firm_name = self.request.get("firm_name")
homepage.thumbnail = db.Blob(thumbnail)
homepage.firm_name = firm_name
homepage.put()
self.redirect("/imageupload")
class ImageResize(webapp.RequestHandler):
def post(self):
q = HomepageImage.all()
q.filter("firm_name", "mta")
qTable = q.get()
if qTable:
qTable.thumbnail = db.Blob(images.resize(self.request.get("img"), 32, 32))
db.put(qTable)
else:
self.response.out.write("""firm not found""")
self.redirect("/imageupload")
class DisplayImage(webapp.RequestHandler):
def get(self):
...
query = HomePage.all()
query.filter("firm_name", "mta")
result = query.get()
self.response.out.write("""firm name: %s""" % result.firm_name)
#self.response.out.write("""<img src="img?img_id=%s"></img>""" %
#chenged this line as systempuntoout's comment to:
self.response.out.write("""<img src="/image?img_id=%s"></img>""" %
result.key())
#but I still get the same error
class Image(webapp.RequestHandler):
def get(self):
...
#I am adding the next line to show that "img_id" is an empty string.
#why "img_id" empty here?
img_id = self.request.get("img_id")
logging.info("""**************************img_id: %s**************************""" % img_id)
#**************************img_id: **************************
homepage = db.get(self.request.get("img_id"))
if homepage.thumbnail:
self.response.headers['Content-Type'] = "image/jpg"
self.response.out.write(homepage.thumbnail)
else:
self.response.out.write("no image")
application = webapp.WSGIApplication(
[
("/imageresize",ImageResize),
("/imageupload", ImageUpload),
("/displayimage", DisplayImage),
("/imagesave", ImageSave),
("/image", Image),
],
debug=True
)
def main():
run_wsgi_app(application)
if __name__ == "__main__":
main()
You are pointing the image source to a not defined wrong img route .
The correct link should point to /image like this:
<img src="/image?img_id=%s"></img>
I've tested your code with my correction and it works nicely:
from google.appengine.ext import db
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
import logging
class HomePage(db.Model):
thumbnail = db.BlobProperty()
firm_name = db.StringProperty()
class ImageUpload(webapp.RequestHandler):
def get(self):
self.response.out.write("""
<form action="/imagesave" enctype="multipart/form-data" method="post">
<div><label>firm name:</label> <input type="text" name="firm_name" size=40></div>
<div><input type="file" name="img" /></div>
<div><input type="submit" value="Upload image"></div>
</form>
""")
class ImageSave(webapp.RequestHandler):
def post(self):
homepage = HomePage()
thumbnail = self.request.get("img")
firm_name = self.request.get("firm_name")
homepage.thumbnail = db.Blob(thumbnail)
homepage.firm_name = firm_name
homepage.put()
self.redirect("/imageupload")
class ImageResize(webapp.RequestHandler):
def post(self):
q = HomepageImage.all()
q.filter("firm_name", "mta")
qTable = q.get()
if qTable:
qTable.thumbnail = db.Blob(images.resize(self.request.get("img"), 32, 32))
db.put(qTable)
else:
self.response.out.write("""firm not found""")
self.redirect("/imageupload")
class DisplayImage(webapp.RequestHandler):
def get(self):
query = HomePage.all()
query.filter("firm_name", "mta")
result = query.get()
self.response.out.write("""firm name: %s""" % result.firm_name)
self.response.out.write("""<img src="/image?img_id=%s"></img>""" %
result.key())
class Image(webapp.RequestHandler):
def get(self):
img_id = self.request.get("img_id")
logging.info("""**************************img_id: %s**************************""" % img_id)
homepage = db.get(self.request.get("img_id"))
if homepage.thumbnail:
self.response.headers['Content-Type'] = "image/jpg"
self.response.out.write(homepage.thumbnail)
else:
self.response.out.write("no image")
application = webapp.WSGIApplication(
[
("/imageresize",ImageResize),
("/imageupload", ImageUpload),
("/displayimage", DisplayImage),
("/imagesave", ImageSave),
("/image", Image),
],
debug=True
)
def main():
run_wsgi_app(application)
if __name__ == "__main__":
main()

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