Matthew Daly's Blog

I'm a web developer in Norfolk. This is my blog...

5th September 2012 12:13 am

Counting Commits in Git

The other day I got to wondering about how I could quickly and easily count how many commits I was making to a specific Git repository every day.

Now, with the default log output, Git shows the date and time at the top of each entry. So in order to determine how many commits have been made to a given repository, all you need to do is determine today’s date, and see how many times that date appears in the log.

So, first task is to get the log. Then, we need to get today’s date, format it appropriately, and use grep to filter for just the lines containing that date. So, we use the date command to fetch the date, then pipe it into awk, which we use to format it the same way as Git does. We then use grep to filter out the lines that have the resulting date. Finally, we use wc to count the number of lines returned by grep.

Here’s what I came up with:

git log | grep "`date | awk '{print $1, $2, $3}'`" | wc -l

I’m not that great with tools like awk, so I’m quite proud of managing to do this. It’s quite handy to have around.

13th August 2012 7:54 pm

Falling Back to Local Copies of Jquery and Jquery Mobile

I use jQuery Mobile a lot at work, and it’s brilliant. For quickly knocking together a high-quality user interface that works well on mobile devices, it’s unbeatable.

Like many web developers, I favour using a CDN-hosted version of both jQuery and jQuery Mobile, because it makes it more likely that the user won’t have to download the appropriate files as their web browser has already cached them. However, by doing this you run the risk of your site being negatively affected if the CDN provider goes down for any length of time. So, I think it’s a good idea to have a fallback for both jQuery and jQuery Mobile.

I spent a while looking and finally managed to come up with a solution that works well, and borrows heavily from a similar solution for jQuery UI by Tim James. First of all, load the stylesheets and JavaScript files as usual:

<link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.1/jquery.mobile.structure-1.1.1.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.js"></script>

Below this, place the following inside another set of script tags:

if (typeof jQuery == 'undefined')
{
document.write(unescape("%3Cscript src='http://path-to-jquery.min.js' type='text/javascript'%3E%3C/script%3E"));
}

If jQuery is not defined, this will write another set of script tags that will load the local copy of jQuery. Now, inside a second set of script tags, place the following code:

if(typeof $.mobile == 'undefined')
{
document.write(unescape("%3Cscript src='http://path-to-jquery-mobile.min.js' type='text/javascript'%3E%3C/script%3E"));
}
$(function() {
if($('.ui-helper-hidden:first').is(':visible') === true){
$('<link rel="stylesheet" type="text/css" href="http://path-to-jquery-mobile-structure.min.css" />').appendTo('head');
}
});

This won’t work if it’s placed in the same set of script tags as the code above, because it requires that jQuery be working already. The first part works similarly to the jQuery fallback - if jQuery Mobile is not defined, it writes a new script tag. The second relies on an element in the DOM with a class of ui-helper-hidden, which jQuery Mobile would hide by default if it were loaded. If it is visible, the jQuery Mobile structure CSS file has not been loaded and so a link to the local copy of the stylesheet is created. Of course, this means you have to create this element, so add the following code to the very top of the body, directly under the opening body tag:

<div class='ui-helper-hidden'></div>

If you download copies of the appropriate files and set the paths to them correctly, you should now be able to enjoy all the advantages of using a CDN for hosting jQuery and jQuery Mobile while also having the security of knowing that if the CDN goes down, your application will still work fine. Exactly the same approach will work with jQuery UI as well.

31st May 2012 9:25 pm

Dumping Wordpress for Octopress

Lately I’ve gotten more and more fed up with WordPress as a blogging platform. It’s certainly a great content management system, and for non-technical bloggers it’s absolutely perfect, but I’ve been increasingly finding myself hitting barriers that make it harder than it should be to get stuff done. It’s just not the best platform for blogging about web development.

Take my recent series of Django tutorials, for example. You wouldn’t believe the amount of time I spent trying to format some of the code properly in TinyMCE for those. Furthermore, the end result, even though the theme was a custom one I’d built myself that was somewhat optimised for showing off code, wasn’t exactly great.

