Django Blog Project #6: Creating standard blog views



This is my OLD blog. I've copied this post over to my NEW blog at:

http://www.saltycrane.com/blog/2008/06/django-blog-project-6-creating-standard/

You should be redirected in 2 seconds.



My last django post actually got a little bit of publicity-- a mention on Python reddit, and a mention in the Yahoo User Interface blog In the Wild for June 20 article. So I feel a little pressure to produce intelligent, engaging content so as not to disappoint the hordes of new readers I must now have. Unfortunately, the topic of this post is pretty dry. Goodbye, new readers. I'll try to find you another time.

The topic of this post is creating standard blog views. So far, I've created a simple blog application that allows me to log in to an Admin interface and enter blog posts, I added some post metadata such as date and tags, and, last time, I added YUI stylesheets to create a cross browser layout solution. However, until now, my trying-so-hard-to-be-a-real website only had a single front page view with a list of all the blog posts. I needed to add other views, such as a single post view, an archive view, and a tag view. Here are the changes I made to implement these views.

Warning: I am a newbie to Django and web development. I've created this blog series to document my journey in creating a blog application using Django. It does not necessarily represent Django best practices. To the extent of my knowledge, I try to follow Django conventions and philosophies, but I know I must still have a number of violations, particularly in this latest revision. Hopefully, as I learn better ways of doing things, I can post corrections. If you have corrections, please leave a comment.

For a much better example of a Django blog application, see Adam Gomaa's Django blog site source code repository. Though I don't understand everything in there, I've referenced it a number of times while creating my blog site. Update 2008-06-29: I just discovered that James Bennett, a Django contributer, also has blog application source code available online. Seeing as he has written a book on Django, this <sarcasm>might</sarcasm> be a good place to look as well.



models.py

In my models.py file, I added a slug field to store the last part of the url and a method called get_tag_list to return the tags for the post as a list.

~/src/django/myblogsite/myblogapp/models.py:
import re
from django.db import models

class Post(models.Model):
    title = models.CharField(maxlength=200)
    slug = models.SlugField(maxlength=100)
    date_created = models.DateTimeField()
    date_modified = models.DateTimeField(auto_now=True)
    tags = models.CharField(maxlength=200)
    body = models.TextField()

    def get_tag_list(self):
        return re.split(" ", self.tags)

    def __str__(self):
        return self.title

    class Meta:
        ordering = ["-date_created"]

    class Admin:
        pass


views.py

In my views.py file, to the frontpage view, I added a single post view, an archive view by year, an archive view by month, and a tag view. I also added a couple functions to create an archive index and a tag index.

~/src/django/myblogsite/myblogapp/views.py:
import re
from datetime import datetime
from django.shortcuts import render_to_response
from myblogsite.myblogapp.models import Post

MONTH_NAMES = ('', 'January', 'Feburary', 'March', 'April', 'May', 'June', 'July',
               'August', 'September', 'October', 'November', 'December')

def frontpage(request):
    posts, pagedata = init()
    pagedata.update({'subtitle': '',})
    return render_to_response('listpage.html', pagedata)

def singlepost(request, year, month, slug2):
    posts, pagedata = init()
    post = posts.get(date_created__year=year,
                            date_created__month=int(month),
                            slug=slug2,)
    pagedata.update({'post': post})
    return render_to_response('singlepost.html', pagedata)
    
def yearview(request, year):
    posts, pagedata = init()
    posts = posts.filter(date_created__year=year)
    pagedata.update({'post_list': posts,
                     'subtitle': 'Posts for %s' % year})
    return render_to_response('listpage.html', pagedata)

def monthview(request, year, month):
    posts, pagedata = init()
    posts = posts.filter(date_created__year=year)
    posts = posts.filter(date_created__month=int(month))
    pagedata.update({'post_list': posts,
                     'subtitle': 'Posts for %s %s' % (MONTH_NAMES[int(month)], year),})
    return render_to_response('listpage.html', pagedata)

def tagview(request, tag):
    allposts, pagedata = init()
    posts = []
    for post in allposts:
        tags = re.split(' ', post.tags)
        if tag in tags:
            posts.append(post)
    pagedata.update({'post_list': posts,
                     'subtitle': "Posts tagged '%s'" % tag,})
    return render_to_response('listpage.html', pagedata)

