Reputation: 9476
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.
Upvotes: 0
Views: 1062
Reputation: 3760
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
Upvotes: 1