Search code examples
pythondjangodjango-formsformsetinline-formset

Using formsets to upload many files to a record, it isn't quite working


I have used as many examples online as I could cobble together in an attempt to get my two simple models:

class Technical_Entry(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    ema = models.ForeignKey(EMA, on_delete=models.CASCADE)
    system = models.ForeignKey('System', on_delete=models.CASCADE) # are SYSTEMS RELATED TO SUBSYSTEMS OR JUST TWO GROUPS?  
    sub_system = models.ForeignKey(SubSystem, on_delete=models.CASCADE)

    drawing_number = models.CharField(max_length=200)
    drawing_title = models.CharField(max_length=255)
    engineer = models.CharField(max_length=200)
    vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE)

    date_drawn = models.DateField()
    ab = models.BooleanField()



class Technical_Entry_Files(models.Model):
    tech_entry = models.ForeignKey(Technical_Entry, on_delete=models.CASCADE)
    file = models.FileField(upload_to='techdb/files/')

    def __str__(self):
        return self.tech_entry.drawing_number

To upload using a formset. While the page 'displays' mostly correctly, it flat out does not create the record in the Technical_Entry_Files model.

Relevant forms.py:

class FileUploadForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(FileUploadForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()  
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-lg-4'
        self.helper.field_class = 'col-lg-8'
    
    class Meta:
        model = Technical_Entry_Files
        fields = ('file',)


TechFileFormSet  = inlineformset_factory(Technical_Entry, Technical_Entry_Files, form=FileUploadForm, extra=1)



class Technical_EntryForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(Technical_EntryForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()  
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-lg-4'
        self.helper.field_class = 'col-lg-8'
        self.helper.add_input(Submit('submit', 'Submit'))

    class Meta:
        model = Technical_Entry
        fields = ('category', 'ema', 'system', 'sub_system', 'drawing_number', 'drawing_title', 'engineer', 'vendor', 'date_drawn', 'ab')


        widgets = {
            'date_drawn':DateInput(attrs={
            'class':'datepicker form-control',        
            'id' : 'datetimepicker2',
            'tabindex' : '1',
            'placeholder' : 'MM/DD/YYYY hh:mm',          
            'autocomplete':'off',
            }, format='%m/%d/%Y'),

            'system' : Select(attrs={'tabindex':'2'}),
        }

Relevant views.py:

class TechEntryUpdateView(LoginRequiredMixin, UpdateView):                                                                                             

    model = Technical_Entry
    form_class = Technical_EntryForm
    template_name = 'techdb/tech_entry_form.html'
    success_url = '/'
                                                                                             
    log_entry_class = Technical_EntryForm(Technical_Entry)
                                                                                         

    def get_context_data(self, **kwargs):
        context = super(TechEntryUpdateView, self).get_context_data(**kwargs)

        if self.request.POST:
            context["file_upload"] = TechFileFormSet(self.request.POST, self.request.FILES,instance=self.object)
        else:
            context["file_upload"] = TechFileFormSet(instance=self.object)

#        entry = context['object'] 
 #       context['entry_id'] = entry.id
  #      theEntry = Technical_Entry.objects.get(pk=entry.id) 
#        entry_form = Technical_EntryForm(instance=theEntry)
#        context['entry_form'] = entry_form         
        return context

    def form_valid(self, form):
        context = self.get_context_data()
        file_upload = context["file_upload"]
        self.object = form.save()
        if file_upload.is_valid():
            file_upload.instance =self.object
            file_upload.save()
        return super().form_valid(form)

class TechEntryCreateView(LoginRequiredMixin, CreateView):
    print ("we are here")
    model = Technical_Entry
    form_class = Technical_EntryForm
    template_name = 'techdb/tech_entry_form.html'
    print(template_name)
    success_url = '/techentry_add'

    def get_context_data(self, **kwargs):
        data = super(TechEntryCreateView, self).get_context_data(**kwargs)
        if self.request.POST:
            data['file_upload'] = TechFileFormSet(self.request.POST, self.request.FILES)
        else:
            data['file_upload'] = TechFileFormSet()
        return data


    def form_valid(self, form):
        context =self.get_context_data()
        file_upload = context['file_upload']
        with transaction.atomic():
            self.object = form.save()

            if file_upload.is_valid():
                file_upload.instance =self.object
                file_upload.save()
        return super(TechEntryCreateView, self).form_valid(form)

and the tech_entry_form.html:

{% extends 'base.html' %}


{% load static %}

{% block page-js %}

<script>
    $('.link-formset').formset({
        addText: 'add file',
        deleteText: 'remove',        
    });
</script>
{% endblock %}

{% block content %}

<main role="main" class="container">

      <div class="starter-template">
        <h1>New Tech Entry</h1>
      </div>

    <h2> Details of Technical Entry </h2>
     <div class="row">
      <div class="col-sm">
       <form action="" method="post" enctype="multipart/form-data">{% csrf_token %}
                {{ form.as_p }}

                <h2> Files </h2>
                {{ file_upload.management_form }}
                {% for upload_form in file_upload.forms %}
                  <div class="link-formset">
                    {{ upload_form.file }}        
                  </div>
                 {% endfor %}

               
                <input type="submit" value="Save"/><a href="{% url 'tech_database:index' %}">back to the list</a>
            </form>
        </div>
       </div>
     </div>
    </main><!-- /.container -->


{% endblock %}

It will save the entry but not the file that was uploaded, and I see no errors either.

There is no techdb/files folder (maybe I have to create that?) but it certainly does not fail anywhere...creates the Technical_Entry record, but not the Technical_Entry_Files record nor is there any thing on disk added.

Additionally and this is really the biggest piece...it only allows me to upload one file even though the tables should allow many files to one tech entry? (Maybe I need to kick some java script).

All the examples I have found either do not use model based forms, class based views or just seemingly overkill. I just need these simple models to let me upload many files to a technical entry. I thought I was close but fear I am not anywhere near getting this to work :/


Solution

  • 1. One thing I noticed is your <form> does not have

    <form action="" method="post" enctype="multipart/form-data">
    

    Here 1 says:

    No characters are encoded. This value is required when you are using forms that have a file upload control

    2. another thing seems missing is: request.FILES

    data['file_upload'] = TechFileFormSet(self.request.POST, self.request.FILES)
    

    Not sure if the above is how you should incorporate in your view.

    The following is from doc: about inlineformset in a function-based view.

    if request.method == 'POST':
            formset = ArticleFormSet(request.POST, request.FILES)
    

    Another part of the doc:about File Uploads says:

    When Django handles a file upload, the file data ends up placed in request.FILES