Custom formset templates in Django - django

I am using a Django formset for this model:
class Book(models.Model):
book_id=models.AutoField(primary_key=True,unique=True)
book_name=models.CharField(max_length=30)
publisher_name=models.CharField(max_length=40)
author=models.ForeignKey(Author)
The formset is defined thus:
BookFormset = inlineformset_factory(Author, Book,
fields=('book_id','book_name', 'publisher_name'), extra=1,
can_delete=False)
The template is:
{{ formset.non_form_errors.as_ul }}
<table id="formset" class="form">
{% for form in formset.forms %}
{% if forloop.first %}
<thead><tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}
{% endfor %}
</tr></thead>
{% endif %}
<tr class="{% cycle row1,row2 %}">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
The fields are displayed column-wise, but I would like them to be displayed row-wise.
The above code produces output like this:
Book name Publisher name
book_field Publisher_field
I would like the output to look like this:
Book name book_field
Publisher name Publisher_field
How can I do this?

In your template, you have two <tr> elements, each of which contains a loop over form.visible_fields, each iteration of which generates a single <th> or <td>.
Change this round so that you have a single loop over form.visible_fields, each iteration of which contains a single <tr> element containing a <th> and a <td>. Like this:
<table id="formset" class="form">
{% for form in formset.forms %}
{% for field in form.visible_fields %}
<tr class="{% cycle row1,row2 %}">
<th>{{ field.label|capfirst }}</th>
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
</tr>
{% endfor %}
{% endfor %}
</table>

The examples above seem to show a column-wise layout, which appears to be the default layout when a formset renders itself.
To make it row-wise, use something like this:
<table>
{% for form in formset.forms %}
{% if forloop.first %}
<thead>
{% for field in form.visible_fields %}
<th>{{ field.label }}</th>
{% endfor %}
</thead>
<tbody>
{% endif %}
<tr class="{% cycle row1,row2 %}">
{% for field in form.visible_fields %}
<td>
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% if forloop.last %}
</tbody>
{% endif %}
{% endfor %}
</table>

Related

Management Formset error is getting triggered

