Learning Django by Example(7) Attach a tag

Web January 20th, 2008

Tag is probably the most distinguish feature of Web 2.0 applications that differentiate them from the traditional hierarchy categories. I want to attach a Web 2.0 tag to Gelman, so the user could simply click the tag, and find the related books that may arouse his/her interest.

django-taggingis a generic tag application to simplify the backend development, all you need to do is just add the TagField to the Book model:
class Book(models.Model):
……
tags = TagField()

And in the book_detail.html template, refer it as object.tags as this:

{{ object.tags|popuptags|safe }}

popuptags is a custom tag that decorates the tags as a list of HTML links, and join them:

@register.filter
def popuptags(value):
    tags = value.split(” “)
    return ” “.join([‘<a href="/bookshelf/tags/%s">%s</a>’ % (x, x) for x in tags])

So tag foo is linked with /bookshelf/tags/foo, so just redirect the request to the view:

(r‘^tags/(?P<tag_name>[\w-]+)/$’, views.by_tag)

And handle it in views.py:

@login_required
def by_tag(request, tag_name):
    tag = Tag.objects.get(name=tag_name)
    return list_detail.object_list(
        request,
        queryset=TaggedItem.objects.get_by_model(Book, tag),
        template_name=“bookshelf/book_list.html”,
        extra_context={‘title’: ‘Tagged by %s’ % tag_name},
    )

All the tedious work has been handled by django-tagging: we first get a tag object by name, and then build a QuerySet using get_by_model method; the rest is handled by the generic view, done. Salute to django-tagging developers!

We would discuss how to add a tag in the next post, that is the magic of Dojo.

Learning Django by Example(6): Search

Web January 4th, 2008

Search is one of the must-have functionalities in Gelman. Here is an SearchQuerySet based upon MySQL full text search extension. It is really cool and neat, but

  • first, I don’t want to build my application against specific database extension, even though MySQL is universally picked up in the Web application.
  • second, I still prefer more flexible and powerful search syntax other than what MySQL provides
  • Last but not the least, I may still need Lucene or Xapian to index, search the PDF, CHM eBooks

So I home-brew the search using PLY, the Python Lex Yacc toolchain. You could check the code here, most of parser.py is just boilerplate, the interesting part is to build django.db.models.Q:

def p_expression_term(t):
    ‘expression : TERM’
    t[0] = Q(**{‘title__icontains’:t[1]})

The semantics is quite straightforward: AND(the default), OR operations are supported directly from Q; and only field title is searched. We may extend the syntax using author: like Google does later, so stay tune.

Learning Django by Example(5): Software is hard

Web December 26th, 2007

“Software is hard.” reads the quote from Donald Knuth that opens Scott Rosenberg’s Dreaming in Code. Even the development of Gelman is not smooth as I expected.

Think big, but not fancy

Gelman is a hobby project to scratch personal itch, at the same time it acts as the playground to learn django and dojo. Therefore, it is extremely difficult to resist the temptation for some bling-bling but not essential features, which are called “free feature” in the industry. For example, the fancy django-registration plug-in. It will be cool to integrate a ring-and-whistle registration module, but do I really need this module since I am probably the only user/administrator of this web application, why bother to waste effort in registration?

Deodorize the smell

It is quite annoying to work on a bad structured code base with misleading name convention. Eventually, I decided to re-organize the layout of the source code, and moved out from Google Code to Assembla, since the latter provides the Trac support for online source browser and ticket support.

Some other changes include:

  • Rename library as bookshelf
  • Move the core functionality for book/add to file/add
  • Redesign and develop (in progress) the MassAdd functionality for files in server’s incoming

Find a formal way

What make Gelman stands out is his admin interface, 60% of its functionalities are only available to administrator. When working with admin module, we should play the game of default admin. Eventually, I may have to copy the code from admin module for the sake of security and behavior consistency. In this situation, the Django Book cover what is on the table, but not under the table.

Here is just a snapshot of current progress, the MassAdd.
MassAdd

Learning Django by Example(6): AJAX File Upload

Web October 4th, 2007

In the last post, I just added the entities in the database, but did not bind the meta data with eBook files. This post will demonstrate how to upload the file in AJAX flavor:

The idea is inspired by this tutorial , using dojo.io.iframe, we could submit a form with file upload asynchronously. In client side, a hidden field meta is added to store the marshaled JSON string. dojo.io.iframe.send shares the same defer concept as dojo.xhrPost:

var meta = dojo.query(“input[@name=meta]“, form)[0];
    meta.value = dojo.toJson(cache[i]);

    // submit the form
    dojo.io.iframe.send({
        url: form.action,
        method: “post”,
        handleAs: “json”,
        form: form,
    }).addCallback(function(ret) {
    … …

NOTE: the handleAs is json, so the argument ret is JavaScript object.

In server side, the JSON string needs to be wrapped by textarea, this is the hard requirement of dojo.iframe.send.

return HttpResponse(“<textarea>%s</textarea>” % simplejson.dumps(retset) , mimetype=‘text/html’);

Conceptually, add-by-search will issue INSERT operation to the database, the result may fall in the following categories:

  • Success
  • FileAlreadyExist: the eBook file exists, so the uploaded file is discarded
  • UnexpectedError: an 500 error happens in server side for unknown reason.

Check r34 for the implementation.

Learning Django by Example(5): Time to Attack

Web September 19th, 2007

In the last four section(1, 2, 3, 4) ,we have been familiar with the environment, it is time to do some real work.

I will add more fresh to models.py , linking eBook file and its meta data, thumbnail of the cover, regular size cover etc. Two other models are added for eBook:

class FileType(models.Model):
    type = models.CharField(maxlength=10)

class File(models.Model):
    type = models.ForeignKey(FileType)
    handle = models.FileField(upload_to=“files”)
    meta = models.ForeignKey(Book)

Book to File is one-to-many as one book may have more than one formats, a PDF version for print, a CHM version for reference.

To server the images of book covers, , we need to setup the MEDIA_ROOT and MEDIA_URL:

MEDIA_ROOT = ‘/home/share/eBooks/’
MEDIA_URL = ‘http://localhost:8000/repo/’

and setup the url mapping as well:

(r’^repo/(?P .*)$’, ‘django.views.static.serve’, {’document_root’: ‘/home/share/eBooks’}),

To save an image file, we can easily call save_FOO_file method, it works with two drawbacks: first, you need to read the image into the memory; second, it is synchronous function call aka it is blocked until the file is fetched, read and written. urlretrive is preferred in this way:

from django.conf import settings
from urllib import urlopen, unquote, urlretrieve
from urlparse import urlparse
from os import path
… …
                thumb = unquote(urlparse(item[‘thumburl’])[2].split(‘/’)[-1])
                # using hard-coded image temporary
                urlretrieve(item[‘thumburl’], path.join(settings.MEDIA_ROOT, ‘images’, thumb))
                book.thumb = path.join(‘images’, thumb);
                book.save()

A further optimization may introduce Twisted or asyncore.

More work needs to be done in the UI side: the Book objects are populated in brief or detail modes. Brief mode just highlight the title, cover, authors and ISBN of the book, commonly used in general listing, search results, my favorite and so on; while detail mode shows more detailed information like editorial review, related book, tags etc. It is worthy extracting the code snippet of brief mode to a template, so we can reuse it later by {% include book_item_brief.html %}. Detail view would fetch the editorial view from Amazon using the same AJAX hack as discussed before.

With stylesheet, it looks much better, Brief view:

Breif view



and Detail view:

Detail view



Check r30.