A complete blog engine using django in 60 minutes

Pagination and the Rss Feed

If you already have a bunch of blog posts in the database, you may have noticed that for example the blog index displays all of them. The list_detail views don’t paginate lists by default. This is nice, because django isn’t getting in your way. You can do whatever you want, if you’re just listing for example a bunch of punch lines that don’t occupy mare than one line, you may not want to paginate that, even if you have 100 or 200 punch lines in the database that will not generate such a big listing that needs to be sliced. But if you have a news listing and each news is big enough that you don’t want more than 3 per page, that’s up to you. Your best option is to tell each view that generates a listing how to paginate it. In our case we are using two of these views, object_list and archive_month. One of the options here is to add another argument to the blog_generic_view that will allow us to tell if the view is to be paginated or not. We can give it a default value of True, because at the moment we only have one view that we don’t want paginated, the single post view. Here’s how it looks:

def blog_generic_view(request, redirect_to, paginate = True, **view_args):
    view_args['queryset'] = view_args.get('queryset', Post.objects.all())
    view_args['template_object_name'] = 'post'
 
    if paginate:
        view_args['paginate_by'] = 5
 
    return redirect_to(request, **view_args)

Now we just need to update the single post url pattern to specifically tell it does not want to be paginated:

urlpatterns = patterns('',
    ...
   url(r'^post/(?P<slug>[a-z-]+)/$', blog_generic_view, 
        {'redirect_to': list_detail.object_detail, 'slug_field': 'slug', 'paginated': False,}, name="single_post"),
   ...
)

Its time to update the blog_list template:

{% extends "base.html" %}
 
