views:

564

answers:

2

I've made a nice form, and a big complicated 'add' function for handling it. It starts like this...

def add(req):
    if req.method == 'POST':
        form = ArticleForm(req.POST)
        if form.is_valid():
            article = form.save(commit=False)
            article.author = req.user
            # more processing ...

Now I don't really want to duplicate all that functionality in the edit() method, so I figured edit could use the exact same template, and maybe just add an id field to the form so the add function knew what it was editing. But there's a couple problems with this

  1. Where would I set article.id in the add func? It would have to be after form.save because that's where the article gets created, but it would never even reach that, because the form is invalid due to unique constraints (unless the user edited everything). I can just remove the is_valid check, but then form.save fails instead.
  2. If the form actually is invalid, the field I dynamically added in the edit function isn't preserved.

So how do I deal with this?

+1  A: 

You can have hidden ID field in form and for edit form it will be passed with the form for add form you can set it in req.POST e.g.

formData =  req.POST.copy()
formData['id'] = getNewID()

and pass that formData to form

Anurag Uniyal
+5  A: 

Are you extending your form from ModelForm? If so, use the instance keyword argument. Here we pass either an existing instance or a new one, depending on whether we're editing an existing article or adding a new one. In both cases the author field is set on the instance, so commit=False is not required (note that I'm assuming only the author can edit their own articles, hence the HttpResponseForbidden exception):

@login_required
def edit(request, id=None, template_name='article_edit_template.html'):
    if id:
        article = get_object_or_404(Article, pk=id)
        if article.author != request.user:
            raise HttpResponseForbidden()
    else:
        article = Article(author=request.user)

    if request.POST:
        form = ArticleForm(request.POST, instance=article)
        if form.is_valid():
            form.save()

            # If the save was successful, redirect to another page
            redirect_url = reverse(article_save_success)
            return HttpResponseRedirect(redirect_url)

    else:
        form = ArticleForm(instance=article)

    return render_to_response(template_name, {
        'form': form,
    }, context_instance=RequestContext(request))

And in your urls.py:

(r'^article/new/$', views.edit, {}, 'article_new'),
(r'^article/edit/(?P<id>\d+)/$', views.edit, {}, 'article_edit'),

The same edit view is used for both adds and edits, but only the edit url pattern passes an id to the view. To make this work well with your form you'll need to omit the author field from the form:

class ArticleForm(forms.ModelForm):
    class Meta:
        model=Article
        exclude=('author',)
Daniel
Yes, it's a `ModelForm`. I needed `commit=False` for other reasons. An article is composed up of a whole bunch of stuff (including some m2m relations). I don't *think* it wanted to work with `instance`. I'll give this a try though.
Mark
In that case, I'd suggest putting the m2m relations (et al) saving/validation in the form instead of the view... either override the save method or possibly, look into formsets. I guess it depends on the context of what you're working with...
Daniel