Customize the Django newform admin UI

Python, Web January 11th, 2009

One of the exciting features Django 1.0 brings to the table is the integration of newform into the admin UI. Hat off to Brian, great work.

The essential workload in the Pattee’s admin UI is to input the meta information of the book, then link it to the eBook file. It is more convenient to take the advantage of Amazon Web Service, so the user scenario is:

  1. Search Amazon by keyword or ISBN
  2. Select the correct book
  3. Update the eBook file
  4. Save it to the database
Add a Book

It is straightforward to override the default template and implementation: redirect the admin URL to our own version of change_form.html template and add_view implementation, then save the object regardless the data integration; or we may reuse the infrastructure, and just implement our required function. Pattee takes the latter approach.

First, declare the BookAdmin and eBookAdmin in admin.py, and hook eBook inline:

class eBookAdmin(admin.StackedInline):
    model = eBook
    extra = 1

class BookAdmin(admin.ModelAdmin):
    form = BookForm
    inlines = [eBookAdmin]
    change_form_template = ‘book_change_form.html’

and don’t forget to register it to admin.site, which is required by Django 1.0.

admin.site.register(Book, BookAdmin)

Then in BookForm, we could override full_clean to provide the cleaned_data. And in BookAdmin, the following tricks will guard against duplication of Book instances:

def save_form(self, request, form, change):
        if form.is_valid():
            try :
                instance = self.model.objects.get(isbn = form.cleaned_data[‘isbn’])
            except ObjectDoesNotExist:
                return super(BookAdmin, self).save_form(request, form, change)
            return instance

That is all. You may consider to check out the source code for details:

svn checkout http://my-svn.assembla.com/svn/pattee/trunk pattee

Restart the Django engine

Python, Web January 10th, 2009

It has been one year since the last update of my Learning Django by Example series, partially in account of the demanding day job, the main reason lies in that the project is bloated with some advanced features which delay scratching my itch. Thanks to the snow storm in the holiday season, our vacation to Las Vegas was canceled, I had time to restart the Django engine.

The new project, Pattee, named after the library in Penn State University, aims to a simple, stupid eBook management system. It is so simple that it even does not have a dedicated UI, only integrated to a third-party web application(currently, douban.com). You may check it out at here:

svn checkout http://my-svn.assembla.com/svn/pattee/trunk pattee

There is no step-by-step tutorial to follow the footprints of the Django Book, each post will target a specific topic.

I may post

Learning Django by Example(7) Attach a tag

Python, 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