The HTML code below is returning a ['ManagementForm data is missing or has been tampered with']. This HTML was what gave me the custom look I was going for, but why is this happening? I don't understand, since I have declared the management_data tag.
HTML
<form method="POST" enctype="multipart/form-data" action=".">
{{ formset.management_data }}
<!-- Security token -->
{% csrf_token %}
{{ formset.non_form_errors.as_ul }}
<table>
{% for form in formset.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th name={{field.label}}>{{ field.label }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="{% cycle row1 row2 %}">
{% for field in form.visible_fields %}
<td name={{field.label}}>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</form>
You need to include {{ formset.management_form }} in your template not {{ formset.management_data }}. Docs
<form method="POST" enctype="multipart/form-data" action=".">
{{ formset.management_form }}
{% csrf_token %}
...

Displaying label on first form of Django formset only

This is a super straightforward question but I can't seem to find any concise answer it. I have a Django formset that displays different tags associated with an object. Here is the form:
class TagForm(forms.Form):
def __init__(self, *args, **kwargs):
tags = kwargs.pop('tags')
super(TagForm, self).__init__(*args, **kwargs)
self.fields['tags'] = forms.ChoiceField(choices=[(tag, tag) for tag in tags], label="Tags")
I'm rendering the formset using the following code:
<li class="list-group-item">
<ul class="list-inline" id="tag-group">
{{ tag_formset.management_form }}
{% for tag_form in tag_formset %}
<li class="list-inline-item">
{{ tag_form.tags.label_tag }}
{{ tag_form.tags }}
</li>
{% endfor %}
</ul>
</li>
My problem is that this creates a label for each tag. Since this is an inline list, I'd only like to display the label prior to the first tag (and no others). I can't find any straightforward way to do this (without modifying the for loop with explicit logic checking if it is the first form being rendered). I optimistically tried to modify my rendering code to the following:
<li class="list-group-item">
<ul class="list-inline" id="tag-group">
{{ tag_formset.management_form }}
{{ tag_form.empty_form.label_tag }}
{% for tag_form in tag_formset %}
<li class="list-inline-item">
{{ tag_form.tags }}
</li>
{% endfor %}
</ul>
</li>
but this didn't display any labels at all. Is there an idiomatic way to only display the form label prior to the first form in a formset?
The code below is working for me. The main idea is simple. Make a Table and in the headers put the tags. and in the table body put only the data. Try it and let me know.
{% for f1 in formset %}
{{ f1.management_form|crispy }}
{% crispy f1 %}
{% endcomment %}
<table{% if form_id %} id="{{ form_id }}_table" {% endif%} class="table table-striped table-condensed">
<thead>
{% if formset.readonly and not formset.queryset.exists %}
{% else %}
<tr>
<td>
</td>
{% for field in formset.forms.0 %}
{% if field.label and not field.is_hidden %}
<th for="{{ field.auto_id }}"
class="control-label {% if field.field.required %}requiredField{% endif %}">
{{ field.label|safe }}{% if field.field.required %}<span
class="asteriskField">*</span>{% endif %}
</th>
{% endif %}
{% endfor %}
</tr>
{% endif %}
</thead>
<tbody>
{% comment %} <tr class="hidden empty-form">
{% for field in formset.empty_form %}
{% include 'bootstrap/field.html' with tag="td" form_show_labels=False %}
{% endfor %}
</tr> {% endcomment %}
{% for form2 in formset2 %}
{% if form2_show_errors and not form2.is_extra %}
{% include "bootstrap/errors.html" %}
{% endif %}
<tr>
<td>
<a class="btn btn-info pull-right" {% comment %}
href="{% url 'set_final' formfs.pk %}/?next={% url 'update-well-view' form.pk %}">
{% endcomment %}
href="{% url 'Scouts-home' %}"> Set Final
</a>
</td>
{% for field in form2 %}
{% include 'bootstrap/field.html' with tag="td" form2_show_labels=False %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>

No named cycles in template. 'row1,row2' is not defined

In my django inline formset, form html:
{% block body %}
<h2>Profile</h2>
<hr>
<div class="col-md-4">
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<table class="table">
{{ familymembers.management_form }}
{% for form in familymembers.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="{% cycle row1,row2 %} formset_row">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Save"/> back to the list
</form>
</div>
{% endblock %}
When I tried to open form it gives
TemplateSyntaxError at /profile/add/
No named cycles in template. 'row1,row2' is not defined
How could I avoid this error?
That's not how you use that tag, as the docs show. The values should be separated by spaces, not commas, and if they are literal strings they should be in quotes.
{% cycle "row1" "row2" %}
If you still get an error, you can try:
class="{% cycle 'row1' 'row2' %} formset_row"

How to I get the length or size or number of fields in a form in a django template?

I would like to write a loop like this, so that I can spread the form fields out in a table. :
{% load widget_tweaks %}
{% load mathfilters %}
{% load get_range %}
{% for k in form|length|div:5|floatformat|add:1|get_range %}
<tr>
{% for field in form %}
{% if forloop.counter >= k|mul:5 and forloop.counter <= k|mul:5|add:4 %}
<th>{{ field.label_tag }}{{ field.errors }}</th>
{% endif %}
{% endfor %}
</tr>
<tr>
{% for field in form %}
{% if forloop.counter >= k|mul:5 and forloop.counter <= k|mul:5|add:4 %}
<td>{{ field|add_class:"span4" }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
This doesn't work, but because the code above fails on form|length. In order for this to work, I need to get, in a template, the number of fields in a form. Does anyone know how to do this? I've searched all over but can't find anything. The following do NOT work:
form.len
form.length
form|length
Thanks!
I'm really not sure what you are looking for, but it sounds like this:
{% for field in form %}
<tr>
{% if forloop.counter0|divisibleby:5 %}
<th class="span4">{{ field.label_tag }}{{ field.errors }}</th>
{% else %}
<th>{{ field.label_tag }}{{ field.errors }}</th>
{% endif %}
</tr>
{% endfor%}
{% for field in form %}
<tr>
{% if forloop.counter0|divisibleby:5 %}
<td>{{ field|add_class:"span4" }}</td>
{% else %}
<td>{{ field }}</td>
</tr>
{% endfor %}
I dont like this code, but it was my first idea.
{% for field in form %}
{% if forloop.last %}
{{ forloop.counter }}
{% endif %}
{% enfor %}
form.fields I believe.
{% for field_name in form.fields %}
thanks for your suggestions - they helped! Here is what finally worked for me:
{% for field in form %}
{% if forloop.counter0|divisibleby:5 %}
<tr>
{% for field in form %}
{% if forloop.counter0 >= forloop.parentloop.counter0 and forloop.counter0 <= forloop.parentloop.counter0|add:4 %}
<th>{{ field.label_tag }}{{ field.errors }} </th>
{% endif %}
{% endfor %}
</tr>
<tr>
{% for field in form %}
{% if forloop.counter0 >= forloop.parentloop.counter0 and forloop.counter0 <= forloop.parentloop.counter0|add:4 %}
<td>{{ field }}</td>
{% endif %}
{% endfor %}
</tr>
{% endif %}
{% endfor %}

Django Admin: Customizing the inline template (tabular.html)

I'm trying to follow the guidelines in this answer, but I'm getting stuck with how to edit the template.
The relevant part of my admin.py:
SegmentFormset = forms.models.inlineformset_factory(Division,Segment)
class DivisionForm(forms.ModelForm):
def __init__(self, **kwargs):
super(DivisionForm, self).__init__(**kwargs)
self.segment_formset = SegmentFormset(instance=self.instance, data=self.data,
prefix=self.prefix)
def is_valid(self):
return (super(DivisionForm, self).is_valid() and
self.segment_formset.is_valid())
def save(self, commit=True):
assert commit == True
res = super(DivisionForm, self).save(commit=commit)
self.segment_formset.save()
return res
class DivisionInline(admin.TabularInline):
model = Division
form = DivisionForm
template = 'competitions/admin/tabular.html'
class CompetitionAdmin(VersionAdmin):
inlines = [DivisionInline,]
The relevant part of my template:
{% for fieldset in inline_admin_form %}
{% for line in fieldset %}
{% for field in line %}
<td class="{{ field.field.name }}">
{{ field.field.errors.as_ul }}
{{ field.field }}
</td>
{% endfor %}
{% endfor %}
<td>My segment formset should be here</td>
{% endfor %}
What I can't figure out is how to access the segment formset. I've experimented with all of the variable names and none of them are my DivisionForm. The division formset is the {{fieldset.formset}} variable and that's as far as I've been able to get.
Edit 1:
Actually, the relevant part of the template is a bit longer ;) Putting in more code:
<tbody>
{% for inline_admin_form in inline_admin_formset %}
{% if inline_admin_form.form.non_field_errors %}
<tr><td colspan="{{ inline_admin_form.field_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr>
{% endif %}
<tr class="{% cycle row1,row2 %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}">
<td class="original">
{% if inline_admin_form.original or inline_admin_form.show_url %}<p>
{% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %}
{% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %}
</p>{% endif %}
{% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
{{ inline_admin_form.fk_field.field }}
{% spaceless %}
{% for fieldset in inline_admin_form %}
{% for line in fieldset %}
{% for field in line %}
{% if field.is_hidden %} {{ field.field }} {% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endspaceless %}
</td>
{% for fieldset in inline_admin_form %}
{% for line in fieldset.formset %}
{% for field in line %}
<td class="{{ field.field.name }}">
{{ field.field.errors.as_ul }}
{{ field.field }}
</td>
{% endfor %}
{% endfor %}
<td>My segment formset should be here</td>
{% endfor %}
{% if inline_admin_formset.formset.can_delete %}
<td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
Formsets are like lists, so you can recurse it like
{% for form in fieldset.formset %}
{{ form.as_p }}
{% endfor %}