Let’s say you want to save the screenshot of a webpage in Django. For example, you might need to store a screenshot of an agreement with your client’s signature when it’s filled up and submitted.
If you look for an external library to do this, you might stumble upon django-screamshot. I haven’t tried this library, but it looks promising.
In this tutorial, however, I’ll show you how to achieve this using the HTML5 Canvas and Python.
Here’s my implementation of a Django save screenshot feature:
html2canvas
This is a great tool that converts a webpage — or parts of it — into an image using the HTML Canvas. You can download it here. There’s also a CDN. Include it on your webpage with the following code, and you’re good to go.
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script>
The Django Form
The form class for this needs to contain a CharField that will hold the data for the image. We use a CharField instead of something like an ImageField because we can’t programmatically populate a file upload field in HTML due to security reasons. So, we get the image in an encoded format and pass it along to the backend from the CharField. Here’s the form I used:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from django import forms from crispy_forms.helper import FormHelper class TestForm(forms.Form): """ Form to upload the screenshot of a webpage """ image_data = forms.CharField(widget=forms.HiddenInput(), required=False) def __init__(self, *args, **kwargs): self.helper = FormHelper() self.helper.form_id = "agreement_form" self.helper.form_method = 'post' super(TestForm, self).__init__(*args, **kwargs) |
The HTML Page
This is the page we’ll be taking a screenshot of. For now let’s just include a bootstrap panel in there some dummy text. We also need a button to submit the form. Here’s the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
{% load crispy_forms_tags %} <!DOCTYPE html> <html> <head> <title>Screenshot!</title> {# html2canvas #} <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script> {# jSignature #} <script type="text/javascript" src="/static/js/jSignature.min.js"></script> <script type="text/javascript" src="/static/js/django_jsignature.js"></script> <div id="div_id_signature" class="form-group"> {# jQuery #} <script src="https://code.jquery.com/jquery-1.11.0.min.js"></script> {# bootstrap #} <!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous"> <!-- Optional theme --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous"> <!-- Latest compiled and minified JavaScript --> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script> </head> <body> <div class="container"> <div class="row"> <div class="col-xs-12"> <h1 class="text-center">My Test Webpage</h1> <hr> <div class="panel panel-default"> <div class="panel-heading"> A wild panel </div> <div class="panel-body"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt cupiditate, debitis soluta nobis doloribus nam, vero nisi. Vitae rerum dolores debitis quo voluptatem! Sit quasi voluptatibus ut, cupiditate laudantium eos. </div> <div class="row"> {% crispy form %} </div> </div> <button type="button" id="submit" class="btn btn-primary">Save Screenshot</button> </div> </div> </div> <script type="text/javascript"> $('#submit').click(function(){ html2canvas(document.body, { onrendered: function (canvas) { // toDataURL defaults to png, so we need to request a jpeg image_data = canvas.toDataURL('image/jpeg'); $('#id_image_data').val(image_data); $('#agreement_form').submit(); } }); }); </script> </body> </html> |
This is what it looks like:
The JavaScript code
Now we need to use html2canvas to generate an image of our website with some JavaScript. The examples shown on the official site only show how to generate and display the image, but we’ll need to convert the image to a data uri in order to send it to the backend. Here’s my implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<script type="text/javascript"> $('#submit').click(function(){ html2canvas(document.body, { onrendered: function (canvas) { // toDataURL defaults to png, so we need to request a jpeg image_data = canvas.toDataURL('image/jpeg'); $('#id_image_data').val(image_data); $('#agreement_form').submit(); } }); }); </script> |
Decoding the Image
Now that we have the image data at the backend, we need to decode it into an actual image and save it where we need. This will require some base64 encoding and decoding magic that took wading through an incredible number of StackOverflow links to figure out. Here’s what I used in the end.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import re import base64 ... class TestForm(forms.Form): """ Form to upload the screenshot of a webpage """ ... def save_screenshot(self): dataUrlPattern = re.compile('data:image/(png|jpeg);base64,(.*)$') image_data = self.cleaned_data['image_data'] image_data = dataUrlPattern.match(image_data).group(2) image_data = image_data.encode() image_data = base64.b64decode(image_data) with open('screenshot.jpg', 'wb') as f: f.write(image_data) |
1 2 3 4 5 6 7 8 9 10 11 12 |
from django.http import HttpResponse from django.views.generic.edit import FormView from .forms import TestForm class Test(FormView): template_name = "test.html" form_class = TestForm def form_valid(self, form, *args, **kwargs): form.save_screenshot() return HttpResponse("Done") |
Final Result
Here’s the result of the Django save screenshot:
Notes:
- If you need a png image instead of a jpg, use canvas.toDataURL('image/png');
- For this simple webpage, the screenshot is quite perfect. However, if you have complex CSS on your page, some elements may not render correctly.
Thank You so Much! It was very helpful