Question
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
- Where would I set
article.id
in theadd
func? It would have to be afterform.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 theis_valid
check, but thenform.save
fails instead. - 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?
Answer
If you are extending your form from a ModelForm, use the instance
keyword
argument. Here we pass either an existing instance
or a new one, depending
on whether we're editing or adding an existing article. In both cases the
author
field is set on the instance, so commit=False
is not required. Note
also that I'm assuming only the author may edit their own articles, hence the
HttpResponseForbidden response.
from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect, render, reverse
@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:
return HttpResponseForbidden()
else:
article = Article(author=request.user)
form = ArticleForm(request.POST or None, instance=article)
if request.POST and form.is_valid():
form.save()
# Save was successful, so redirect to another page
redirect_url = reverse(article_save_success)
return redirect(redirect_url)
return render(request, template_name, {
'form': form
})
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',)