In my opinion, Markdown is a far better option for writing blog posts in than using a rich text editor like TinyMCE. You normally have a pretty good idea what the end results will look like, and it’s generally well-formatted HTML. Also, it means I can easily write my blog posts in Vim.

Octopress was therefore an obvious choice. It’s absolutely brilliant for sharing code - compared to WordPress, code samples look stunning. Also, because it generates static HTML, it loads an awful lot faster than WordPress does, and the default theme is extremely nice. So that’s what I’ve gone for.

The only issue is that my web host are a bit funny about offering SSH access, so I’ve resorted to FTP-ing the files across, which isn’t ideal. Still, it’s not that big an issue, and I’ll have to see how it goes with Octopress. I do have a low-end VPS I could point this domain name at instead and run it from there if necessary, so I can always resort to that if this is too cumbersome.

23rd April 2012 9:55 pm

Yet Another Tutorial for Building a Blog Using Python and Django - Part 5

In this instalment I’ll be showing you how we can make our blogging engine a little nicer to look at by adding some CSS and images, as well as expanding on Django’s templating system.

First of all, let’s add some CSS to our blog. When developing a web app with Django, you should place static files such as stylesheets and images in a folder inside your app (not project) folder called static. My project is called DjangoBlog, and my app is called blogengine, so all my static content should go in DjangoBlog/blogengine/static/. Here’s the stylesheet, which I’ve saved as style.css:

body {
background-color: #f0f0f0;
font-family: Arial, Helvetica, sans-serif;
}
#main {
width: 800px;
height: 100%;
margin: 50px auto;
}
ul#pageList {
margin: 0px;
padding: 10px 0px 10px 0px;
}
ul#pageList li {
display: inline;
margin-right: 10px;
font-size: 18px;
}
.post, .page {
width: 600px;
padding: 20px;
margin-bottom: 20px;
background-color: #ffffff;
}

In the same folder, I have a PNG icon for an RSS feed, and if you had some JavaScript files you wanted to use (such as a copy of jQuery), you would put them here too. Note that there’s nothing to stop you creating subfolders within /static, and in fact I would recommend you do so for any future project so you can separate out images, CSS and JavaScript easily.

With that done, we now need to change our templates to make use of this CSS. Here’s what header.html should look like:

<html>
<head>
<title>My Django Blog</title>
<link rel="stylesheet" type="text/css" href="/static/style.css" />
</head>
<body>
<div id="main">
<h1>My Django Blog</h1>
<a href="/feeds/posts/"><img src="/static/rss.png" width="50px" height="50px"></a>
<ul id="pageList">
<li><a href="/">Home</a></li>
{% load flatpages %}
{% get_flatpages as flatpages %}
{% for flatpage in flatpages %}
<li><a href="{{ flatpage.url }}">{{ flatpage.title }}</a></li>
{% endfor %}
</ul>

Next, here’s footer.html:

</div>
</body>
</html>

Now here’s category.html:

{% include 'header.html' %}
{% load comments %}
<h1>Posts for {{ category.title }}</h1>
{% if posts %}
{% for post in posts %}
<div class="post">
<h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
{{ post.text }}
{% get_comment_count for post as comment_count %}
<h3>Comments: {{ comment_count }}</h3>
</div>
{% endfor %}
<br />
{% if page.has_previous %}
<a href="/{{ page.previous_page_number }}/">Previous Page</a>
{% endif %}
{% if page.has_next %}
<a href="/{{ page.next_page_number }}/">Next Page</a>
{% endif %}
{% else %}
<div class="post">
<p>No posts matched</p>
</div>
{% endif %}
{% include 'footer.html' %}

Then, posts.html:

{% include 'header.html' %}
{% load comments %}
{% if posts %}
{% for post in posts %}
<div class="post">
<h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
{{ post.text }}
{% get_comment_count for post as comment_count %}
<h3>Comments: {{ comment_count }}</h3>
</div>
{% endfor %}
<br />
{% if page.has_previous %}
<a href="/{{ page.previous_page_number }}/">Previous Page</a>
{% endif %}
{% if page.has_next %}
<a href="/{{ page.next_page_number }}/">Next Page</a>
{% endif %}
{% else %}
<div class="post">
<p>No posts matched</p>
</div>
{% endif %}
{% include 'footer.html' %}

