The pagination code requires a little explanation: first line creates the paginator with 2 items
per page. Normally you’d set it to something like 10 or 15, but in this case we want to create
just a few posts to illustrate pagination. The list of posts is ordered by created time in reverse
order.

The way pagination works is that your next/previous link at the bottom of the page will send the
page number in GET request and we’ll tell paginator object to use that page when sending a list
of posts to our template. When no page is given, we set page number to 1.

Finally, if there’s an error setting the page to given number, which usually means the page number
is too high (that can happen, for instance, if we delete some items and then use an old link) —
the sensible thing to do is to return the last page.

Our base template and front page template will be in templates/blog/bbase.html and
templates/blog/list.html:

If you’re wondering about linebreaks filter I’ve added after the body, it simply converts
newlines in body text into html line breaks.

I’ve also added a bit of styling and some sample blog posts (can you tell that I love
Wikipedia?)

Second page:

If you still remember the first tutorial, we made a small customization of change_list template
to show a link for adding multiple todo items. We need to do exactly the same thing here, except
that the template will live in blog/post/change_list.html and the link will be as follows:

We’ll also need a separate page for each post with visitors’ comments and full post text (if we
later decide to limit post body shown on front page). Here’s how I plan to set things up: the url
will be /blog/post/{pk}/ where pk is the primary key of post’s object; template will be called
post.html and the view function will be post().

Add the urlconf line; the number here will refer to the Post object, not Comment object:

(r"^add_comment/(\d+)/$","add_comment"),

We’re not going to do any sort of validation on comments — if Name is empty, we’ll simply have
it set to “Anonymous”. If both fields are empty, we’ll redirect right back:

fromdjango.formsimportModelFormclassCommentForm(ModelForm):classMeta:model=Commentexclude=["post"]defadd_comment(request,pk):"""Add a new comment."""p=request.POSTifp.has_key("body")andp["body"]:author="Anonymous"ifp["author"]:author=p["author"]comment=Comment(post=Post.objects.get(pk=pk))cf=CommentForm(p,instance=comment)cf.fields["author"].required=Falsecomment=cf.save(commit=False)comment.author=authorcomment.save()returnHttpResponseRedirect(reverse("dbe.blog.views.post",args=[pk]))

When Django creates the form from Comment model, the form will require Name to be filled in
because author property is not null. Validation is performed when form object is saved — we have
to turn the requirement off before that call. Even so, we can’t commit it because the model itself
will complain about a blank author: therefore we have to save without committing, set the author
and then save the model. That’s quite a few hoops we have to jump through here, but what can you
do.

CommentForm class should be clear enough — the only detail is that we’re ommitting the post
property from fields, otherwise the post pulldown would be shown in the form, and we definitely
don’t want that.

The post() view will have to provide a list of comments and a blank form now:

fromdjango.core.context_processorsimportcsrfdefpost(request,pk):"""Single post with comments and a comment form."""post=Post.objects.get(pk=int(pk))comments=Comment.objects.filter(post=post)d=dict(post=post,comments=comments,form=CommentForm(),user=request.user)d.update(csrf(request))returnrender_to_response("post.html",d)

Hopefully all of this looks clear; I won’t delve into csrf at this point but it should be enough
to know that this code is required in Django 1.2 when you have a POST form and it will protect you
and your visitors from CSRF attacks.