How do I make my Django form save images?

Viewed 229

I created an upload form for users allowing them to create a model object. Everything else works, but the image field doesn't store the loaded images. I use the Crispy FormHelper in the views.py, but HTML source code shows that the elements (enctype, layout_class, field_class etc...) aren't loading. I think this is where the problem is.

Here's what the rendered form looks like:

enter image description here

For context, the form should look like this:

enter image description here

<form  class="form-horizontal" method="post" >
...
</form>

I've walked through the code and the tutorial I used line by line, but I can't find where the problem lies.

views.py

class CarCreate(generic.CreateView):

    model = Car

    slug_field = 'id'
    slug_url_kwarg = 'car_create'

    template_name = 'showroom/car_create.html'
    form_class = CreateCarForm
    success_url = None

    def get_context_data(self, **kwargs):
        data = super(CarCreate, self).get_context_data(**kwargs)
    self.request.POST:
            data['images'] = CarImageFormSet(self.request.POST, 
        self.request.FILES)
        else:
            data['images'] = CarImageFormSet()
        return data

    def form_valid(self, form):
        context = self.get_context_data()
        images = context['images']
        with transaction.atomic():
            form.instance.seller = self.request.user
            self.object = form.save()
            if images.is_valid():
                images.instance = self.object
                images.save()
        return super(CarCreate, self).form_valid(form)

    def get_success_url(self):
        # return reverse_lazy('showroom:cars', kwargs={'slug': self.object.id}) # Throws an error
        return reverse_lazy('showroom:cars')

forms.py

class CreateCarForm(ModelForm):

    class Meta:
        model = Car
        exclude = ['seller', 'id']

    def __init__(self, *args, **kwargs):
        super(CreateCarForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = True
        self.helper.form_class = 'form-horizontal'
        self.helper.form_enctype = 'multipart/'
        self.helper.label_class = 'col-md-3 create-label'
        self.helper.field_class = 'col-md-9'
        self.helper.form_enctype = 'multipart/form-data'
        self.helper.layout = Layout(
            Div(
                ...
                Fieldset('Add Images',
                    Formset('images')),
                HTML("<br>"),
                ButtonHolder(Submit('submit', 'Save')),    
            )
        )

formset.html

{% load crispy_forms_tags %}

<table>
{{ formset.management_form|crispy }}

    {% for form in formset.forms %}
            <tr class="{% cycle 'row1' 'row2' %} formset_row-{{ formset.prefix }}">
                {% for field in form.visible_fields %}
                <td>
                    {# Include the hidden fields in the form #}
                    {% if forloop.first %}
                        {% for hidden in form.hidden_fields %}
                            {{ hidden }}
                        {% endfor %}
                    {% endif %}
                    {{ field.errors.as_ul }}
                    {{ field }}
                </td>
                {% endfor %}
            </tr>
    {% endfor %}

</table>
</br>

{% block scripts %}
{% load static %}
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>    
    <script src="{% static 'js/django-dynamic-formset/jquery.formset.js' %}"></script>
    <script type="text/javascript">
        $('.formset_row-{{ formset.prefix }}').formset({
            addText: 'add another',
            deleteText: 'remove',
            prefix: '{{ formset.prefix }}',
        });
    </script>


{% endblock %}

car_create.html

{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container">
    <div class="card">
        <div class="card-header">
            <h2>Submit a car</h2>
        </div>
        {% csrf_token %}
        <div class="card-body">
            {% crispy form %}
        </div>
      </div>
</div>
{% endblock content %}

Update

I tinkered with the car_create.html, and got the crispy form working. It still doesn't render the FormHelper fields so the images aren't saving.

Looking through the rendered form code, it renders the form-horizontal and other layout features, but it only ignores the enctype. Crispy documentation doesn't mention the form_enctype at all.

Finally, a solution!

I've finally figured out a solution. It's somewhat jury-rigged, but a little duct tape never hurt. See the answer below.

enter image description here

1 Answers

I knew the problem related to the enctype field. I had placed it in my FormHelper, but it was never passed into the form during the actual HTML user session. So I simply added it to the enclosing car_create.html form file like so:

            <form method="POST" enctype="multipart/form-data">
                {% crispy form %}
            </form>

The forms are now nested, but it works! I'm elated.

Related