Back
Jun 25, 2011

Ajax blocks in Django

Quite often we have to write paginated or filtered blocks of information on page. For example, list of similar houses. It's rendered on server first, and later pagination is done with ajax.

In order to make, I used following setup:

  • inclusion tag, that generates context for tag
  • view, that parses request params and them launches the same tag to create context

So in order to edit one such block, we have to edit three files (apart from template) - tag, view, url.

I got annoyed by this and decided to create a decorator that would automate this process.

With this decorator, you just have to write one function that creates context. Tag and view are generated and registered automatically. Url params like ?obj_id=1 are declared in decorator and are processed automatically.

'''
This module introduces viewtag concept:
funcions that act both as template tags and as views.
Example usage:
First in urls (once):
    url(r'^viewtag/([-\w]+)/$', 'viewtags.view', name='viewtag'),
and
   viewtags.autodiscover()
Then in app/viewtags.py:
    from placeforpeople.viewtags import viewtag, model_arg
    @viewtag('viewtag_test.html', [model_arg('obj', Object)]) # you can use querysets too
    def test_viewtag(request, obj, page=0):
        return {
            'obj': obj,
            'page': page,
        }
Later in templates:
    {% vt:test_viewtag campaign %}
or
    $.get('{% url viewtag "test_viewtag" %}', {page: 2, object: 5}, function(data) {
        $('body').append(data);
    });
'''
import inspect
from django import template
from django.template.loader import get_template
from django.http import Http404
from django.shortcuts import render_to_response
from django.utils.importlib import import_module
from django.conf import settings


register = template.Library()
template.builtins.append(register)

_viewtags_registry = {}


def viewtag(template, args=[]):
    def decorator(func):
        _register_tag(func, template)
        _viewtags_registry[func.__name__] = (func, template, args)

        return func
    return decorator


def view(request, viewtag_name):
    try:
        func, template_, arg_processors = _viewtags_registry[viewtag_name]
    except KeyError:
        raise Http404()

    argnames = inspect.getargspec(func).args
    kwargs = {}
    for argname in argnames:
        if argname in request.REQUEST and argname != 'request':
            kwargs[argname] = request.REQUEST[argname]
    for proc in arg_processors:
        kwargs = proc(request, kwargs)

    return render_to_response(template_, func(request, **kwargs), template.RequestContext(request))


def model_arg(key, qs_or_model):
    from django.db import models

    if isinstance(qs_or_model, models.base.ModelBase):
        qs = qs_or_model._default_manager.all()
    else:
        qs = qs_or_model

    def processor(request, kwargs):
        id = request.REQUEST.get(key) or request.REQUEST.get(key + '_id')
        if not id is None:
            kwargs[key] = qs.get(id=id)
        return kwargs
    return processor


def autodiscover():
    for app in settings.INSTALLED_APPS:
        try:
            import_module('%s.viewtags' % app)
        except ImportError:
            pass


def _register_tag(func, template):
    def tag(parser, token):
        parts = token.split_contents()
        args, kwargs = [], []
        for part in parts[1:]:
            if '=' in part:
                kwargs.append(part.split('=', 1))
            else:
                args.append(part)

        return TempNode(func, template, args, dict(kwargs))
    register.tag('vt:' + func.__name__, tag)


class TempNode(template.Node):
    def __init__(self, func, template_, args, kwargs):
        self.func = func
        self.template = get_template(template_)
        self.args = [template.Variable(arg) for arg in args]
        self.kwargs = dict([(k, template.Variable(v)) for (k,v) in kwargs.items()])

    def render(self, context):
        args = [context['request']] + [arg.resolve(context) for arg in self.args]
        kwargs = dict([
            (key, val.resolve(context)) for (key, val) in self.kwargs.items()
        ])
        context.update(self.func(*args, **kwargs))
        res = self.template.render(context)
        context.pop()
        return res

Subscribe for the news and updates

More thoughts
Nov 27, 2024Technology
Stoicism At Work

This article explores how Stoic principles can be applied in the workplace to navigate stress, improve self-control, and focus on what truly matters, with practical examples from the author’s experience in software development.

Mar 18, 2024Technology
From boring to exciting: turn learning to code into an adventure

Tired of boring programming courses where you're forced to read thick textbooks and write code that's never used? Need a platform that makes learning fun and exciting? Then you're in the right place!

Feb 28, 2017Technology
How to write an API in Django

There is such a term as Remote Procedure Call (RPC). In other words, by using this technology, programs can call functions on remote computers. There are many ways to implement RPC.

Nov 21, 2016Technology
Crawling FTP server with Scrapy

Welcome all who are reading this article. I was given a task of creating a parser (spider) with the Scrapy library and parsing FTP server with data. The parser had to find lists of files on the server and handle each file separately depending on the requirement to the parser.

Oct 11, 2010Technology
Testing authentication in Django

In order to check if user is authentcated in test, you can run:

Mar 6, 2010TechnologyManagement
Supplementing settings in settings_local

For local project settings, I use old trick with settings_local file:try:from settings_local import \*except ImportError:passSo in settings_local.py we can override variables from settings.py. I didn't know how to supplement them. For example how to add line to INSTALLED_APPS without copying whole list.Yesterday I finally understood that I can import settings from settings_local:# settings_local.pyfrom settings import \*INSTALLED_APPS += (# ...)