Reputation: 145
I'm trying to allow users of my site to upload image files. The way I want to accomplish this is to use jQuery to upload the file to the server, parse the file's EXIF data if it has any data (namely, GeoLocation data). If the image file does not have the GeoLocation data, I want to ask the user for the data, then show a confirmation page. Otherwise, if I have the data, show the confirmation page with the data. Once the user confirms the data is correct, then the data is added to a Django model. The problems I am currently having are displaying the image on a confirmation page and having the form be submitted via jQuery and not as a usual POST. As you will see in the code, I have methods to get the data out that works when the file path is known, but currently I'm getting the error: coercing to Unicode: need string or buffer, InMemoryUploadedFile found. Here's code for reference (sorry, its a bit long):
forms.py:
from django import forms
from photos.models import PhotoMessages
class PhotoUploadForm(forms.Form):
"""
The form used to upload a user's photo
"""
photo = forms.ImageField(label='Photo to upload', help_text='Must be a valid image', error_messages={'required': 'A photo is required'})
legalAgreement = forms.BooleanField(label='', help_text=PhotoMessages.objects.filter(tag='Legal Agreement')[0].message, error_messages={'required': PhotoMessages.objects.filter(tag='Legal Agreement Error')[0].message})
views.py:
from django.shortcuts import render
from django.http import HttpResponseRedirect
from photos.forms import PhotoUploadForm
from photos.models import PhotoMessages
from photos.utils import readexif, get_client_ip
from datetime import datetime, timedelta
def uploadPhoto(request):
"""
Creates the initial form for uploading a file and handles form errors and proper submission
"""
if request.user.is_authenticated():
if request.method == 'POST': # If the form has been submitted...
form = PhotoUploadForm(request.POST, request.FILES) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# First, get the image location
image = form.cleaned_data.get('photo')
# TODO the above is a InMemoryUploadedFile
# need to figure out how to use the data in that file, possibly by
# saving its contents to a directory
# then, get the exif data from the file
dat = readexif(image)
# uploaded time
uploaded = datetime.now()
# get the IP address from where the file was uploaded
ip = get_client_ip(request)
# get the user that uploaded the file
user = request.user
# if we have the geolocation data, show the user a confirmation page
if 'lat' in dat:
# we have location info
return render(request, 'confirm.html', {
'img': image,
'lat': dat['lat'],
'lon': dat['lon'],
'taken': dat['taken'],
'uploaded': uploaded,
'ip': ip,
'user': user,
})
# else:
# we need to request the location info
else:
form = PhotoUploadForm() # An unbound form
return render(request, 'uploadPhoto.html', {
'form': form,
})
else:
return render(request, 'notLoggedIn.html', {
'mesg': PhotoMessages.objects.filter(tag='User not logged in')[0].message,
})
utils.py (utility functions for getting EXIF and other data):
import exifread
from datetime import datetime
import dateutil.parser
# DateTime tags for when the image was taken
DT_TAGS = ["Image DateTime", "EXIF DateTimeOriginal", "EXIF DateTimeDigitized", "DateTime"]
def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def readexif(f):
"""
Method to read in exif data from an image
returns a dictionary of the EXIF data found
"""
ret = {}
dt_value = None
f.open('rb')
ret['processed'] = False
try:
# read the file
tags = exifread.process_file(f)
ret['processed'] = True
# handle time image taken
for dt_tag in DT_TAGS:
try:
dt_value = "%s" % tags[dt_tag]
except:
continue
if dt_value:
ret['taken'] = exif_info2time(dt_value)
# GeoLocation Data
longitude = "%s" % tags['GPS GPSLongitude']
longitude = longitude.strip('[]')
latitude = "%s" % tags['GPS GPSLatitude']
latitude = latitude.strip('[]')
ret['lon'] = deg_min_sec_to_deg(longitude.split(','))
ret['lat'] = deg_min_sec_to_deg(latitude.split(','))
longRef = "%s" % tags['GPS GPSLongitudeRef']
latRef = "%s" % tags['GPS GPSLatitudeRef']
if longRef == 'W':
ret['lon'] = -ret['lon']
if latRef == 'S':
ret['lat'] = -ret['lat']
finally:
f.close()
return ret
def exif_info2time(ts):
return dateutil.parser.parse(ts)
def deg_min_sec_to_deg2(deg, minutes, sec):
return deg + minutes*(1.0/60) + sec * (1.0/3600)
def deg_min_sec_to_deg(arr):
if "/" in arr[0]:
degArr = arr[0].split("/")
deg = float(degArr[0])/float(degArr[1])
else:
deg = float(arr[0])
if "/" in arr[1]:
minArr = arr[1].split("/")
minutes = float(minutesArr[0])/float(minutesArr[1])
else:
minutes = float(arr[1])
if "/" in arr[2]:
secArr = arr[2].split("/")
sec = float(secArr[0])/float(secArr[1])
else:
sec = float(arr[2])
return deg_min_sec_to_deg2(deg, minutes, sec)
upload.js (currently this is commented out):
$( "#photoUpload" ).submit(function( event ) {
event.preventDefault();
$("#myModalContent").html('Loading...');
// console.log(event);
$.post(event.target.action, $( "#photoUpload" ).serialize(), function(data) {
$("#myModalContent").html(data);
})
});
Any help would be greatly appreciated. UPDATE 1/3 - I'm no longer having the problem of getting the data out of the file. Now, I just need to display the image with the obtained information on a page requesting additional information and then store the image and information received into my model to store into a database.
Upvotes: 0
Views: 1401
Reputation: 6725
form.cleaned_data['photo']
is not a filename, but an InMemoryUploadedFile
. Small files, by default smaller than 2.5MB, don't even have a file name; they are only held in memory. InMemoryUploadedFile
is a subclass of File
, so instead of f = open(fn, 'rb')
, you could do:
def readexif(f):
f.open('rb')
tags = exifread.process_file(f)
# ... do more stuff with tags
and pass the File
from your form directly, just like you are currently doing:
# ...
dat = readexif(image)
# ...
That should at least correct the error you are currently getting.
Upvotes: 1