0

I have made a few Django projects after having read the tutorial but I am by no means an expert in Django.

I am trying to take a screenshot of the current page and store it (if one does not exist).

To achieve this, we require a few things:

  1. function to get screen shot of current page
  2. function to async post this image to a view which should store it
  3. view that stores the posted image

However, the screen shot function results in a Blob and I am having trouble getting a Django view to properly handle this.

A demo project is available here: https://gitlab.com/SumNeuron/so_save_blob

Function for screenshot

const screenshot = (function() {
  function urlsToAbsolute(nodeList) {
      if (!nodeList.length) {
          return [];
      }
      var attrName = 'href';
      if (nodeList[0].__proto__ === HTMLImageElement.prototype
      || nodeList[0].__proto__ === HTMLScriptElement.prototype) {
          attrName = 'src';
      }
      nodeList = [].map.call(nodeList, function (el, i) {
          var attr = el.getAttribute(attrName);
          if (!attr) {
              return;
          }
          var absURL = /^(https?|data):/i.test(attr);
          if (absURL) {
              return el;
          } else {
              return el;
          }
      });
      return nodeList;
  }
  function addOnPageLoad_() {
      window.addEventListener('DOMContentLoaded', function (e) {
          var scrollX = document.documentElement.dataset.scrollX || 0;
          var scrollY = document.documentElement.dataset.scrollY || 0;
          window.scrollTo(scrollX, scrollY);
      });
  }
  function capturePage(){
    urlsToAbsolute(document.images);
    urlsToAbsolute(document.querySelectorAll("link[rel='stylesheet']"));
    var screenshot = document.documentElement.cloneNode(true);
    var b = document.createElement('base');
    b.href = document.location.protocol + '//' + location.host;
    var head = screenshot.querySelector('head');
    head.insertBefore(b, head.firstChild);
    screenshot.style.pointerEvents = 'none';
    screenshot.style.overflow = 'hidden';
    screenshot.style.webkitUserSelect = 'none';
    screenshot.style.mozUserSelect = 'none';
    screenshot.style.msUserSelect = 'none';
    screenshot.style.oUserSelect = 'none';
    screenshot.style.userSelect = 'none';
    screenshot.dataset.scrollX = window.scrollX;
    screenshot.dataset.scrollY = window.scrollY;
    var script = document.createElement('script');
    script.textContent = '(' + addOnPageLoad_.toString() + ')();';
    screenshot.querySelector('body').appendChild(script);
    var blob = new Blob([screenshot.outerHTML], {
        type: 'text/html'
    });
    return blob;
  }

  return capturePage
})()

Function to async post Blob

function setupAjaxWithCSRFToken() {
  // using jQuery
  var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
  function csrfSafeMethod(method) {
      // these HTTP methods do not require CSRF protection
      return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
  }
  // set csrf header
  $.ajaxSetup({
      beforeSend: function(xhr, settings) {
          if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
              xhr.setRequestHeader("X-CSRFToken", csrftoken);
          }
      }
  });

}

function asyncSubmitBlob( url, blob ) {
  var fd = new FormData();
  fd.append('image', blob);
  $.ajax({
      url: url,
      type: "POST",
      data: fd,
      contentType: false,
      processData: false,
      success: function(response){ console.log(response) },
      error: function(data){ console.log(data) }
  })
}

So to submit a screenshot of the current page:

setupAjaxWithCSRFToken()
const page = window.location.pathname;
const blob_url = "{% url 'my-app:post_blob' 'REPLACE' %}".replace(/REPLACE/,page == '/' ? '' : page)
asyncSubmitBlob( blob_url, screenshot() )

View to store the posted blob image

urls.py

...
from django.urls import include, path
...
app_name='my-app'
url_patterns=[
  ...
  path('post_blob/', views.post_blob, {'page':'/'},name='post_blob'),
  path('post_blob/<page>', views.post_blob,name='post_blob'),
  ...
]

views.py

from .models import PageBlob
...
def post_blob(request, page):
    if request.FILES: # save screenshot of specified page
        try:

            pb = PageBlob.objects.all().filter(page=page))
            if not pb.count():
                pb = PageBlob()
                pb.page = page
                pb.blob = request.FILES['image']
                pb.save()


                return HttpResponse('Blob Submitted')
        except:
            return HttpResponse('[App::my-app]\tError when requesting page_image({page})'.format(page=page))
    else: # return screenshot of requested page
        try:
            # get objects storing screenshot for requested page
            pb = PageBlob.objects.all().filter(page=page)
            # if one exists
            if pb.count():
                pb = pb[0]

                ## this just returns the string literal "blob"
                return HttpResponse(str(pb.blob))


            return HttpResponse('[App::my-app]\tNo blob for {page}'.format(page=page))
        except:
            return HttpResponse('[App::my-app]\tError when trying to retrieve blob for {page}'.format(page=page))
    return HttpResponse('Another response')

models.py

class PageBlob(models.Model):
    page = models.CharField(max_length=500)
    blob = models.TextField(db_column='data', blank=True)

But I can not seem to faithfully capture and retrieve the blob.

Many S.O. questions of storing blobs use the model approach with import base64 to encode and decode the blob. One even recommends using the BinaryField. However, Django's documentation states firmly that BinaryField is not a replacement for the handling of static files.

So how could I achieve this?

S.O. posts I have found helpful to get this far
SumNeuron
  • 4,850
  • 5
  • 39
  • 107
  • It's unclear exactly what in this pipelin you need help fixing. Your blob data seems to be `text/html`, not an image. `var blob = new Blob([screenshot.outerHTML], { type: 'text/html' });` – Håken Lid May 15 '18 at 08:03
  • @HåkenLid well it is the text of the html nodes... – SumNeuron May 15 '18 at 11:17
  • Why use blob for text? You can render the site to and image client side using https://github.com/niklasvh/html2canvas . Note that the resulting screenshot might look different than in the browser. – Håken Lid May 15 '18 at 11:35
  • @HåkenLid when I tried it, it only took screen shot of that which fit within the view of the browser (not overflow) and I would like overflow. Perhaps that has changed... – SumNeuron May 15 '18 at 11:37

0 Answers0