def init():
    posts = Post.objects.all()
    tag_data = create_tag_data(posts)
    archive_data = create_archive_data(posts)
    pagedata = {'version': '0.0.5',
                'post_list': posts,
                'tag_counts': tag_data,
                'archive_counts': archive_data,}
    return posts, pagedata

def create_archive_data(posts):
    archive_data = []
    count = {}
    mcount = {}
    for post in posts:
        year = post.date_created.year
        month = post.date_created.month
        if year not in count:
            count[year] = 1
            mcount[year] = {}
        else:
            count[year] += 1
        if month not in mcount[year]:
            mcount[year][month] = 1
        else:
            mcount[year][month] += 1
    for year in sorted(count.iterkeys(), reverse=True):
        archive_data.append({'isyear': True,
                             'year': year, 
                             'count': count[year],})
        for month in sorted(mcount[year].iterkeys(), reverse=True):
            archive_data.append({'isyear': False,
                                 'yearmonth': '%d/%02d' % (year, month),
                                 'monthname': MONTH_NAMES[month], 
                                 'count': mcount[year][month],})
    return archive_data

def create_tag_data(posts):
    tag_data = []
    count = {}
    for post in posts:
        tags = re.split(" ", post.tags)
        for tag in tags:
            if tag not in count:
                count[tag] = 1
            else:
                count[tag] += 1
    for tag, count in sorted(count.iteritems(), key=lambda(k, v): (v, k), reverse=True):
        tag_data.append({'tag': tag,
                         'count': count,})
    return tag_data


Templates
I made the following template changes:
  • I modified the sidebar in base.html to display an archive index and a tag index.
  • I replaced frontpage.html with a more generic listpage.html template used for displaying the frontpage, archives, and tag views.
  • I added a singlepost.html template for displaying a single post view.
  • In all the templates, I created links to navigate among the different views.
~/src/django/myblogsite/templates/base.html:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.2/build/reset-fonts-grids/reset-fonts-grids.css">
  <link rel="stylesheet" type="text/css" href="/site_media/css/mystyle.css">
  <title>{% block title %}Sofeng's Blog {{ version }}{% endblock %}</title>
</head>

<body>
  <div id="doc" class="yui-t4">

    <div id="hd">
      <h1>
        <img align="center" src="/site_media/image/crane.jpg">
        {% block header1 %}Sofeng's Blog {{ version }}{% endblock %}
      </h1>
      <h2>{% block header2 %}{% endblock %}</h2>
    </div>

    <div id="bd">
      <div id="yui-main">
        <div class="yui-b">
          {% block main %}{% endblock %}
        </div>
      </div>
    
      <div class="yui-b" id="sidebar">
        {% block sidebar %}
        <h4>ABOUT</h4>
        <p>This is my new blog created using <a href="http://www.djangoproject.com">Django</a>,
          a <a href="http://www.python.org">Python</a> web framework. This site is under 
          construction. My current blog is located at: 
          <a href="http://iwiwdsmi.blogspot.com">http://iwiwdsmi.blogspot.com</a>.
        </p>
        <br>
        <h4>TAGS</h4>
        {% for line in tag_counts %}
            <a href="/blog/tag/{{ line.tag }}/">{{ line.tag }}</a> ({{ line.count }})<br>
        {% endfor %}
        <br>
        <h4>ARCHIVE</h4>
        {% for line in archive_counts %}
          {% if line.isyear %}
            {% if not forloop.first %}
              <br>
            {% endif %}
              <a href="/blog/{{ line.year }}/">{{ line.year }}</a> ({{ line.count }})<br>
          {% else %}
                <a href="/blog/{{ line.yearmonth }}/">{{ line.monthname }}</a> ({{ line.count }})<br>
          {% endif %}
        {% endfor %}
        <br>
        {% endblock %}
      </div>
    </div>

    <div id="ft">
      Created with <a href="http://www.djangoproject.com/">Django</a>.<br>
      Hosted by <a href="http://www.webfaction.com/">Webfaction</a>.
    </div>
  </div>

