inline forms/subforms: how to use inlineformset_factory ?

1,071 views
Skip to first unread message

Dominic Ashton

unread,
Nov 29, 2008, 6:00:16 AM11/29/08
to Django users
Hi all,

I've been working fruitlessly all day attempting what I though would
be a simple task: Adding a sub form/inline form so that I can have two
related forms displaying on the same page tied to models in a one-to-
many relationship.

This was very easy to do in the admin, but now I am attempting to do
this on my pages aimed at users.

I have been searching this mailing list, and google, all day and have
come up with very little. I read through the form chapter in Apress
Practical Django Projects (chapter 9) and unbeleivbly in that book
inline forms/sub forms are not covered outside of the Admin. In fact,
when I tend to find any information it is usually somebody trying to
extend the functionality in the admin, which I am not trying to do.

What I did come up with was to find inlineformset_factory. This seems
to be what i'm after, but it's proving tricky to find a clear and
simple example of its usage, in a context relating to my project,
anywhere on the web.

To give you a more specific idea of what im trying to implement:

############## High level aim: #########################

The are projects, and one project can have many issues and many
schedule items. For example:

Computer upgrade project:

issues:
john needs to order computers
bill needs to look into comparability issues
jane needs to order new internet circuit

schedule:
jan 16th orders
jan 24th delivery
jan 25th installation
jan 30th training

################# My models: ############################

class Project(models.Model):
name = models.CharField(max_length=50)
start_date = models.DateField(null=True, blank=True)
scheduled_end = models.DateField(null=True, blank=True)
is_live = models.BooleanField(default=True)
company = models.ForeignKey('Company')
date_created = models.DateField(auto_now_add=True)
last_updated = models.DateField(auto_now=True)

class Meta:
ordering = ['company']

def __unicode__(self):
return '%s %s' % (self.name, self.company)

class Project_schedule(models.Model):
start_date = models.DateField(null=True, blank=True)
duration = models.IntegerField(null=True, blank=True)
project = models.ForeignKey('Project')
project_role = models.ForeignKey('Project_role')
date_created = models.DateField(auto_now_add=True)
last_updated = models.DateField(auto_now=True)

class Meta:
ordering = ['project']

def __unicode__(self):
return '%s %s' % (self.project_role, self.project)

class Project_issue(models.Model):
name = models.CharField(max_length=50)
priority = models.ForeignKey('Priority_choice')
owner = models.CharField(max_length=50, null=True, blank=True)
notes = models.TextField(null=True, blank=True)
logged_date = models.DateField(null=True, blank=True)
date_for_resolution = models.DateField(null=True, blank=True)
project = models.ForeignKey('Project')
date_created = models.DateField(auto_now_add=True)
last_updated = models.DateField(auto_now=True)

class Meta:
ordering = ['project']

def __unicode__(self):
return '%s %s' % (self.name, self.project)

############## My attempt at a view: ###############

#project view
def project(request):
if request.method == 'POST':
#form = ProjectForm(request.POST)
form = inlineformset_factory(Project, Project_schedule,
request.POST )
if form.is_valid():
form.save()
return HttpResponseRedirect('')
else:
form = inlineformset_factory(Project, Project_schedule,
extra=0)

return render_to_response('sam_app/project.html', {'form':
form})

############################

I understand that the above must be totally incorrect. I'm not getting
any data showing up on my template.


I found this resource:

http://markmail.org/message/7byspr32sbarh7ni#query:inlineformset_factory+page:1+mid:zfow7vn6n5bkakmp+state:results

Which says something about the formset being a class and needing to
instantiate it before passing it to the template, but I am not able to
follow this so well.

My template looks like this:

{% block content%} <form action="." method="POST">
<table>
{{ form.as_table }}
</table>
<p><input type="submit" value="Submit"></p>
</form>
{% endblock %}


I'd really appreciate it if somebody could point me to a resource
which gives an example of what i'm trying to accomplish. I imagine
this must be a common requirement so I just can't understand why I can
find anything after so much searching.

Thanks,

Brian Rosner

unread,
Nov 29, 2008, 3:33:42 PM11/29/08
to django...@googlegroups.com
On Sat, Nov 29, 2008 at 4:00 AM, Dominic Ashton
<dominic...@gmail.com> wrote:
> I have been searching this mailing list, and google, all day and have
> come up with very little. I read through the form chapter in Apress
> Practical Django Projects (chapter 9) and unbeleivbly in that book
> inline forms/sub forms are not covered outside of the Admin. In fact,
> when I tend to find any information it is usually somebody trying to
> extend the functionality in the admin, which I am not trying to do.
>
> What I did come up with was to find inlineformset_factory. This seems
> to be what i'm after, but it's proving tricky to find a clear and
> simple example of its usage, in a context relating to my project,
> anywhere on the web.

