0

I'm currently building a web app using Django, and I've implemented what might be termed a "signature field". Essentially, it uses the jQuery UI plugin at http://keith-wood.name/signature.html to implement a signature capture field, which when the form is submitted, is sent via AJAX POST to the server as a base 64 encoded string, then converted to an image on the server.

Now, I've refactored the client-side aspect of it into a reusable widget called a SignatureInput, and I've also implemented the server-side aspect of it in an individual view. However, I'd really like to make it the field generic so that I can use it with the existing generic views, and that's where I'm struggling - it may just be a wood-for-the-trees moment, but I can't find anything in the Django documentation that covers situations like this.

The SignatureField itself extends ImageField, and in the admin interface I want to stick with the existing image upload dialogue, so I don't want to override it at model level. Instead, when it's submitted on the front end only, I want it to be pulled from request.POST, processed and added to request.FILES.

I defined the following widget for it in widgets.py:

class SignatureInput(ClearableFileInput):
def render(self, name, value, attrs=None):
    try:
        id = self.attrs['id']
    except KeyError:
        id = None
    if id: 
        id_html = ' id="%s"' % (id)
    else:
        id_html = ''

    # Value is set - show existing image and field to change it
    if value:
        html = """ 
    <div class="signatureContainer">
    <br /><img class="existingSignature" alt="Signature" title="Signature" src="/media/%s"></img>
    <div data-role="collapsible">
        <h4>New signature</h4>
        <br /><div class="signature"%s></div><br /><br />
        <a data-role="button" class="signatureClear">Clear signature</a>
    </div>

        """ % (value, id_html)
    else:
        html = """
    <div class="signatureContainer">
    <br /><div class="signature"%s></div><br /><br />
    <a data-role="button" class="signatureClear">Clear signature</a>
    </div>
        """ % (id_html)
    return html

The SignatureInput field is used for all the front-end forms that require a signature field, and are submitted as base 64 encoded strings using jQuery AJAX.

Here's how I've implemented the server-side code for processing the images in my existing views:

# Create your views here.
import cStringIO as StringIO
from xhtml2pdf import pisa
from django.http import HttpResponse, HttpResponseRedirect
from django.template.loader import get_template
from django.template.context import Context
from django.shortcuts import render, render_to_response
from my_app.models import *
from my_app.forms import JobForm
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.conf import settings
import base64
import uuid
import sys 


def decodeImage(image):
    # Remove encoding data
    image = image.replace('data:image/png;base64,', '') 

    # Decode image
    image = base64.b64decode(image)

    # Return it
    return image


def addJob(request):
    # If request is a GET request, just render it
    if request.method == 'GET':
        return render_to_response('my_app/job_form.html', {'form': JobForm})
    elif request.method == 'POST':
        # If request is a POST request, process it
        # Get the signatures
        if request.POST.get(u'signature'):
            signature = StringIO.StringIO(decodeImage(request.POST[u'signature']))

            # Construct the File objects
            signatureOutput = InMemoryUploadedFile(signature,
                    field_name='signature',
                    name=settings.MEDIA_ROOT + str(int(uuid.uuid1()))[:10] + '.png',
                    content_type="image/png",
                    size=sys.getsizeof(signature),
                    charset=None)
            request.FILES[u'signature'] = signatureOutput

        # Validate the data
        jobform = JobForm(request.POST, request.FILES)

        if jobform.is_valid():
            # Save the form as a new instance of Job
            jobform.save()

            # Show the success page
            return HttpResponseRedirect('/forms/jobs/add/success/')
        else:
            # Return an error
            return render(request, 'my_app/job_form.html', {
                'form': jobform,
            })

How might I go about carrying out the processing currently done in the addJob view function earlier in the submission process, so that I can use the signature fields with a generic view? Basically I need to be able to process the string into an image and move it from request.POST to request.FILES before it hits the view. The middleware seems to be the wrong place to do that.

4
  • the widget handles just the html repr of the field. you should override your jobform save() method (or, specifically your <field_name>_save() one) Commented Jun 20, 2013 at 11:35
  • @SamueleMattiuzzo JobForm in this case is a ModelForm. Does this mean that the way to do it is to override the default save() method for the specific field, as opposed to the form or model (in this case I specifically don't want to do it in the model because it still needs to work the regular way in the admin) Commented Jun 20, 2013 at 11:46
  • I said jobform, not the model associated with it :) You can override the form' save() method or just the field save() method, it's up to you. You just can't do it in the widget Commented Jun 20, 2013 at 12:35
  • @SamueleMattiuzzo Ok, just clarifying it in my head. Cheers, I think that's given me enough to go on Commented Jun 20, 2013 at 12:39

1 Answer 1

1

You could try to do this in your custom widget using the value_from_datadict method. From the Django docstring of this function:

Given a dictionary of data and this widget's name, returns the value of this widget. Returns None if it's not provided.

This method is supposed to return a value based on POST data dictionary and the name of the widget. So in your case you can read your base64 string from POST and convert it into an image file data. This data needs to correspond to what is expected by the ImageField.

This question uses the function I mentioned:
Django subclassing multiwidget - reconstructing date on post using custom multiwidget

Also check the Django code for this function in here:
https://github.com/django/django/blob/master/django/forms/widgets.py

Sign up to request clarification or add additional context in comments.

1 Comment

I am glad it worked. I learned about this method because of your question so thank you :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.