</body>
</html>
~/src/django/myblogsite/templates/listpage.html:
{% extends "base.html" %}

{% block title %}
  Sofeng's Blog {{ version }}
  {% if subtitle %}:{% endif %}
  {{ subtitle }}
{% endblock %}

{% block header1 %}
  {% if subtitle %}
    <a href="/blog/">Sofeng's Blog {{ version }}</a>
  {% else %}
    Sofeng's Blog {{ version }}
  {% endif %}
{% endblock %}

{% block header2 %}
  {{ subtitle }}
{% endblock %}

{% block main %}
  {% for post in post_list %}
    <h3><a href="/blog/{{ post.date_created|date:"Y/m" }}/{{ post.slug }}/">
        {{ post.title }}</a>
    </h3>
    {{ post.body }}
    <hr>
    <div class="post_footer">
      {% ifnotequal post.date_modified.date post.date_created.date %}
        Last modified: {{ post.date_modified.date }}<br>
      {% endifnotequal %}
      Date created: {{ post.date_created.date }}<br>
      Tags: 
      {% for tag in post.get_tag_list %}
        <a href="/blog/tag/{{ tag }}/">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
      {% endfor %}
    </div>
    <br>
  {% endfor %}
{% endblock %}
~/src/django/myblogsite/templates/singlepost.html:
{% extends "base.html" %}

{% block title %}
  Sofeng's Blog {{ version }}: {{ post.title }}
{% endblock %}

{% block header1 %}
  <a href="/blog/">Sofeng's Blog {{ version }}</a>
{% endblock %}

{# to fix IE #}
{% block header2 %} {% endblock %}

{% block main %}
    <h3>{{ post.title }}</h3>
    {{ post.body }}
    <hr>
    <div class="post_footer">
      {% ifnotequal post.date_modified.date post.date_created.date %}
        Last modified: {{ post.date_modified.date }}<br>
      {% endifnotequal %}
      Date created: {{ post.date_created.date }}<br>
      Tags: 
      {% for tag in post.get_tag_list %}
        <a href="/blog/tag/{{ tag }}/">{{ tag }}</a>{% if not forloop.last %}, {% endif %}
      {% endfor %}
    </div>
    <br>
{% endblock %}


urls.py

As the last step in my bottom-up approach, I modified my urls.py and urls_webfaction.py.

~/src/django/myblogsite/urls_webfaction.py:
from django.conf.urls.defaults import *
from myblogsite.myblogapp.views import *

urlpatterns = patterns(
    '',
    (r'^admin/', include('django.contrib.admin.urls')),
    (r'^myview1/$', myview1),
    (r'^blog/$', frontpage),
    (r'^blog/(\d{4,4})/(\d{2,2})/([\w\-]+)/$', singlepost),
    (r'^blog/(\d{4,4})/$', yearview),
    (r'^blog/(\d{4,4})/(\d{2,2})/$', monthview),
    (r'^blog/tag/([\w\-]+)/$', tagview),
)


Finish

I uploaded, updated, and restarted the Apache server as usual.


Here is a snapshot screenshot of the site:

The live site is located at: http://saltycrane.com/blog/

Related posts:
   Django Blog Project #1: Creating a basic blog
   Django Blog Project #2: Deploying at Webfaction
   Django Blog Project #3: Using CSS and Template Inheritance
   Django Blog Project #4: Adding post metadata
   Django Blog Project #5: YUI CSS and serving static media
   Django Blog Project #7: Adding a simple Atom feed
   Django Blog Project #8: Adding basic comment functionality

2 comments:

Anonymous said...

Hey you need to use django.views.generic.date_based
as per the djangoproject weblog !!
Everything is there templates views etc.
This is save loads of time and agro!!
Chris

sofeng said...

Chris,
Thanks for the tip! It looks like I did a lot of extra work. I will definitely look to incorporate this next time.
-sofeng

About

This is my *OLD* blog. I've copied all of my posts and comments over to my NEW blog at:

http://www.saltycrane.com/blog/.

Please go there for my updated posts. I will leave this blog up for a short time, but eventually plan to delete it. Thanks for reading.