Search code examples
djangotemplatesdjango-templatesmany-to-many

Using the "through " table in a Django template


I'm really new to Django and I've already tried to find a solution without success. I have these three models:

class Items(models.Model):
    ID = models.AutoField(primary_key=True)
    name = models.TextField()
    price = MoneyField(blank=True, null=True, decimal_places=2, max_digits=8, default_currency='EUR')
    def __str__(self):
        return self.name

class Interventions(models.Model):
    ID = models.AutoField(primary_key=True)
    date = models.DateField()
    time = models.TimeField(blank=True, null=True) 
    description = models.TextField()
    ID_items = models.ManyToManyField(Items, through="Details")
    
class Details(models.Model):
    ID = models.AutoField(primary_key=True)
    ID_item = models.ForeignKey(Items, on_delete = models.CASCADE)
    ID_intervention = models.ForeignKey(Interventions, on_delete = models.CASCADE)
    quantity = models.IntegerField(null=True, blank=True)
    def __str__(self):
        return str(self.ID_intervention)

I would like to be able to use the "quantity" field of the Details table, directly in the "intervention.html" template.

intervention.html

{% extends 'base.html'%}
{% load static %}
{% block content %}
    <h1>INSERT an INTERVENTION</h1> 
    <br>

    <form action="" method="post">
      {% csrf_token %}
      <div class="grid-container">
        <h3>Date</h3><div>{{ form.date }}</div>
        <h3>Time</h3><div>{{ form.time }}</div>
        <h3>Description</h3><div>{{ form.description }}</div>
        <h3>Item/Items</h3><div class="overflow_scroll">{{ form.ID_items}}</div>
      </div>
      <br>
      <input type="submit" value="Save">
    </form>[![example][1]][1]
    </div>

{% endblock %}

Unfortunately, the "intervention.html" template is formed by the "InterventionForm" ModelForm contained in the "forms.py" file. And so I don't know how to proceed. The result I'd like to get would be something like in the image.

Thank you all for every little help.


