views:

2444

answers:

4

I'm trying to prepopulate a ModelForm and an inlineformset_factory with an instance of a Model, BUT when the user submits the form, I need to create a new instance of the Model and it's related child records.

Example Model:

class Artist(models.Model):
    artist = models.CharField(max_length=100)

class Song(models.Model):
    artist = models.ForeignKey(Artist)
    song = models.CharField(max_length=200)

I want the user to see an edit form based on an instance of Artist, along with an InlineFormSet for that Artist's related Songs. The form will be prepopulated with the existing data and the user can change the artist's name and the song names. However, when the user submits the form, I don't want to overwrite the existing records. Instead, I want to create a new instance of Artist and add new Songs for this new artist.

I have tried setting the primary key of artist to None before saving - and this forces a new instance of Artist. However, I lose the ForeignKey relationship between Artists and Songs.

Example View:

def edit(request, artist_id=None):
    if  artist_id == None:
        artistsubmission = Artist()
    else:
        artistsubmission = Artist.objects.get(id = artist_id)
        artistsubmission.pk = None

    if request.method == 'POST':
        form = ArtistEditForm(request.POST, instance=artistsubmission)
        formset = SongFormSet(request.POST, instance=artistsubmission)

        if form.is_valid() and formset.is_valid():
            form.save()
            formset.save()
            return HttpResponseRedirect('/success/')    
    else:
        form = ArtistEditForm(instance=artistsubmission)
        formset = SongFormSet(instance=artistsubmission)

    return render_to_response('edit.html', {'form':form, 'formset':formset})
A: 

I think the easiest way to do this would be to set default values by passing a dictionary in as the first argument to the form. You can get all of a model instance's fields by:

d_initial = Artist.objects.filter(pk=artist_id).values()[0]

This is the dictionary that you'd pass in to the form.

form = ArtistEditForm(d_initial)

If you've excluded anything from the form, you might want to remove it from the dictionary. Same for id. But this should produce a form that has all the values from the existing instance, but will save to a new instance.

tghw
Thanks - this works really well for the model, but not for the inlineformset_factory. It seems inlineformset_factory will not take initial values.
jlewis
+2  A: 

You can iterate over the individual forms in your formset, change what you need to and save.

if form.is_valid() and formset.is_valid():
    artist = form.save()
    for f in formset.forms:
        song = f.save(commit=False)
        song.artist = artist.id
        song.save()
Harold
Thanks - I think I could make this work with a modelformset, but not an inlineformset. It would require that I loop through songs, set the initial value, then perform a save on each item. That feels like I'd be recreating a lot of the inlineformset capability. If I'm missing the something, I'd appreciate any additional advice. Thanks ;)
jlewis
I'll mark this as the answer and post a different question about inlineformsets once I work through this. Thanks again.
jlewis
A: 

I found this snippet which I think was more what you were looking for. http://www.djangosnippets.org/snippets/1246/

Justin Hamade
A: 

I had a similar problem and I solved it like this:

def edit(request, artist_id=None):
    if artist_id == None:
        artistsubmission = Artist()
    else:
        artistsubmission = get_object_or_404(Artist, id = artist_id)

    if request.method == 'POST':
        form = ArtistEditForm(request.POST, instance=artistsubmission)
        formset = SongFormSet(request.POST, instance=artistsubmission)

        if form.is_valid() and formset.is_valid():
            artistsubmission.pk = None

            artist = form.save(commit=False)
            artist.id = None
            artist.save()

            for f in formset.forms:
                song = f.save(commit=False)
                song.artist_id = artist.id
                song.id = None
                song.save()
            return HttpResponseRedirect('/success/')
    else:
        form = ArtistEditForm(instance=artistsubmission)
        formset = SongFormSet(instance=artistsubmission)
    return render_to_response('edit.html', {'form':form, 'formset':formset})
Irfan