After having done a static page with some jQuery for my home page, and then a very simple web app to manage the list of TV shows I watch (so I know how far behind I am in watching them), I figured the next web app should be slightly more complicated.
So I tackled lists in a generic fashion. I don't know where I get it, but I tend to make lists of stuff. Nothing significant, mind you, but I do have several lists that I am constantly updating in my head on various topics, such as stuff I want to do in Vancouver before I graduate and leave and what I would do if I was given $20,000,000.
And so I tried to come up with a generic web app for unsorted and definition lists. Now when I have done my web apps I have tried to stick to the DRY principle as much as possible. This meant that I only needed to define a single list model as no matter what kind of list you had, a list is a list at its most basic level. But what type of list does control what information you want per entry of that list. That means two models for list entries. That's slightly tricky since list entries need a reference to the list they are a part of, and thus leads to two different reference sets under Google App Engine's datastore model.
So I did what any programmer would do in this situation and I abstracted with a dash of information upfront. When a list is created, I specify what kind of list it is since that does not fluctuate. Based on that information, various methods defined on the list model pull list entries from the proper reference set (e.g. if a list is defined to be a unordered list it has the items() method return from the unordereditem_set attribute on the list model). And I continued to abstract everything I needed on the list model to the point that I use the list model to get the list entry class that is appropriate. I basically work blindly in the code as to what list entry model is being used.
The other thing I did was define only a single management view for lists and list entries. This means that all creation and editing of lists goes to a single view (same goes for list entries). This allowed me to control more based on URLs than I do from my TV series web app. It does lead to more branching within the single management view, but it keeps things simpler for me as any commonality is easily handled. Besides, I prefer to have a single method that handles a single class of issues based on its arguments more than defining a bunch of custom methods.
And Django deserves credit for letting the views be simplified like this. Being able to generate the form to use is generic enough that I was able to use my abstraction setup for the list types to not have to care what model form I was using; I just used ``list.item_class().form`` and just didn't care.
And the last improvment I did was memcache the hell out of my entire site. I defined a subclass of the Client class provided by App Engine. It allows me to take my old memoize decorator and have it generate the key to use automatically based on the function being decorated and a common prefix. I even went as far as to provide a key argument that is a callable that can return parts to use in the key so that it can be dynamic based on the arguments to the decorated function (which is apparently a complaint someone had with my simple decorator I posted at the App Engine cookbook site). Although, in hindsight, it might be simpler and be a stronger common-case to do it based on the URL instead (sans GET arguments) for the common case instead of the view function's name. Then using Django's reverse() would be all that is needed to figure out what to delete from the memcache. Ugh, why didn't I think of this simplification last night?
When I add or edit something now I just delete the proper entry in the memcache. And if someone is logged in as an admin then no caching is used since admin-specific stuff is put in the HTML.
Once again, I am a happy camper when it comes to App Engine and Django. And once the Django helper for App Engine releases a 1.0 version and I can run that and Django from a zip file, I will a REALLY happy camper. I think the only thing really missing that I wish I had was a slug property for models so that I didn't have to specify them myself by hand. That, and a method I knew I could override that was always called upon commitment to the datastore.