Saving to aws S3 with inline formsets

Hey,

So I have this prototype where I save an object from a regular form view to my S3 bucket using S3Boto3:

class UploadFileView(FormView):
    form_class = UploadFileForm
    template_name = 'uploader/upload_cls.html'
    success_url = 'myapp:index'
    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST, request.FILES)
        if form.is_valid():
            file = request.FILES["file"]
            media_storage = MediaStorage()
            saved_file = media_storage.save(f"testfile-{datetime.now()}", file)
            return redirect(self.success_url)
        return render(request, self.template_name, {'form':form})

class UploadFileForm(forms.Form):
    file = forms.FileField()

class MediaStorage(S3Boto3Storage):
    location=settings.AWS_MEDIA_LOCATION

That works fine and when picking a file from the uploader view, it gets saved to my S3 bucket. So I know that the whole settings.py / AWS setup is good, because I can upload & retrieve my documents this way.

Now I want to do the same with an inlineformset and I’m a little stuck.

The issue that I have is that the inlineformset itself works. However, the file itself is never saved to the S3 bucket. I’m guessing I should override a save() method somewhere to make that happen… But which one? The form’s? The model’s? Or iterate directly in the view’s post()? Here’s rough idea of the relevent code:


DocumentFormSet = inlineformset_factory(Produit, DocumentProduit,
                                        form=DocumentsProduitForm,
                                        extra=0,
                                        can_delete=True)


class DocumentProduit(models.Model):
	fk_produit = models.ForeignKey(Produit, on_delete=models.SET_DEFAULT,related_name="documents", null=False, blank=False, default=0)
	document = models.FileField(verbose_name="Document Produit", blank=True, null=True)
	filename = models.CharField(max_length=50, verbose_name="Nom du document", null=True, blank=True)

class DocumentsProduitForm(ModelForm):
    class Meta:
        model = DocumentProduit
        fields = ["document", "filename"]

Saving the formsets is actually done via a mixin that I add to the cbv:

class FormFormsetValidationMixin:
    def form_valid(self, form, formsets=[]):
        self.object = form.save()
        for fmset in formsets:
            fmset.instance = self.object
            fmset.save()
        return self.object

So what currently happens is that my inlines are correctly saved (other formsets also use this same pipeline). I get no errors and the succes_url is rendered, however the S3 shows nothing.

Basically what I would like to achieve is a smart way to ensure my object is saved, ideally without iterating directly in the view’s post() methods to mess around with individual request.FILES objects. Any smart ideas?

I don’t see where or how you’re building the formset from the post data, but I’m guessing you may be missing the request.FILES reference for it.

1 Like

Ah that would be in the view, here:

    def post(self, request, *args, **kwargs):
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        fourprod_formset = FourProdFormSet(self.request.POST, instance=self.object)
        cases_formset = CaseFormSet(self.request.POST, instance=self.object)
        formset_kits = ProdkitFormSet(self.request.POST, instance=self.object)
        formset_document = DocumentFormSet(self.request.POST, instance=self.object)
        if (form.is_valid() and fourprod_formset.is_valid() and cases_formset.is_valid() and formset_kits.is_valid() and formset_document.is_valid()):
            return self.form_valid(form, fourprod_formset, extra_formset=[cases_formset, formset_kits, formset_document])
        else:
            return self.form_invalid(form, fourprod_formset, cases_formset, formset_kits, formset_document)

Appears to me like you’re missing the reference to request.FILES here.

1 Like

Okay, something along the lines of:


formset_document = DocumentFormSet(self.request.POST, files=self.request.FILES, instance=self.object)

Yup, binding request.FILES to the form (in addition to request.POST data) for the win.

There’s an example at Creating forms from models | Django documentation | Django