Django Blog Project #6: Creating standard blog views
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 genericlistpage.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
Comments
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
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
Ah come on - your site comes up on the se arch list 'create a standard blog', and it all looks so interesting.. your linky thingy just wasted part of my 500mb's.. please change your key words or what ever brings your site to the fore to_html~remove_my_blog_which_really_has(nothing:NO_thing&to?say>about?anything=that/affects]us=in#the'the£real*world[
Thanks for a lot of information,I just get in trouble of month archive and your article save my life
disqus:2673551886
I have some links in my post body and they are displayed as plain text, like: my text, how can I display them like 'my text' like a link.
disqus:3103704601