Using a Django FileField in an inline formset

2019-07-24 23:34发布

having issues getting the file to upload in my app. A User submits a report and can add attachments (through a foreign key relationship). I've got the inline form showing up and will work if I leave it blank, but when I try to upload a file then submit the form I get a 500 error. The base report is made, but the attachment that's getting inlined doesn't get saved.

forms.py

class ReportForm(forms.ModelForm):
accidentDate = forms.DateField(widget=SelectDateWidget(
                               empty_label=("Choose Year",
                                             "Choose Month",
                                             "Choose Day"),
                               ),
                               label=(u'Accident Date'),
                               initial=timezone.now())
claimNumber = forms.CharField(max_length=50,
                              label=(u'Claim Number'),
                              required=True)
damageEstimate = forms.DecimalField(max_digits=6, decimal_places=2,
                                    label=(u'Damage Estimate'))


description = forms.CharField(widget=forms.Textarea(attrs={'rows': 5,
                                                           'cols': 80}
                                                    ),
                              label=(u'Description'),
                              required=True)
drivable = forms.BooleanField(label=(u'Drivable'), 
                              required=False)
receivedCheck = forms.CharField(max_length=30, label=(u'Who Received Check'))
reported = forms.BooleanField(label=(u'Reported to Insurance Company'), 
                              required=False)
reportedDate = forms.DateField(widget=SelectDateWidget(
                                empty_label=("Choose Year",
                                             "Choose Month",
                                             "Choose Day"),
                            ),
                           initial=timezone.now(),
                           label=(u'Date Reported to Insurance'))
repairsScheduled = forms.DateField(widget=SelectDateWidget(
                                empty_label=("Choose Year",
                                             "Choose Month",
                                             "Choose Day"),
                            ),
                           initial=timezone.now(),
                           label=(u'Scheduled Repair Date'))
repairsCompleted = forms.DateField(widget=SelectDateWidget(
                                empty_label=("Choose Year",
                                             "Choose Month",
                                             "Choose Day"),
                            ),
                           initial=timezone.now(),
                           label=(u'Repair Completion Date'))
repairsPaid = forms.BooleanField(label=(u'Repairs Paid'), 
                              required=False)
subrogationReceived = forms.BooleanField(label=(u'Subrogation Received'), 
                              required=False)
subrogationDate = forms.DateField(widget=SelectDateWidget(
                                empty_label=("Choose Year",
                                             "Choose Month",
                                             "Choose Day"),
                            ),
                           initial=timezone.now(),
                           label=('Subrogation Date'))

class Meta:
    model = Report
    exclude = ('driver',)


ReportAttachmentFormSet = inlineformset_factory(Report,  # parent form
                                                  ReportAttachment,  # inline-form
                                                  fields=['title', 'attachment'], # inline-form fields
                                                  # labels for the fields
                                                  labels={
                                                        'title': (u'Attachment Name'),
                                                        'attachment': (u'File'),
                                                  },
                                                  # help texts for the fields
                                                  help_texts={
                                                        'title': None,
                                                        'attachment': None,
                                                  },
                                                  # set to false because cant' delete an non-exsitant instance
                                                  can_delete=False,
                                                  # how many inline-forms are sent to the template by default
                                                  extra=1)

relevant views.py CreateView

class ReportCreateView(CreateView):
model = Report
form_class = ReportForm
object = None

def get(self, request, *args, **kwargs):
    """
    Handles GET requests and instantiates blank versions of the form
    and its inline formsets.
    """
    self.object = None
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    report_attachment_form = ReportAttachmentFormSet()
    return self.render_to_response(
              self.get_context_data(form=form,
                                    report_attachment_form=report_attachment_form,
                                    )
                                 )

def post(self, request, *args, **kwargs):
    """
    Handles POST requests, instantiating a form instance and its inline
    formsets with the passed POST variables and then checking them for
    validity.
    """
    self.object = None
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    report_attachment_form = ReportAttachmentFormSet(self.request.POST, self.request.FILES)
    if form.is_valid() and report_attachment_form.is_valid():
        return self.form_valid(form, report_attachment_form)
    else:
        return self.form_invalid(form, report_attachment_form)