{% block content %}
	{% if is_paginated %}
		<div class="pagination">
		    <span class="step-links">
		        {% if page_obj.has_previous %}
		            <a href="/?page={{ page_obj.previous_page_number }}">previous</a>
		        {% endif %}
 
		        <span class="current">
		            Posts {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
		        </span>
 
		        {% if page_obj.has_next %}
		            <a href="/?page={{ page_obj.next_page_number }}">next</a>
		        {% endif %}
		    </span>
		</div>
	{% endif %}
	{% if post_list.count %}
		{% for post in post_list %}
			{% include "blog/post.html" %}
		{% endfor %}
	{% else %}
		<p>There are no items to display...</p>
	{% endif %}
{% endblock %}

See, just before the post list, we create the pagination links. When we define the paginate_by argument in the listing views, a couple more extra template context variables are added. The is_paginated variable is a Boolean value that tells if this list is paginated, in this case, if we have less than 5 posts, this value will be false. Then there is another variable, page_obj which olds a couple of properties to help us out on building pagination links. Shouldn’t be hard to understand whats going on (take a peek on django docs for more info http://docs.djangoproject.com/en/1.1/topics/pagination/#topics-pagination ), just notice the href attributes of the links itself, they just add a get argument to the url with the page name, and this is what the listing generic views will look for to know which page to display.

Now if you don’t have more than 5 posts, add some more now and then go test this new feature.

Syndication feed

You already know the generic views and the comment framework and some other nice stuff, but you’re yet to know another django treat; the syndication-feed framework. To activate it, all you need to do is to add a small ull pattern into your blog app:

urlpatterns = patterns('',
    ...
   (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}),
)

Is just a simple url pattern, pointing to the feed framework, with an argument feed_dict. This argument is supposed to be a python dictionary mapping a feed slug (basically the rest of the url) to a feed class. That we can define directly in the urls.py file if we want to, but first let’s create a feed class. Create a new file under the blog app folder called feeds.py, inside that file create the following class:

from django.contrib.syndication.feeds import Feed
from mysite.blog.models import Post
 
class BlogLatestEntries(Feed):
    title = "The super Blog"
    link = "/"
    description = "The latest stuff in the blog."
 
    def items(self):
        return Post.objects.all()[:5]

First important thing to notice here is that this is a subclass django.contrib.syndication.feeds. There are a couple of class properties defined, title, link, and description. They correspond to RSS standard feed properties. Finally, there’s a method tems(), which purpose is to return the queryset on which this feed will be based. Now we can create the feed_dict in urls.py:

...
from mysite.blog.feeds import BlogLatestEntries
 
feeds = {
    'latest': BlogLatestEntries,
}
 
urlpatterns = patterns('',
...

And of course, django does not know how to map a Post object to a feed entry, we have to tell it how to do it. By default, the syndication framework will look for two templates for the title and description, named feeds/<feed slug>_title.html and feeds/<feed slug>_description.html respectively, where feed slug is the bit of the url after feed. Inside those templates, each queryset item is available through the context variable obj. To get the link, it looks for a get_absolute_url method in the model class, or item_link, in case the first option fails. This is just fine for simple feeds, so we can use those. The feeds/latest_title.html template can be defined simply as:

{{ obj.title }}

And feeds/latest_description.html:

{{ obj.body }}

And we’ll define a get_absolute_url in the Post model class:

    ...
    objects = PostManager()
 
    @models.permalink
    def get_absolute_url(self):
        return ('single_post', [self.slug])
 
    def __unicode__(self):
    ...

This method uses a python decorator models.permalink, which will pre-process the return value and convert it into to a url, just like the url template tag does. The return value must be a tuple containing the name of pattern, a list of positional argument to the view, and a dictionary of named arguments to the view, which we omit in this case, using just a positional argument, since we know that particular pattern only has one argument. We could have simply used a formatted string to generate the correct url and return it without using any decorator, but we would have violated the DRY principles.

Now just take a quick tour to http://127.0.0.1:8000/feeds/latest/ and see for yourself. Really cool uh?

Share and Enjoy:
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay
  • DZone

Pages: 1 2 3 4 5 6 7 8 9 10

79 Comments

  1. SMiGL says:

    Nice post. Thanks

  2. seifsallam says:

    Greate

  3. [...] This post was mentioned on Twitter by Andrés Nieto, Jordi Cabot, Richard Laksana, Paulo Suzart, Matias Carranza and others. Matias Carranza said: RT @aNieto2k: http://bit.ly/5sgOm4 <– Crea un blog engine usando #django en 60 minutos. [...]

  4. Social comments and analytics for this post…

    This post was mentioned on Twitter by rlaksana: A complete blog engine using django in 60 minutes – http://su.pr/1PhNlx...

  5. HRCerqueira says:

    Thanks for the positive feedback

  6. Fernando says:

    Nice tutorial, well, on the page 7 , in the fist code box, the url pattern, i cant see the code with my brooser i see:

    “url(r’^post/(?P ”

    can someone helpe me with the right pattern?

    im using iceweasel and epiphany was broser.

  7. HRCerqueira says:

    Hi Fernando

    On top of my head i think is this:
    url(r’^post/(?P\d+)/$’, list_detail.object_detail, {‘queryset’: Post.objects.all(), ‘template_object_name’: ‘post’,}, name=”single_post”)

    Don’t know why (I think is the wysiwyg messed up the code) that code box was partially replaced by a flash object. Anyway I think is fixed now, I just reposted the code here just to be safe. Hope it helps.

    Cheers

  8. Luke says:

    Great tutorial! I have been using django for a while, but never really dived into the generic views and feed framework. I learned quite a bit from going through the entire tutorial. I also created a git repo of the entire tutorial on github while going through it step-by-step. The repo is at http://github.com/durden/django_blog. Let me know what you think!

  9. tamara says:

    life insurance no physical >:P low income health insurance 994945 cheap california auto insurance %-)) homeowners insurance in florida umbody

  10. Alexander says:

    error at / unknown specifier: ?P.
    I get this error when add the line on page 2 to url.py.
    What am I doing wrong?

  11. satlist says:

    >:PPP florida auto insurance =) auto insurance >:DDD auto insurance quotes 992

  12. reiven says:

    hi! great tutorial, i’ve found a couple of typos in the explanation but it works fine.
    i just want to know if you can recommend any “tag cloud” system for django. thanks

  13. Jorge says:

    Hi!

    Great tutorial, i’ve only got a question about the beginnings of page 7. When editing post_detail.html and adding {% url single_post slug=post.slug %} it shows:

    TemplateSyntaxError at /

    Caught an exception while rendering: Reverse for ’single_post’ with arguments ‘()’ and keyword arguments ‘{’slug’: u’las-cosas-van-bien’}’ not found.

    My urls.py is exactly as it has to be at that part of the tutorial:

    from django.conf.urls.defaults import *
    from django.views.generic import list_detail
    from mysite.blog.models import Post

    urlpatterns = patterns(”,
    url(r’^$’, list_detail.object_list, {‘queryset’: Post.objects.all(), ‘template_object_name’: ‘post’,}, name=”blog_home”),
    url(r’^post/(?P\d+)/$’, list_detail.object_detail, {‘queryset’: Post.objects.all(), ‘template_object_name’: ‘post’,}, name=”single_post”),
    )

    Hope you can help.

    • Jorge says:

      I’ve got it

      Two things to fix:

      1.- Firts, my fault, the url patterns, change the order, first the “/post/” and second the “^$” one.
      2.- Your fault :-) , the {% url single_post slug=post.slug %} it may be {% url single_post object_id=post.id %}. It works for me, if there is another solution, i will apretiate it.

    • vpkdyvgyrxe says:

      Dts4qT frwkvpcyffsd, [url=http://lqfshxiixcgy.com/]lqfshxiixcgy[/url], [link=http://khcdkpvmbgpo.com/]khcdkpvmbgpo[/link], http://oejbjxszygmb.com/

  14. HRCerqueira says:

    Thanks for the comments guys. I’ve been flooded with work in the last weaks or so, and I was pretty much ignoring comments because of this freakin spam, until I noticed that some of there were legitim :-D

    @reiven, I don’t know any, sorry, but is a cool idea for an article when i got the time :-P

    @jorge, you can allways try this pattern:
    url(r’^post/(?P[\w-]+)/$’, list_detail.object_detail, {‘queryset’: Post.objects.all(), ‘template_object_name’: ‘post’,}, name=”single_post”)
    It should work without modifying the template and you get a pretty url :-)

  15. Vernon says:

    Hi, I am struggling to understand one thing, why do you import the mysite.blog.views in the blog.urls.py file?

    from mysite.blog.views import Post

    I am trying to adapt your tutorial to an existing site I have, and I don’t see where you added anything to the mysite.blog.views.py file?

    It keeps throwing up an error for me, both in my site, and also if I run “python manage.py shell” and try to import it.

    What should, but this point in the tutorial, be in the blog.views.py file?

    • Vernon says:

      Sorry, I am referring to the sixth page of the tutorial…I didn’t realize that your comments were for the whole thing. This section is where I run into trouble:

      from django.conf.urls.defaults import *
      from django.views.generic import list_detail
      from mysite.blog.views import Post

      urlpatterns = patterns(”,
      url(r’^$’, list_detail.object_list, {‘queryset’: Post.objects.all(), ‘template_object_name’: ‘post’,}, name=”blog_home”),
      )

    • xzkazy says:

      wyTXkM uhzymzrglfin, [url=http://qyiaqzfvgrkz.com/]qyiaqzfvgrkz[/url], [link=http://hnqpbpxmcwgc.com/]hnqpbpxmcwgc[/link], http://ygagfedicxlb.com/

  16. Vernon says:

    After this line:

    “Open your sttings.py file look for the INSTALLED_APPS setting and add ‘django.contrib.comments’ to the list.”

    You need to add that you need to run “manage.py syncdb” again to get it to work. (and change sttings.py to settings.py)

  17. uhexvzv says:

    JdjAle zkmammoukssl, [url=http://xqxsjohpapqo.com/]xqxsjohpapqo[/url], [link=http://vcmpgicffttc.com/]vcmpgicffttc[/link], http://pderjagieumo.com/