Did you happen to read the official Django documentation on model
formsets? In model formsets there are two factory functions.
modelformset_factory and inlineformset_factory. The latter is a subset
of the former and makes it easier to work with related objects through
a foreign key. Therefore the only different between the two is how you
create them, but from then on they take on the same behavior and API.
With that in mind read
http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#id1

>
> ############## My attempt at a view: ###############
>
> #project view
> def project(request):
> if request.method == 'POST':
> #form = ProjectForm(request.POST)
> form = inlineformset_factory(Project, Project_schedule,
> request.POST )
> if form.is_valid():
> form.save()
> return HttpResponseRedirect('')
> else:
> form = inlineformset_factory(Project, Project_schedule,
> extra=0)
>
> return render_to_response('sam_app/project.html', {'form':
> form})
>
> ############################
>
> I understand that the above must be totally incorrect. I'm not getting
> any data showing up on my template.

Yes, this is wrong. Hopefully by read the documentation I gave you
will show you were your problem is. Just to clarify a bit more,
inlineformset_factory returns a class. You then instantiate the class
to a FormSet instance. During that process you provide it with POST
data or any data for that matter. Then the FormSet instance is passed
to the template. Also, don't call a FormSet class or instance 'form'.
That is just going to be confusing. It is better named FormSet (or
some variant depending on the models) for a class and formset for
instances. This will help you see the differences and when you are
dealing with both.

--
Brian Rosner
http://oebfare.com

Dominic Ashton

unread,
Dec 2, 2008, 8:02:02 AM12/2/08
to Django users

>
> Did you happen to read the official Django documentation on model
> formsets? In model formsets there are two factory functions.
> modelformset_factory and inlineformset_factory. The latter is a subset
> of the former and makes it easier to work with related objects through
> a foreign key. Therefore the only different between the two is how you
> create them, but from then on they take on the same behavior and API.
> With that in mind readhttp://docs.djangoproject.com/en/dev/topics/forms/modelforms/#id1
>
Brian,

Thanks for you reply.

I did read this documentation, and was confused because I read this:

>>> from django.forms.models import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book)
>>> author = Author.objects.get(name=u'Orson Scott Card')
>>> formset = BookFormSet(instance=author

And thought, "ok, that works great in the interpreter, but how do I
put it into my system?" Does it go in forms.py? views.py?

If it's forms.py, how do I structure it?

I guess I'm just not getting it. Will try a load of trial and error
this evening see if I can get something to work.

Cheers,

Dominic

Dominic Ashton

unread,
Dec 2, 2008, 8:29:56 AM12/2/08
to Django users

>
> I did read this documentation, and was confused because I read this:
>
> >>> from django.forms.models import inlineformset_factory
> >>> BookFormSet = inlineformset_factory(Author, Book)
> >>> author = Author.objects.get(name=u'Orson Scott Card')
> >>> formset = BookFormSet(instance=author
>
> And thought, "ok, that works great in the interpreter, but how do I
> put it into my system?" Does it go in forms.py? views.py?
>
> If it's forms.py, how do I structure it?
>
> I guess I'm just not getting it. Will try a load of trial and error
> this evening see if I can get something to work.
>
> Cheers,
>
> Dominic

Ok, I see this must be initiated just as shown in the documentation,
in the views.py file. I have managed to get this far:

from django.forms.models import inlineformset_factory
#project view
def project(request):
ModelForm = inlineformset_factory(Project, Project_schedule)
if request.method == 'POST':
form = ProjectForm(request.POST)
project = Project.objects.get(pk=request.pk)
formset = ModelForm(instance=project)
if form.is_valid():
form.save()
return HttpResponseRedirect('')
else:
form = ProjectForm()
formset = ModelForm()

return render_to_response('sam_app/project.html', {'form':
form, 'formset': formset})

At least I can now initialize a blank model inlineformset. However, I
can't for the life of me work out how to tie the two together so that
I can:

1) Validate both sets of data
2) save both to the database.

As you can see, I tried request.pk, which I realize is really stupid
as how can the thing have a PK when it's not even saved in the db?
When I realized that, I hit a mental brick wall because without access
to the pk how am I going to work with relations?

Does the way to solve this involve:

1) saving the parent data (if it validates)
2) retrieve the pk from the db
3) use that pk to save the child data (if it validates)

If so, what prevents the system from retrieving the incorrect pk, say
in the case of heavy concurrent access of the db?

This is all a bit confusing for a noob. If there was some kind of
example code to look at I feel it would really help. I tried to dig
through the admin code but found it a little heavy to follow.

Cheers,


Dominic
Reply all
Reply to author
Forward
0 new messages