Here’s single.html:

{% include 'header.html' %}
{% load comments %}
{% for post in posts %}
<div class="post">
<h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
<h3>{{ post.pub_date }}</h3>
{{ post.text }}
<h3>By {{ post.author.first_name }} {{ post.author.last_name }}</h3>
<h3>Categories: {% for category in post.categories.all %} {{ category.title }} {% endfor %}</h3>
{% get_comment_count for post as comment_count %}
<h3>Comments: {{ comment_count }}</h3>
<ol>
{% get_comment_list for post as comments %}
{% for comment in comments %}
<li>{{ comment }}</li>
</ol>
{% endfor %}
{% render_comment_form for post %}
{% endfor %}
</div>
<br />
{% if page.has_previous %}
<a href="/{{ page.previous_page_number }}/">Previous Page</a>
{% endif %}
{% if page.has_next %}
<a href="/{{ page.next_page_number }}/">Next Page</a>
{% endif %}
{% include 'footer.html' %}

And finally, flatpages/default.html:

{% include 'header.html' %}
<div class="page">
<h1>{{ flatpage.title }}</h1>
{{ flatpage.content }}
</div>
{% include 'footer.html' %}

Phew! There’s quite a lot there, so you may wish to grab these files from the GitHub repository rather than enter them yourself.

Now, all of the references to the CSS or image file need to refer to the /static folder under the root of the web server. Here’s the reference to our stylesheet:

<link rel="stylesheet" type="text/css" href="/static/style.css" />

And here’s where we get the image:

<a href="/feeds/posts/"><img src="/static/rss.png" width="50px" height="50px"></a>

All of our static files can be referenced via the /static folder by default, without needing to set up a rule to cover them in urls.py.

One other point worth noting is that we’ve added some code to the header to display links to all of the flat pages. This particular snippet of code in header.html is noteworthy:

{% load flatpages %}
{% get_flatpages as flatpages %}
{% for flatpage in flatpages %}
<li><a href="{{ flatpage.url }}">{{ flatpage.title }}</a></li>
{% endfor %}

Here, we first of all load the flat pages. Then we retrieve them, and loop through each of them. For each page, we create a new list item containing a link to the flat page, with the text being the flat page’s title. Note that we’re just referring to each flatpage object’s attributes here. Then we end the for loop.

The only problem with this is that all of the pages except the flat pages are handled by the blogengine application, not the flatpages one, so we can’t get the values for the flat pages, so we need to amend blogengine/views.py. Open it and add the following line near the top:

from django.contrib.flatpages.models import FlatPage

Now, nowhere else in the view is the FlatPage application needed, but it’s required in the template, so by importing it here we make it available in the template.

With that done, our Django-powered blog is beginning to look a bit more presentable, so I’ll leave it to you to style it however you wish, using this as a starting point. The blog is now pretty much feature-complete, however there’s one more thing I’d like to demonstrate before we finish up, namely generic views.

As you may have gathered by now, Django uses slightly different terminology to many other web development frameworks. Although it can be considered an MVC (Model-View-Controller) framework like many others, it’s generally referred to as an MTV (Model-Template-View) framework, with views containing the logic needed to present the data. While Django ships with a number of built-in applications to do certain repetitive tasks easily, not every task lends itself well to being handled by one generic application. However, these tasks may still require something similar be implemented over and over again, and that’s what generic views are for.

We don’t yet have a list of all of the available categories, so let’s use a generic view to do that. In urls.py, add the following lines at the top:

from django.views.generic import ListView
from blogengine.models import Category

Then, add the following lines at the top of the section for categories:

url(r'^categories/?$', ListView.as_view(
model=Category,
)),

Then, go into your templates folder and create a new folder in there called blogengine (or whatever you’re calling your blog application). In there, create a new file called category_list.html and enter the following code in it:

{% include 'header.html' %}
{% for category in object_list %}
<h3>{{ category.title }}</h3>
<p>{{ category.description }}</p>
{% endfor %}
{% include 'footer.html' %}

Now, ensure the development server is running, and go to http://127.0.0.1:8000/categories/, and you should see a list of your categories.

