Search code examples
djangodjango-formsdjango-class-based-viewsinline-formset

Using formsets for my fileupload does not work when doing an update class based view only on create in django


I have used as many examples online as I could cobble together in an attempt to get my two simple models to have the ability to do an inline formset allowing me to add many files to a technial drawing. This is not working, I can only add one file for the Create and the update only updates the Technical_Entry model, not a file...which in of itself acts funny. The UI ona create shows one spot to add a file, then save the entire record and its child record. That works. The update, the UI shows the file that was saved earlier..(great!) but then has two more 'choose file' slots (random?) and adding a file to those does nothing when the save is clicked. It doesn't remove the file previously added in the create, but it also does not save the new file added to the now three slots (one used, two free). So update does not work for the files for some reason.

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, max_num=15)



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 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)


class TechEntryUpdateView(LoginRequiredMixin, UpdateView):                                                                                             

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

    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)    
        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(TechEntryUpdateView, 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 %}

And what the UI looks like on edit...

enter image description here


Solution

  • 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) #removed
                                                                                             
        def get_context_data(self, **kwargs):                
            context = super(TechEntryUpdateView, self).get_context_data(**kwargs)
            #self.object = self.get_object() #removed
    
            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)
    
            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)
            return super(TechEntryUpdateView, self).form_valid(form) #replaced old one
    

    UPDATE 1. in order to be able to add mulitple files when creating,

    TechFileFormSet  = inlineformset_factory(Technical_Entry, Technical_Entry_Files, form=FileUploadForm, extra=4, max_num=4)
    
    # note I changed to extra=4, if you always want to have only 4 files, then also change to max_num=4
    

    2. When update, the reason why the update is not working even after modifying the views was because you were not passing hidden fields. You were not passing the ids of the files, therefore, your formsets were not passing .is_valid(), hence, no update. Add for loop about hidden fields like below.

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

    #Note the for loop I add about hidden fields.