Django: how to display form errors for each model object in a inline formset

19,194

Solution 1

EDIT

authors_books_edits.html

<form method="post" action="/test/{{ author.id }}/books/">
    {% csrf_token %}
    {{ formset.management_form }}
    {% for form in formset.forms %}
        {{ form.non_field_errors }}
        {{ form.errors }}
        <table>
            {{ form.as_table }}
        </table>
    {% endfor %}
    <input type="submit" value="Submit"/>
</form>

views.py

from django.shortcuts import *
from django.forms.models import inlineformset_factory

from .models import *

def author_books_edit(request, author_id):
    a = get_object_or_404(Author, pk=author_id)
    authorsbooks = a.books_set.all()
    bookformset = inlineformset_factory(Author, Books, can_delete=True, can_order=True, exclude=('company',), extra=1)
    formset = bookformset(instance=a)
    if request.method == "POST":
        formset = bookformset(request.POST, request.FILES, instance=a)
        if formset.is_valid():
            formset.save()
        else:
            form_errors = formset.errors
            return render_to_response('authors_books_edits.html', {'author': a, 'authorsbooks': authorsbooks, 'formset': formset, 'form_errors': form_errors}, context_instance=RequestContext(request))
    return render_to_response('authors_books_edits.html', {'author': a, 'authorsbooks': authorsbooks, 'formset': formset,}, context_instance=RequestContext(request))

models.py

from django.db import models

class Author(models.Model):
    fname = models.CharField(max_length=100)
    lname = models.CharField(max_length=100)
    def fullname(self):
        return '%s %s' % (self.fname, self.lname)
    fullname = property(fullname)
    def __unicode__(self):
        return self.fullname

class Books(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=50)
    publisher = models.CharField(max_length=50)
    edition = models.CharField(max_length=50)
    comment = models.TextField()
    def __unicode__(self):
         return self.title

urls.py

from django.conf.urls.defaults import patterns, include, url

urlpatterns = patterns('testapp.views',
    url(r'test/(?P<author_id>\d+)/books/$', 'author_books_edit'),
)

You can make another temporary app to test it.

It looks like this: http://imageshack.us/photo/my-images/824/screenshotat20120227190.png/

== END EDIT

You can iterate over forms as such:

{% for form in formset.forms %}
   {{ form }}
{% endfor %}

In that case, refer to Django's displaying a form using a template documentation: https://docs.djangoproject.com/en/dev/topics/forms/#displaying-a-form-using-a-template

Then, more interesting, customizing a form template (see form.non_field_errors): https://docs.djangoproject.com/en/dev/topics/forms/#customizing-the-form-template

Solution 2

Assuming the book is a form you could do:

{% for b in authorsbooks %}
    {{b.errors}}
{% endfor %}
Share:
19,194
thedeepfield
Author by

thedeepfield

Ruby/RoR, Python/Django, JAVA/Android.

Updated on June 09, 2022

Comments

  • thedeepfield
    thedeepfield almost 2 years

    I have a author model and a books model. A user can modify properties of all the books from a given author. I want to be able to display errors for each individual book rather than have all the errors listed on the top, How can I do this?

    MODELS

    from django.db import models
    from django.forms import ModelForm, Textarea
    from django import forms
    
    class Author(models.Model):
        fname = models.CharField(max_length=100)
        lname = models.CharField(max_length=100)
        def fullname(self):
            return '%s %s' % (self.fname, self.lname)
        fullname = property(fullname)
        def __unicode__(self):
            return self.fullname
    
    class Books(models.Model):
        author = models.ForeignKey(Author)
        title = models.CharField(max_length=50)
        publisher = models.CharField(max_length=50)
        edition = models.CharField(max_length=50)
        comment = models.TextField()
        def __unicode__(self):
             return self.title
    

    VIEW

    def author_books_edit(request, author_id):
        a = get_object_or_404(Author, pk=author_id)
        authorsbooks = a.books_set.all()
        bookformset = inlineformset_factory(Author, Books, can_delete=True, can_order=True, exclude=('company',), extra=1)
        formset = bookformset(instance=a)
        if request.method == "POST":
            formset = bookformset(request.POST, request.FILES, instance=a)
            if formset.is_valid():
                formset.save()
            else:
                form_errors = formset.errors
                return render_to_response('test/authors_books_edits.html', {'author': a, 'authorsbooks': authorsbooks, 'formset': formset, 'form_errors': form_errors}, context_instance=RequestContext(request))
        return render_to_response('test/authors_books_edits.html', {'author': a, 'authorsbooks': authorsbooks, 'formset': formset,}, context_instance=RequestContext(request))
    

    TEMPLATE

    #all errors are here
    {% for dict in form_errors %}
        {{ dict }}
    {% endfor %}
    
    #all forms are here, i want to pair the errors for each form
    <form method="post" action="/test/{{ author.id }}/books/">
        {% csrf_token %}
            <table>
            {{ formset }}
            </table>
        <input type="submit" value="Submit"/>
        </form>
    

    UPDATED TEMPLATE: doesn't display errors

    <form method="post" action="/test/{{ author.id }}/books/">
        {% formset.management_form %}
        {% csrf_token %}
        <table>
            {% for x in formset %}
            {{x.errors }}
            {{ x }}
            {% endfor %}
        </table>
    <input type="submit" value="Submit"/>
    

  • thedeepfield
    thedeepfield about 12 years
    i updated my template but now i get an error and the page is broken... I've updated my post to include the updated template.
  • Nix
    Nix about 12 years
    What did the debug output say?
  • thedeepfield
    thedeepfield about 12 years
    it says ValidationError [u'ManagementForm data is missing or has been tampered with']
  • thedeepfield
    thedeepfield about 12 years
    for some reason my errors are still not showing up. I've updated my template. what am I doing wrong?
  • jpic
    jpic about 12 years
    Did you try {{ x.non_field_errors }} instead of {{x.errors }} ?
  • thedeepfield
    thedeepfield about 12 years
    yes, x.non_field_errors does not show anything. It's weird because {% for dict in form_errors %} {{ dict }} {% endfor %} shows errors, just not in pair with my forms...
  • jpic
    jpic about 12 years
    There are two kinds of errors: field errors and non field errors. To get field errors, which should display next to the inputs, use {{ form.field_name.errors }}. To get non field errors, which should display at the top of the form, call {{ form.non_field_errors }}
  • thedeepfield
    thedeepfield about 12 years
    thanks for your input but the errors are still not appearing.. I put {{ form.non_field_errors }} right under {{ form.management_form }}, I also put {{ x.nameofmyfield.errors }} right above {{ x }} and nothing showed up.. what am I doing wrong? this is driving me nuts.
  • jpic
    jpic about 12 years
    I got an update which might do what you want. If it doesn't, please elaborate a description of the result you want (pseudo-html ?)
  • jpic
    jpic about 12 years