django: How to use ModelForm with dynamic 0-n possibilities?
Question
The Quetion
Hello. I'm trying to figure out the best way to use one form to create one parent object and then create 0-n sub objects. I'm assuming ModelForms are what I need to do, but I'm having a hard time understanding how to structure the template and view. Would someone be willing to explain how to create n number of sub objects?
The Research
I've read several other articles and posts relating to this:
- django model/modelForm - How to get dynamic choices in choiceField?
- https://stackoverflow.com/questions/5575560/how-do-i-create-a-drop-down-menu-in-django-using-a-modelform-with-dynamic-values
- http://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/
- Overriding the save method in Django ModelForm
To name a few.
The Details
I have two models like so:
// models.py
class DataItem(models.Model):
name = models.CharField(max_length=255)
date_created = models.DateTimeField(auto_now_add=True)
date_last_updated = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User, blank=False)
def __unicode__(self):
return self.name
class DataItemSet(models.Model):
item = models.ForeignKey(DataItem, blank=False)
type_ind = models.IntegerField()
And I've created two ModelForms to correspond:
// forms.py
class CreateDataItemForm(forms.ModelForm):
class Meta:
model = DataItem
exclude = ('owner',)
def save(self, user, commit=True):
item = super(CreateDataItemForm,self).save(commit=False)
item.owner = user
if commit:
item.save()
return item
class CreateDataItemSetForm(forms.ModelForm):
class Meta:
model = DataItemSet
exclude = ('item',)
def save(self, parent, commit=True):
set = super(CreateDataItemSetForm,self).save(commit=False)
set.item = parent
if commit:
set.save()
return set
And in my view, I'm trying to have one form submit the creation of a new DataItem
and 1-n DataItemSet
s which are owned by the DataItem
. Here is an example of the output of the form:
And the template:
<form action="." method="post">
{% csrf_token %}
<table>
{{ create_form.as_table }}
</table>
<table>
<tr>
<th>What to track:</th>
<td>
<select>
<option value="1">Number</option>
<option value="2">Currency ($)</option>
<option value="3">Date</option>
<option value="4">Day</option>
<option value="5">Time</option>
</select>
</td>
<td>
</td>
</tr>
<tr>
<th>What to track:</th>
<td>
<select>
<option value="1">Number</option>
<option value="2">Currency ($)</option>
<option value="3">Date</option>
<option value="4">Day</option>
<option value="5">Time</option>
</select>
</td>
<td>
<button type="button">+</button>
</td>
</tr>
</table>
<p>
<button type="submit">Create</button>
</p>
</form>
And lastly, the view:
// views.py
@login_required
@csrf_protect
def create_data_item(request):
create_form = CreateDataItemForm()
c = {'create_form':create_form}
c.update(csrf(request))
if request.method == 'POST':
data = request.POST.copy()
form = CreateDataItemForm(data, instance=DataItem())
item_sets = [CreateDataItemSetForm(request.POST, prefix=str(x), instance=DataItemSet()) for x in range(0,9)]
if form.is_valid():
# create new data item
new_item = form.save(request.user)
#create new set of stuff to track
for item_set in item_sets:
new_item_set = item_set.save(new_item)
# return to the add entry page
return redirect('/add')
else:
return render_to_response('internal/create_data_item.html',c)
I'm missing how to allow for dynamic extraction of 0-n DataItemSets from the form
. I can't figure it out with ModelForm.
Thanks for any help!
Solution
This is what model formsets - specifically, inline formsets - are for.