Now, you didn’t write a view for this at all. Instead, this is handled by a generic view. In urls.py, we imported the ListView generic view, which is nothing more than a list of objects. We then import the Category model. Then, we define a URLconf that maps the categories/ url to ListView, which displays a list of all the Category objects. The template used is determined automatically, and we create that template as normal. Note that in the template we refer to object_list - this demonstrates that we’re referring to the objects passed through generically, and in theory this same template could display any objects with attributes called title and description.

Now, it probably won’t have escaped your notice that a blog is effectively a list of posts, so can’t we use a generic view to display them? Well, yes we can! So why don’t we cut down on the amount of code we need to maintain and use a generic view, rather than writing our own view?

Go into blogengine/views.py and delete the getPosts function in its entirety. Next, go into urls.py and delete the part that deals with showing the posts (the two lines just under the Home page comment), and replace them with this:

url(r'^(?P<page>\d+)?/?$', ListView.as_view(
model=Post,
paginate_by=5,
)),

Note here that we specify how many items we paginate by. The ListView generic view supports pagination, making it ideally suited for any list of objects that may be spread across multiple pages - you just import the model you want and pass it through in the model parameter.

We also need to import the Post object in urls.py. Amend the line where you imported the Category model as follows:

from blogengine.models import Category, Post

Then move posts.html into your templates/blogengine folder and rename it post_list.html, then amend it to look like the following:

{% include 'header.html' %}
{% load comments %}
{% if object_list %}
{% for post in object_list %}
<div class="post">
<h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
{{ post.text }}
{% get_comment_count for post as comment_count %}
<h3>Comments: {{ comment_count }}</h3>
</div>
{% endfor %}
<br />
{% if page_obj.has_previous %}
<a href="/{{ page_obj.previous_page_number }}/">Previous Page</a>
{% endif %}
{% if page_obj.has_next %}
<a href="/{{ page_obj.next_page_number }}/">Next Page</a>
{% endif %}
{% else %}
<div class="post">
<p>No posts matched</p>
</div>
{% endif %}
{% include 'footer.html' %}

The changes here are minimal, just adjusting the names of objects to what the generic view uses. Now, if you refresh the browser, you should be able to see your blog posts, only now they’re being handled by Django’s ListView generic view. But they’re in the wrong order, so go into blogengine/models.py and add this code to the Post model:

class Meta:
ordering = ["-pub_date"]

This defines the order the Post objects should be in the model, rather than the view. If you now refresh the browser, they should be in the right order.

A ListView is only one of the generic views available in Django. There are others that are useful under other circumstances, but they’re beyond the scope of this tutorial, so I suggest that if you’re interested, you take the time to learn more about them on your own. They can save you a lot of time and effort if used well.

Sadly, that brings this series of tutorials to an end. I hope you’ve enjoyed learning about Django, and I hope you’ll be inspired to build something cool with it! As always, the code is available on GitHub, so feel free to download it, use it as the basis for your own projects, or whatever else you’d like to do with it.

29th March 2012 9:29 pm

Yet Another Tutorial for Building a Blog Using Python and Django - Part 4

Welcome back! In this tutorial we’ll continue extending our Django-powered blogging engine. We’ll add the capability to assign blog posts to categories, and comment on posts. We’ll also generate an RSS feed for our blog posts.

Categories are somewhat tougher to implement than most of what we’ve done beforehand. One category can be assigned to many blog posts, and many categories can be assigned to one blog post, so this relationship is described as a “many to many relationship” when drawing up the database structure. What it means is that you can’t directly map categories onto posts and vice versa - you have to create an intermediate database table for the relationship between posts and categories.

Here’s what your models.py should look like:

from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Category(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=40, unique=True)
description = models.TextField()
class Meta:
verbose_name_plural = "Categories"
def __unicode__(self):
return self.title
def get_absolute_url(self):
return "/categories/%s/" % self.slug
class Post(models.Model):
title = models.CharField(max_length=200)
pub_date = models.DateTimeField()
text = models.TextField()
slug = models.SlugField(max_length=40, unique=True)
author = models.ForeignKey(User)
categories = models.ManyToManyField(Category, blank=True, null=True, through='CategoryToPost')
def __unicode__(self):
return self.title
def get_absolute_url(self):
return "/%s/%s/%s/" % (self.pub_date.year, self.pub_date.month, self.slug)
class CategoryToPost(models.Model):
post = models.ForeignKey(Post)
category = models.ForeignKey(Category)

