Question

Using the generic Class Based CreateView I'm trying to upload an image via a ClearableFileInput widget which is the default widget used by inlineformset_factory but this is failing.

The form.save() works just fine in my view, it's the specimage_form.save() that seems to fail. If I print self.request.FILES during the submission it appears as though the selected file is in memory - but stickng a print statement in the SpecImage save() function it's clear this function never gets called.

It is possible to upload an image using the admin site using inlines in which case I see the print statement..

Here's my code - appreciate any advice or guidance. Thanks in advance..

models.py

class Spec(models.Model):
    car = models.ForeignKey('vehicles_dvla_listpoint.AutoLookup')
    owner = models.ForeignKey(User)
    uploaded = models.DateField(default=date.today, editable=False)

    def get_absolute_url(self):
        return reverse('car_create')

    def __unicode__(self):
        return "{0}".format(self.car)

class SpecImage(models.Model):
    def orig_car_id_folder(instance, filename):
        return 'uploads/images/orig/{0}/{1}'.format(instance.car_id, filename)
    def thumb_car_id_folder(instance, filename):
        return 'uploads/images/thumb/{0}/{1}'.format(instance.car_id, filename)

    car = models.ForeignKey(Spec)

    orig_image = models.ImageField(
        upload_to=orig_car_id_folder, 
        verbose_name='Upload Image',
    )

    thumbnail = models.ImageField(
        upload_to=thumb_car_id_folder,
        null=True,
        blank=True,
    )

    def save(self, force_update=False, force_insert=False): 
        print "here ...." # << Don't see this where submitting outside of the admin
        import os
        from PIL import Image
        from cStringIO import StringIO
        from django.core.files.uploadedfile import SimpleUploadedFile
        # Set thumbnail size
        THUMBNAIL_SIZE = (75, 75)
        # Process original image using PIL
        image = Image.open(self.orig_image)           
        # Convert to RGB if necessary
        if image.mode not in ('L', 'RGB'):
            image = image.convert('RGB')
        # PIL already has constraint proportions
        # Also use Image.ANTIALIAS to make the image look better
        image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
        # Save thumbnail - to disk?
        temp_handle = StringIO()
        image.save(temp_handle, 'png')
        temp_handle.seek(0)
        # Save to thumbnail field (in table)
        # Prepare file name - just name & strip .ext 
        name_ext = os.path.splitext(os.path.split(self.orig_image.name)[-1])
        suf = SimpleUploadedFile(name_ext[0],
                temp_handle.read(), content_type='image/png')
        self.thumbnail.save(suf.name+'.png', suf, save=False)
        # Save this photo instance (again)
        super(SpecImage, self).save()

urls.py

urlpatterns = patterns('',
    url(r'^add/$', CarCreate.as_view(), name='car_create'),
    url(r'^thanks/$', TemplateView.as_view(template_name='thanks.html')),
)

forms.py

import autocomplete_light
from django.forms.models import inlineformset_factory

from .models import Spec, SpecImage

class SpecForm(autocomplete_light.ModelForm):
    class Meta:
        model = Spec

SpecImageFormSet = inlineformset_factory(Spec, SpecImage, extra=1)

views.py

class CarCreate(CreateView):
    template_name = 'spec_form_inlines.html'
    model = Spec
    form_class = SpecForm
    success_url = '/car/thanks/'

    def form_valid(self, form):
        context = self.get_context_data()
        specimage_form = context['specimage_form']
        if specimage_form.is_valid():
            self.object = form.save()
            specimage_form.instance = self.object
#            specimage_form.instance = self.request.FILES
            specimage_form.save()
            return HttpResponseRedirect('/car/thanks/')
        else:
            return self.render_to_response(self.get_context_data(form=form))

    def form_invalid(self, form):
        return self.render_to_response(self.get_context_data(form=form))

    def get_context_data(self, **kwargs):
        context = super(CarCreate, self).get_context_data(**kwargs)
        if self.request.POST:
            context['specimage_form'] = SpecImageFormSet(self.request.POST)
        else:
            context['specimage_form'] = SpecImageFormSet()
    return context

spec_form_inlines.html

<html>
<body>

<h1>Add Book</h1>

<form enctype="multipart/form-data" method="post" action="">
    {% csrf_token %}

    {{ form.as_p }}

    {{ specimage_form.management_form }}
    {% for form in specimage_form.forms %}
        {{ form.as_p }}
    {% endfor %}

    <input type="submit" value="Add Car" class="submit"/>
</form>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.js" type="text/javascript"></script>
{% include 'autocomplete_light/static.html' %}

</body>
</html>

I'm pretty new with Class Based Views I managed to acheive this with function views last time but they seem an old way of doing things and sometimes it's not an obvious transition. Any advice or guidance would be very welcome.

Thanks.

Was it helpful?

Solution

So this appears to be a simple enough fix but it took me a while to work out the problem by checking online examples and reading the Django docs so I'm leaving it here rather than taking the question down.

My initial code was based on this blog post.

Unless I'm missing something, despite being an example that includes an image upload, what it appears to lack is a reference to request.FILES to bind the images. So the simple change made which now appears to work is to change this line:

if self.request.POST:
  context['specimage_form'] = SpecImageFormSet(self.request.POST)

to this

if self.request.POST:
  context['specimage_form'] = SpecImageFormSet(self.request.POST, self.request.FILES)

An additional example which uses render_to_response instead of setting a context element can be found here

Similarly an additional reference to request.FILES needs to be made for whichever formset has an ImageField.

Both these approaches seem to work ok.

More information about binding images to a form is in the Django Docs.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top