Josh Ourisman » On the other hand

CostToDrive: An awesome new tool for planning road trips

December 3rd, 2008

Later this month Jessi and I are going on a 2,500+ mile road trip. We'll be spending Christmas in Florida with some of Jessi's family and new years in Georgia with some friends of ours. Why on Earth would we spend so much time on the road (the long legs are approximately 17 hours of driving each) when we could just hop a jet and be there in a couple hours? Well, it's saving us a ton of money, and since it's a longer trip the amount of time driving really isn't that significant.

How much money is this saving us, you might ask? Well, when I was initially planning this out I just used Google maps to figure out the distance, divided by MPG our car gets (mid 30s on the highway, it's an '06 Mazda 3), then multiplied by what I figured would be a realistic price of gas in December. At the time (this was a few months ago) I figured that $5/gal would be reasonable, and the cost came out to around $350. It would cost that much just for us to fly one way from Boston to Florida, not to mention a multi-city itinerary and the additional costs of having to get from the airport to wherever we were going.

Now, however, I've discovered an awesome new website: CostToDrive.com. It basically takes care of all that for you, and with up to date information. So I just entered our starting and ending point, and what kind of car we're driving (currently it doesn't handle multi-destination trips, but I've apparently still retained some basic arithmetic skills). Not only does it you an estimate of how much the trip will cost, but it actually gives you driving directions that include the specific gas stations you should go to in order to get the best price. How awesome is that?

Apparently, at current prices, it will cost us $59.01 to drive from Somerville to Amelia Island at an average cost per gallon of $1.73, $20.79 to drive from Amelia Island to Athens Georgia at $1.82/gal., and $56.80 to drive from Athens back to Somerville at $1.54/gal. (Seriously? When I first got my car in '99 it cost me $1.75/gal. to fill up in Oakland, CA!) So all in all the trip should cost right around $140 (assuming current gas prices, but who knows what will really happen there). Not bad! Definitely not worth my money to fly.

Modifying the Django Admin: redirects after adding, changing, and deleting

October 27th, 2008

One of the Django projects that I've been working on for about a year and and will (fingers crossed) be going live in the very very very near future has involved a lot of modification to Django's admin interface. I plan on writing more about the many, many specific changes that I've made to the interface (without modifying the actual Django codebase, so that the changes can be easily applied by anyone without breaking updates), but to talk about them all at once would make far too long of a post. So I'll be taking them on one at a time.

The change I want to talk about right now involves redirecting the user to the page I want them to be at after they've finished adding or editing an object rather than to that object's model's change_list. Normally, if you're editing an Entry object in the Blog app, when you hit save it will take you to /admin/blog/entry/, which is a list of all the Entry objects in the database. However there are some instances in which this isn't the behavior I want. Once such instance involves model inheritance. Say, for example, you have multiple types of Entries which you've accomodated through multi-table-inheritance. Because the different sub-classes are different models, they all have their own change_lists in the Django admin. But I want to be able to view, edit, and create Entries of all types from one page.

Fortunately, Django makes this fairly easy to accomplish. All that is necessary is to override the appropriate methods in the Entry ModelAdmin. That will end up looking something like this:

class EntryAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, extra_context=None):
result = super(EntryAdminAdmin, self).change_view(request, object_id, extra_context)

if not request.POST.has_key('_addanother') and not request.POST.has_key('_continue'):
result['Location'] = iri_to_uri("/admin/blog/entry/")
return result

The exact same modification should be made to add_view as well, and a nearly identical modification to delete_view though it doesn't need to deal with the _addanother and _continue cases. You can then use the EntryAdmin class for all of your varioud Entry sub-classes, or, if you need some other changes to the admin for different Entries sub-classes you can sub-class EntryAdmin for them. Now, whenever you hit the save button after editing any sort of Entry, it will always take you back to /admin/blog/entry/ rather than /admin/blog/linkentry/ or whatever your other subclasses are. If you want it to only take you back to /admin/blog/entry/ if you're coming from a particular page and otherwise take you to /admin/blog/linkentry/ all you need is to add a GET variable to your url (something like '/admin/blog/linkentry/add/?return_to_main=True') and then check for it in your modified change_view, add_view, and delete_view methods with a request.GET.get('return_to_main', False). I've even used this between objects of different model types to create a 'dashboard' page that allows you to view, and alter the relationships between an object of one type with objects of another type. All that's necessary in a case like that is to pass the id of the object in your GET variable and take that into account when creating your uri. An added benefit of that it makes it easy to auto-fill the ForeignKey field when creating a related object. In such a case you'll also need to keep that GET variable in the URLs down the line in order to maintain compatilibility with the 'Save and add another' and 'Save and continue editing' features. But that's still a simple modification:

class EntryAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, extra_context=None):
result = super(EntryAdminAdmin, self).change_view(request, object_id, extra_context)

other_id = request.GET.get("other_id", None)
if not request.POST.has_key('_addanother') and not request.POST.has_key('_continue'):
if other_id:
result['Location'] = iri_to_uri("/admin/dashboard/%s/") % other_id
return result
elif request.POST.has_key('_continue'):
if other_id:
result['Location'] = iri_to_uri("?other_id=%s" % other_id)
return result
elif request.POST.has_key('_addanother'):
if other_id:
result['Location'] = iri_to_uri("%s?other_id=%s" % (result['Location'], other_id))
return result
return result

But more on that sort of thing in other posts.


copyright © Joshua Ourisman 2006-2009 all rights reserved