We’re adding quite a bit of new code here. First of all we’re defining a new model called Category. Each category has a title, a description, and a slug (so we can have a dedicated page for each category). As usual, we define methods for unicode and get_absolute_url, but also note the class Meta. Here we’re defining some metadata for the class (ie, data about the data). The only thing we do here is essentially telling the admin interface that the plural of Category is not “Categorys” but “Categories”.

Then, in Post we add an additional field called Category, which we define as a ManyToManyField. Note the parameters passed through - we’re saying here that a post need not be assigned a category, and that CategoryToPost should be used as an intermediate table to link posts to categories.

Finally, we define the aforementioned CategoryToPost model, which has two fields, post and category. Both of these are foreign keys, mapping to a blog post and a category respectively. By creating entries in this table, a link can be created between a post and a category.

With our model changed, it’s time to update our admin.py as well:

import models
from django.contrib import admin
from django.contrib.auth.models import User
class CategoryAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
class CategoryToPostInline(admin.TabularInline):
model = models.CategoryToPost
extra = 1
class PostAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
exclude = ('author',)
inlines = [CategoryToPostInline]
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
admin.site.register(models.Post, PostAdmin)
admin.site.register(models.Category, CategoryAdmin)

Here we define a new class called CategoryAdmin, which details how we’re changing the admin interface for Category from the defaults generated from the fields provided. The only change we make here is that we prepopulate the slug field from the title, much like we did with blog posts.

Next, we define an inline for the relationships between categories and post, called CategoryToPostInline. This is a new concept - essentially it means that the category to post relationships can be defined in another model’s admin interface. We define the model this applies to, and that by default we will only add one additional field for adding categories when writing or editing a post (though users can add as many as they wish, or none). Note that the model this is based on is admin.TabularInline - this represents a tabular layout. If you prefer, you can use an alternative layout by using StackedInline instead.

Then, in PostAdmin we add our newly declared CategoryToPostInline to the PostAdmin class as an inline. Finally, at the bottom we register Category with the admin interface, so we can create and manage categories easily.

With that done, it’s time to edit our views.py:

# Create your views here.
from django.shortcuts import render_to_response
from django.core.paginator import Paginator, EmptyPage
from blogengine.models import Post, Category
def getPosts(request, selected_page=1):
# Get all blog posts
posts = Post.objects.all().order_by('-pub_date')
# Add pagination
pages = Paginator(posts, 5)
# Get the specified page
try:
returned_page = pages.page(selected_page)
except EmptyPage:
returned_page = pages.page(pages.num_pages)
# Display all the posts
return render_to_response('posts.html', { 'posts':returned_page.object_list, 'page':returned_page})
def getPost(request, postSlug):
# Get specified post
post = Post.objects.filter(slug=postSlug)
# Display specified post
return render_to_response('single.html', { 'posts':post})
def getCategory(request, categorySlug, selected_page=1):
# Get specified category
posts = Post.objects.all().order_by('-pub_date')
category_posts = []
for post in posts:
if post.categories.filter(slug=categorySlug):
category_posts.append(post)
# Add pagination
pages = Paginator(category_posts, 5)
# Get the category
category = Category.objects.filter(slug=categorySlug)[0]
# Get the specified page
try:
returned_page = pages.page(selected_page)
except EmptyPage:
returned_page = pages.page(pages.num_pages)
# Display all the posts
return render_to_response('category.html', { 'posts': returned_page.object_list, 'page': returned_page, 'category': category})

Here we import the Category model as well as the Post model. Then, the only additional change we need to make is to add a brand new getCategory view function. Note that this is quite similar to the getPosts function - we set up pagination in the same way, and rather than get all the posts, we get just those in the specified category. Also note that we’re using the template category.html rather than posts.html here, and we pass through category as well as posts and page when we return the render_to_response.