Solution

  • After several tries, I finally arrived at the solution.

    Basically I changed the structure of Models.py, Forms.py and Views.py.

    After which the rest is simple, in fact it is sufficient to adapt the html sheet of the interventions on which the "Intervention" view works and add some Javascript code to duplicate the formset to insert.

    Models.py

    class Articles(models.Model):
         ID = models.AutoField(primary_key=True)
         item_description = models.TextField()
         class Meta:
            ordering = ('item_description',)
    
    class Interventions(models.Model):
         ID = models.AutoField(primary_key=True)
         start_date = models.DateField()
         description = models.TextField()
         Item_ID = models.ManyToManyField(Items, through="ItemDetail", blank=True)
        
    class ItemDetail(models.Model):
         ID = models.AutoField(primary_key=True)
         article_id = models.ForeignKey(articles, on_delete = models.CASCADE)
         Intervention_ID = models.ForeignKey(Interventions, on_delete = models.CASCADE)
         quantity = models.IntegerField(null=True, blank=True, default='1')
    

    Forms.py

    class FormIntervention(forms.ModelForm):
         class Meta:
             model = Interventions
             exclude = ['ID', 'Item_ID']
             fields = [ 'start_date',
                         'intervention_time',
                         'description',
                     ]
    
    class FormDetail(forms.ModelForm):
         class Meta:
             model = ItemDetail
             exclude = ['ID']
             fields = [ 'Article_ID',
                         'Intervention_ID',
                         'amount']
    
    class ArticleForm(forms.ModelForm):
         class Meta:
             model = Articles
             fields = '__all__'
             widgets = {'item_description': TextInput(),}
    
    #------------------------------------------------------------------------------------------------
    FormSetDetail = inlineformset_factory(
         Interventions, DetailArticles,
         form = FormDetail,
         extra=1
    )
    

    Views.py

    @login_required
    def intervention(request):
         if request.method == 'POST':
             formIntervention = FormIntervention(request.POST or None, request.FILES or None)
             formsetArticles = FormSetDetail(request.POST or None, request.FILES or None, prefix='child_formset')
             if formIntervention.is_valid() and formsetArticles.is_valid():
                 formint = formIntervention.save()
                 formsetItems.instance = formint
                 formsetArticles.save()
    
                 messages.success(request, 'Action successfully added.')
             return HttpResponseRedirect('/') #used to prevent the form from being saved more than once after page refresh
    
         else:
             formIntervention = FormIntervention()
             formsetArticles = FormSetDetail(prefix='child_formset')
    
         return render(request, "addIntervention.html", {'formIntervention': formIntervention,
                                                       'formsetItems': formsetItems})
    

    addIntervention.html:

         <form id="id-form-intervention" method="post">
           {% csrf_token %}
               <div id="form-container" class="external_scroll_reduced">
                 {{ formsetArticles.management_form }} <!--add info on the formset in the form of hidden <input> (form-TOTAL_FORMS)-->
                 <div id="div_formset">
                   {% for form in formsetArticles %}
                       <div class="form-detail">
                           {{ form }}
                       </div>
                   {% endfor %}
                 </div>
                 <button id="add-form" type="button" disabled>Add article</button>
               </div><br>
           <div class="item16"><input class="button" type="submit" value="save"></div>
         </form>
    

    Formset.js

    let detailForm = document.querySelectorAll('.form-detail');
    let container = document.querySelector('#form-container');
    let btnAdd = document.querySelector('#add-form');
    let totalForms = document.querySelector('#id_child_formset-TOTAL_FORMS'); //thanks to {{ formset.management_form }}
    let formNum = detailForm.length - 1; //-1 because it starts at 0
    
    let ArrSelectItems = [];
    let ArrDeleteItems = [];
    
    let divFormsets = document.querySelector('#div_formset');
    let labelsFormset = divFormsets.getElementsByTagName('label');
    let checkboxes = divFormsets.querySelectorAll('input[type="checkbox"]');
    
    for(let i = 0; i < labelsFormset.length; i++){labelsFormset[i].hidden = true;};
    
    btnAdd.addEventListener('click', addForm);
    
    let updateFormset = setInterval(function(){
       for(i=0; i < totalForms.value; i++){
         idFormsetArticle = 'id_child_formset-' + i + '-ID_article';
         idFormsetDelete = 'id_child_formset-'+ i + '-DELETE';
         selectFormset = document.getElementById(idFormsetArticle);
         deleteFormset = document.getElementById(idFormsetDelete);
         ArrSelectItems.push(selectFormset);
         ArrDeleteItems.push(deleteFormset);
    
         if(ArrSelectItems[i].value === ""){
           checkboxDelete = ArrDeleteArticles[i];
           checkboxDelete.checked = true;
         }else{
           checkboxDelete = ArrDeleteArticles[i];
           checkboxDelete.checked = false;
         };
       }
       //convert checkbox to boolean based on whether they are checked
       let checkboxValues = Array.from(ArrDeleteItems).map(function(checkbox){
         return checkbox.checked;
       });
       //check each checkbox value (.every) --> Check if at least one checkbox is selected (therefore the article has not been selected)
       let result = checkboxValues.every(function(value){
         return value === false;
       });
    
       if(result === false){
         btnAdd.disabled = true;
       }else{
         btnAdd.disabled = false;
       }
    
       ArrSelectItems = [];
       ArrDeleteItems = [];
    
    }, 100);
    
      
    function addForm(e){
       e.preventDefault();
    
       let newForm = detailForm[0].cloneNode(true); // clone Form detail
       let formRegexIdItem = RegExp(`child_formset-(\\d){1}-Item_ID`,'g'); //Regex to find all instances of the form by number number (I used the for loop, damn!)
       let formRegexQuantity = RegExp(`child_formset-(\\d){1}-quantity`,'g');
       let formRegexDelete = RegExp(`child_formset-(\\d){1}-DELETE`,'g');
      
       formNum++;
      
       newForm.innerHTML = newForm.innerHTML.replace(formRegexArticleId, `child_formset-${formNum}-Article_ID`); //update new form number
       newForm.innerHTML = newForm.innerHTML.replace(formRegexQuantity, `child_formset-${formNum}-quantity`);
       newForm.innerHTML = newForm.innerHTML.replace(formRegexDelete, `child_formset-${formNum}-DELETE`);
       container.insertBefore(newForm, btnAdd) // insert the new form, before the add button
      
       totalForms.setAttribute('value', `${formNum+1}`) //increment the total number of forms, contained in {{ formset.management_form }}, otherwise Django Error
    }
    

    Thanks anyway everyone!!!