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

2

8

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"/>

Febricity answered 24/2, 2012 at 19:4 Comment(8)
Can you add source for Books and Author model so that I can try it ? thanks ;)Frederik
"get_object_or_404(author, pk=author_id)" should be "get_object_or_404(Author, pk=author_id)" (capitalized Author) right ? Also, are you sure your model is called Book ? It looks like it should be Books ?Frederik
also: aurthorsbooks should be authorsbooksFrederik
Yes, sorry I am copying over but masking some variables. Anyways, the structure is essentially the same. and still unable to see errors.. except for when using: {% for dict in form_errors %} {{ dict }} {% endfor %}Febricity
Can you show pseudo html of the result that you want ? I'm not quite sure in understand "i want to pair the errors for each form"Frederik
See a book not listed under this author? Add one now! Title: *Error, your missing a title. Comment: this book is awesomeFebricity
Do you want something like <form1errors /><form1 /><form2errors /><form2 /> ... and so on ? or something else ?Frederik
let us continue this discussion in chatFrederik
F
13

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

Frederik answered 24/2, 2012 at 19:12 Comment(6)
for some reason my errors are still not showing up. I've updated my template. what am I doing wrong?Febricity
Did you try {{ x.non_field_errors }} instead of {{x.errors }} ?Frederik
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...Febricity
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 }}Frederik
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.Febricity
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 ?)Frederik
R
1

Assuming the book is a form you could do:

{% for b in authorsbooks %}
    {{b.errors}}
{% endfor %}
Rattrap answered 24/2, 2012 at 19:11 Comment(4)
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.Febricity
What did the debug output say?Rattrap
it says ValidationError [u'ManagementForm data is missing or has been tampered with']Febricity
This is because: docs.djangoproject.com/en/dev/topics/forms/formsets/…Frederik

© 2022 - 2024 — McMap. All rights reserved.