The next change we need to make is adding category.html. Go into your template directory and save the code below as category.html:

{% include 'header.html' %}
<h1>Posts for {{ category.title }}</h1>
{% if posts %}
{% for post in posts %}
<h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
{{ post.text }}
{% endfor %}
<br />
{% if page.has_previous %}
<a href="/{{ page.previous_page_number }}/">Previous Page</a>
{% endif %}
{% if page.has_next %}
<a href="/{{ page.next_page_number }}/">Next Page</a>
{% endif %}
{% else %}
<p>No posts matched</p>
{% endif %}
{% include 'footer.html' %}

With our template in place, the last step is to add an appropriate URLconf. Edit urls.py to look like this:

from django.conf.urls.defaults import patterns, include, url
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'blog.views.home', name='home'),
# url(r'^blog/', include('blog.foo.urls')),
# Uncomment the admin/doc line below to enable admin documentation:
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
# Home page
url(r'^$', 'blogengine.views.getPosts'),
url(r'^(?P<selected_page>\d+)/?$', 'blogengine.views.getPosts'),
# Blog posts
url(r'^\d{4}/\d{1,2}/(?P[-a-zA-Z0-9]+)/?$', 'blogengine.views.getPost'),
# Categories
url(r'^categories/(?P<categorySlug>\w+)/?$', 'blogengine.views.getCategory'),
url(r'^categories/(?P<categorySlug>\w+)/(?P<selected_page>\d+)/?$', 'blogengine.views.getCategory'),
# Flat pages
url(r'', include('django.contrib.flatpages.urls')),
)

Now, if you run python manage.py syncdb again, the category system should be up and running.

The next step is to add the facility to handle comments. Again, Django has its own application built in for handling comments, so go into setings.py and enter the following under INSTALLED_APPS:

     'django.contrib.comments',

Then run python manage.py syncdb again to generate the appropriate database tables. You’ll also need to amend urls.py to provide a dedicated URL for comments:

# Comments
url(r'^comments/', include('django.contrib.comments.urls')),

Place this before the URLconf for the flat pages.

Comments can be attached to any type of content, but we only want to attach them to blog posts, and they should only be visible in the single post template. But first of all, let’s add a comment count to posts in posts.html and category.html. Replace posts.html with this:

{% include 'header.html' %}
{% load comments %}
{% if posts %}
{% for post in posts %}
<h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
{{ post.text }}
{% get_comment_count for post as comment_count %}
<h3>Comments: {{ comment_count }}</h3>
{% endfor %}
<br />
{% if page.has_previous %}
<a href="/{{ page.previous_page_number }}/">Previous Page</a>
{% endif %}
{% if page.has_next %}
<a href="/{{ page.next_page_number }}/">Next Page</a>
{% endif %}
{% else %}
<p>No posts matched</p>
{% endif %}
{% include 'footer.html' %}

And replace category.html with this:

{% include 'header.html' %}
{% load comments %}
<h1>Posts for {{ category.title }}</h1>
{% if posts %}
{% for post in posts %}
<h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
{{ post.text }}
{% get_comment_count for post as comment_count %}
<h3>Comments: {{ comment_count }}</h3>
{% endfor %}
<br />
{% if page.has_previous %}
<a href="/{{ page.previous_page_number }}/">Previous Page</a>
{% endif %}
{% if page.has_next %}
<a href="/{{ page.next_page_number }}/">Next Page</a>
{% endif %}
{% else %}
<p>No posts matched</p>
{% endif %}
{% include 'footer.html' %}

The only significant changes here are that at the top we load comments, and underneath the post text we get the comment count for each post as the variable comment_count, then we display it underneath.

Now, we want to go further with our single post template. As well as a comment count, we want to add the actual comments themselves. Finally, we need a form for adding comments - in theory you can use the admin interface for doing this, but it’s very unlikely you’d want to do so. Open up single.html and edit it to look like this:

