You are hereGeneric, URL base view count for Django

Generic, URL base view count for Django


By meledictas - Posted on 15 November 2008

A lot of web sites have a view count statistic for showing how many views for particular web page. For Django (or any MVC web framework), we call web page base on URL. I will show you how to implement generic URL count and display it the template.

Counter models

Below is the models,

from django.db import models
from django.utils.translation import ugettext_lazy as _

class PathCount(models.Model):
    path = models.CharField(_('path'), max_length=250, null=True, blank=True)

    def __unicode__(self):
        return '%s' % self.path

class ViewCount(models.Model):
    url = models.URLField(unique=True, verify_exists=False, max_length=1000)
    count = models.IntegerField(default=1)

    def __unicode__(self):
        return "%s-%s" % (self.url, self.count)

Simple? I have a ViewCount model for storing the URL and its view count. The PathCount will use to store the URL path we don't want to keep the view count.

The middleware

from counter.models import ViewCount, PathCount
class ViewCountMiddleware(object):
    def process_request(self,request):
        if is_count_path(request.path):
            view_count = ViewCount.objects.filter(url=request.path)
            if view_count:
                view_count[0].count += 1
                view_count[0].save()
            else:
                ViewCount(url=request.path).save()


def is_count_path(path):
    if not path.endswith('/'):
        path = path+'/'
    except_paths = [p.path for p in PathCount.objects.all()]
    for p in except_paths:
        wildcard =  p.endswith('*')
        if wildcard:
            p=p.replace('*', '')

        if p == path:
            return False
        #wildcard?
        if wildcard:
            if path.startswith(p):
                return False
    return True

The middleware is key of the application. Every request come in, the request middleware check the URL, if the URL is exist, increase the count, if not, add the URL to the database and set the count to 1.

You may have a question, how about the 404 URL. I leave it up to you. Sometime I want to know the URL user (spammer) try to access.

The is_count_path function queries the count path from the PathCount model. Check if URL is needed to get the view count. The function work with wild card, for example, if we have /admin/* in the PathCount, all URL start with /admin/ will not be get the count view adding.

The template tag

To be easy and flexible, we will have the template tag for getting the view count the any web page. Below is the code,

from django import template

from counter.models import ViewCount

register = template.Library()

class ViewCountForURLNode(template.Node):
    def __init__(self, url, context_var):
        self.url = url
        self.context_var = context_var

    def render(self, context):

        url = template.resolve_variable(self.url, context)

        views_count = ViewCount.objects.filter(url=url)
        if not views_count:
            new_view_count = ViewCount(url=url).save()

        context[self.context_var] = ViewCount.objects.filter(url=url)[0].count

        return ''

def do_viewcount_for_url(parser, token):
    """
    Example usage::

        {% view_count_for_url "/blog/" as count %}

    """
    bits = token.contents.split()
    if len(bits) != 4:
        raise template.TemplateSyntaxError("'%s' tag takes exactly three arguments" % bits[0])
    if bits[2] != 'as':
        raise template.TemplateSyntaxError("second argument to '%s' tag must be 'as'" % bits[0])
    return ViewCountForURLNode(bits[1], bits[3])

register.tag('view_count_for_url', do_viewcount_for_url)

The view_count_for_url taks the url as parameter. The example usages are,

{% view_count_for_url "/" as count %}

The above example is the example for getting the view count the URL "/" which tell how many view for the web site.

{% viewcount_for_url request.path as count %}

The above example is very convenient way to get the view count using the request template context. In order to use this, we have to have django.core.context_processors.request in the TEMPLATE_CONTEXT_PROCESSORS setting.