djangodjango-formsdjango-templatesformset

How to use model form instances of a formset in Django template


I'm trying to access the instance of the forms in a formset, but it is not working. I CAN access them using the variable notation, as in {{ form }}, but not in code, as in {% url 'section' form.instance.pk %}. I need to iterate through the forms in the formset along with the corresponding model instance.

My view:

# views.py

def sec(request, companyurl):
    company = get_if_exists(Company, author=request.user)

    SectionFormSet = modelformset_factory(Section, form=SectionForm, can_delete=True)
    sections = Section.objects.filter(company=company).order_by('order')
    formset = SectionFormSet(request.POST or None,
                             initial=[{'company': company}],
                             queryset=sections

    context = {
        'sections': sections,
        'formset': formset,
    }
    return render(request, 'questions/sections.html', context)

My model:

# models.py

class Section(models.Model):

    section = models.CharField(max_length=100)
    company = models.ForeignKey(Company, on_delete=models.CASCADE)
    order = models.PositiveIntegerField(default=1000000)
    show = models.BooleanField(default=True)

    def __str__(self):
        return self.section

My Form (I'm using django-crispy forms):

# forms.py
class SectionForm(forms.ModelForm):
    class Meta:
        model = Section
        fields = ['company', 'section', 'show', 'order']
        labels = {
            'section': '',
            'show': 'Show'
        }
    def __init__(self, *args, **kwargs):
        super(SectionForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.layout = Layout(
            Div(
                Div(HTML("##"), css_class = 'my-handle col-auto'),
                Div('section', css_class='col-3'),
                Div('show', css_class = 'col-auto'),
                Div('DELETE', css_class = 'col-auto'),
                Field('company', type='hidden'),
                Field('order', type='hidden'),
                css_class='row',
            ),
        )

My template (this is where the problem is seen):

<form action="#" method="post">
  {% csrf_token %}

  {{ formset.management_form }}

  <div id="simpleList" class="list-group">


  {% for fo in formset %}

    <div class="list-group-item hold">

      {% crispy fo %}

      <!-- TESTING TO SEE IF THIS WORKS, AND IT DOES! -->
      {{ fo.instance }} + {{ fo.instance.pk }} + {{ fo.instance.section }}

      <!-- THE PROBLEM OCCURS WHEN THIS IS ADDED -->
      <a href="{% url 'section' fo.instance.pk fo.instance.section %}">
        {{ fo.instance }}
      </a>
      <!-------------------------------------------->

      <input type="hidden" name="order" value="{{ section.pk }}">

      {% for hid in fo.hidden_fields %}
        {{ hid }}
      {% endfor %}

    </div>

  {% endfor %}

  <button type="submit" class="btn btn-outline-primary">Save changes</button>

</form>

When I add the <a href="{% url 'section' fo.instance.pk fo.instance.section %}>link</a> line I get

Reverse for 'section' with arguments '(None, '')' not found. 1 pattern(s) tried: ['section/(?P<pk>[0-9]+)/(?P<section>[^/]+)\\Z']

The error is clear. fo.instance.pk is None and fo.instance.section is an empty string. Yet when I remove the anchor tag, the line above appears and shows the correct values for both of these. I think I know the difference in how the {{ }} and the {% %}, and I thought I knew how model form instances were tied to the model, but I am missing something.

Thanks for any help.


Solution

  • Formsets create blank forms

    The answer was staring me in the face, when I printed the results. The last form, a blank, of course was giving me None and an empty string, since it had no data to fill it with. Thus the simple solution is to check for this before trying to form the url with the information. Therefore, this has nothing to do with the differences between {{ }} and {% %} nor form instances.

    {% for fo in formset %}
    
        <div class="list-group-item hold">
    
          {% crispy fo %}
    
          <!-- TESTING TO SEE IF THIS WORKS, AND IT DOES! -->
          {{ fo.instance }} + {{ fo.instance.pk }} + {{ fo.instance.section }}
    
          <!-- THE PROBLEM OCCURED WHEN THIS WAS ADDED -->
          <!-- THE SIMPLE SOLUTION: --------------------->
          {% if fo.instance.pk %}
             <a href="{% url 'section' fo.instance.pk fo.instance.section %}">
               {{ fo.instance }}
             </a>
          {%  endif %}
          <!-------------------------------------------->
    
          <input type="hidden" name="order" value="{{ section.pk }}">
    
          {% for hid in fo.hidden_fields %}
            {{ hid }}
          {% endfor %}
    
        </div>
    
      {% endfor %}
    
      <button type="submit" class="btn btn-outline-primary">Save changes</button>
    
    </form>