{% include 'header.html' %}
{% load comments %}
{% for post in posts %}
<h1><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h1>
<h3>{{ post.pub_date }}</h3>
{{ post.text }}
<h3>By {{ post.author.first_name }} {{ post.author.last_name }}</h3>
<h3>Categories: {% for category in post.categories.all %} {{ category.title }} {% endfor %}</h3>
{% get_comment_count for post as comment_count %}
<h3>Comments: {{ comment_count }}</h3>
<ol>
{% get_comment_list for post as comments %}
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ol>
{% render_comment_form for post %}
{% endfor %}
<br />
{% if page.has_previous %}
<a href="/{{ page.previous_page_number }}/">Previous Page</a>
{% endif %}
{% if page.has_next %}
<a href="/{{ page.next_page_number }}/">Next Page</a>
{% endif %}
{% include 'footer.html' %}

This includes the same changes as the other two templates, so we load comments and display the comment count. Afterwards, we get the comment list for this post as comments, and then loop through the comments, showing them in an ordered list. Afterwards, we then use render_comment_form to show the default comment form for this post. If you’d prefer to create your own comment form, you can use get_comment_form instead to get a form object you can use in the template.

You’ll also need to make some minor changes to the view to get the form working. Save single.html and open blogengine/views.py and add the following line of code to your import statements:

from django.template import RequestContext

Then, amend the final line of the getPost function as follows:

    return render_to_response('single.html', { 'posts':post}, context_instance=RequestContext(request))

The reason this needs to be changed is that the comment form includes the {% csrf_token %} tag, which requires information from the request object, and in order to do so rather than the default context, you need to pass through a RequestContext object instead, but don’t worry too much about the details.

If you now ensure the development server is running and visit a blog post, you should now see that you can post comments. If you want to enhance this very basic comment form, take a look at the excellent documentation on the Django website. Alternatively, there are a number of third-party comment services, such as Disqus and IntenseDebate that can handle comments for you and just require you to paste a snippet of code into whatever template you want to enable comments on, and these may be more convenient.

Finally for this lesson, as promised, we’ll implement our RSS feed. Again, there’s an application bundled with Django that will do this - the syndication framework. Open settings.py and paste the following line in at the bottom of your INSTALLED_APPS:

     'django.contrib.syndication',

Save the file and run python manage.py syncdb to add the appropriate tables to your database. Then, we need to add a URLconf for the RSS feed. We’ll allow a consistent naming scheme for RSS feeds, so this will be /feeds/posts, and if you wanted to you could add /feeds/comments, for instance. Add this to you urls.py, before the url for flat pages:

# RSS feeds
url(r'^feeds/posts/$', PostsFeed()),

We’ll also need to tell urls.py where to find PostsFeed(). In this case, we’re going to put it in the view, so add this import line near the top:

from blogengine.views import PostsFeed

Now open blogengine/views.py and add the following line to the import statements at the top:

from django.contrib.syndication.views import Feed

Then add the following class declaration to the bottom:

class PostsFeed(Feed):
title = "My Django Blog posts"
link = "feeds/posts/"
description = "Posts from My Django Blog"
def items(self):
return Post.objects.order_by('-pub_date')[:5]
def item_title(self, item):
return item.title
def item_description(self, item):
return item.text

This is pretty simple. We import the Feed class from thew views provided by the syndication framework, then we base PostsFeed on Feed. We set the title, the link for the feed, and a description for the feed. Then we get the last 5 Post objects in reverse chronological order, and we define each item’s title as the post title. and each item’s description as the text of the post. From here’ it’s pretty easy to see how you could create feeds based on comments, or pretty much any other object that might exist in the database.

And with that done, our blogging engine is pretty-much feature-complete. We have blog posts with comments, categories, an RSS feed, and flat pages, but the look and feel of the site definitely needs some attention. Next time, we’ll make our blogging engine look a little nicer. Once again, the code is available on GitHub in case you find that more convenient.

Recent Posts

Logging to the ELK Stack With Laravel

Full-text Search With Mariadb

Building a Letter Classifier in PHP With Tesseract OCR and PHP ML

Console Applications With the Symfony Console Component

Rendering Different Views for Mobile and Desktop Clients in Laravel

About me

I'm a web and mobile app developer based in Norfolk. My skillset includes Python, PHP and Javascript, and I have extensive experience working with CodeIgniter, Laravel, Django, Phonegap and Angular.js.