def form_valid(self, form, report_attachment_form):
    """
    Called if all forms are valid. Creates Report instance along with the
    associated ReportAttachment instances then redirects to success url
    Args:
        form: Report Form
        report_attachment_form: Report attachment Form

    Returns: an HttpResponse to success url

    """
    self.object = form.save(commit=False)
    # pre-processing for Report instance here...

    self.object.driver = Profile.objects.get(user=self.request.user)
    self.object.save()

    # saving ReportAttachment Instances
    report_attachments = report_attachment_form.save(commit=False)
    for ra in report_attachments:
        #  change the ReportAttachment instance values here
        #  ra.some_field = some_value
        ra.save()

    return HttpResponseRedirect(self.get_success_url())

def form_invalid(self, form, report_attachment_form):
    """
    Called if a form is invalid. Re-renders the context data with the
    data-filled forms and errors.

    Args:
        form: Report Form
    report_attachment_form: Report Attachment Form
    """
    return self.render_to_response(
             self.get_context_data(form=form,
                                   report_attachment_form=report_attachment_form
                                   )
    )

report_form.html

{% block body %}
<p>
<form action="" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <fieldset>
            {{ form.non_field_errors }}
            {% for field in form %}
            <div class="row">
                <div class="col-md-3">{% bootstrap_label field.label %}</div>
                <div class="col-md-8">{% bootstrap_field field show_label=False %}</div>
            </div>
            {% endfor %}
    </fieldset>
    <fieldset>
            <legend>Attachments</legend>
            {{ report_attachment_form.management_form }}
            {{ report_attachment_form.non_form_errors }}
                            {% for form in report_attachment_form %}
                    <div class="inline {{ report_attachment_form.prefix }}">
                    {% for field in form.visible_fields %}
                            <div class="row">
                                    <div class="col-md-3">{% bootstrap_label field.label %}</div>
                                    <div class="col-md-8">{% bootstrap_field field show_label=False %}</div>
                            </div>
                    {% endfor %}
                    </div>
            {% endfor %}
    </fieldset>
    <div class="row">
            <div class="col-md-1">
                    <input type="submit" class="btn btn-groppus-primary bordered" value="Submit" />
            </div>
    </div>
    </form>
</p>
{% endblock %}

{% block scripts %}
    <script src="{% static 'formset/jquery.formset.js' %}"></script>
    <script type="text/javascript">
            $(function() {
                    $(".inline.{{ report_attachment_form.prefix }}").formset({
                            prefix: "{{ report_attachment_form.prefix }}", // The form prefix for your django formset
                            addCssClass: "btn btn-block btn-primary bordered inline-form-add", // CSS class applied to the add link
                            deleteCssClass: "btn btn-block btn-primary bordered", // CSS class applied to the delete link
                            addText: 'Add another attachment', // Text for the add link
                            deleteText: 'Remove attachment above', // Text for the delete link
                            formCssClass: 'inline-form' // CSS class applied to each form in a formset
                    })
            });
    </script>
{% endblock %}

Having a hard time figuring this one out since it doesn't give me any sort of traceback to deal with, I included the enc-type in the form, and I'm passing request.FILES in the views.py file. I've got file uploads working in normal forms, but inline is proving to be trouble.

Let me know if you need me to clarify anything, any help is appreciated :)

UPDATE: Got a traceback by making the CreateView csrf exempt using @method_decorator(csrf_exempt, name='dispatch')

which gives me a ValueError: save() prohibited to prevent data loss due to unsaved related object 'report'. From the trace I can see that my file is in fact making it into memory, and the issue is here:traceback

Will continue updating as I progress, seems like this method of saving FK objects worked flawlessly pre-1.8 so if I'm lucky it's a quick fix to get the object to save right.

1条回答
smile是对你的礼貌
2楼-- · 2019-07-25 00:20

All I needed to do was pass the instance of the Report Form I was working with along to the formset, nice and easy. So in my CreateView's post method, I changed the declaration of report_attachment_form to report_attachment_form = ReportAttachmentFormSet(self.request.POST, self.request.FILES, instance=form.instance) and we're good as gold.

查看更多
登录 后发表回答