Django and Google Maps

Adding a Google Maps address preview to the Django admin interface.

Last post I wrote about extending the Django Admin interface by adding a little dynamic data to the help_text field. For more advanced things you need to customize the html widget itself.

One thing I want to do is have a Django model automatically get a Google Map of the address I have stored and save this file. For my purposes though I don’t want this to be automatic, I’d rather an admin user check the map first then decide to overwrite any previous map file. I’ll extend the a widget to get this effect.

First I need a preview box. I want to insert some extra html, I’m actually going to put this into a checkbox form, you will see why later. I need to override the render def on the widget to add some code.


class MapImageWidget(forms.CheckboxInput):
def render(self, name, value, attrs=None):
output = []
output.append(super(MapImageWidget, self).render(name, value, attrs))
output.append("If checked, the above map file will be overwritten with Google Maps.<table><tr><td><a href=\"javascript:get_map()\">Preview Google Maps</a></td></tr><tr><td> <iframe width=\"400px\" height=\"400px\" name=\"mapframe\"></iframe> </td></tr></table>")

return mark_safe(u''.join(output))

I’m using Google Maps static maps url to get my previews. I made a javascript function called get_map to look up the url based on the address field on my model. I want it to actually use the currently typed in address rather than some ajax solution which may get the currently saved address.


function get_map(){
var address = document.getElementById("id_address").value;
var city = document.getElementById("id_city").value;
var state = document.getElementById("id_state").value;
var zip = document.getElementById("id_zip").value;
parent.mapframe.location="http://maps.google.com/maps/api/staticmap?sensor=false&size=400x400&markers=size:mid|color:red|" + address + "," + city + "," + state + "," + zip;
}

I put the javascript into my change_form.html admin template. This function is only for previewing by the user and has nothing to do with actually saving my map image. For that I have a checkbox as in this screen shot

Maps preview in admin edit page

When the user checks off the box, Django will know to get that image and set it to the map ImageField. I do this by overriding the save function on my model.


def save(self, *args, **kwargs):
if (self.use_google_maps):
self.use_google_maps = False;
image = urllib.urlretrieve("http://maps.google.com/maps/api/staticmap? sensor=false&size=400x400&markers=size:mid|color:red|" + \
self.address + "," + self.city + "," + self.state + "," + self.zip)
self.map.save(self.company_name + "_map.jpg", File(open(image[0])))
super(Company, self).save(*args, **kwargs)

I use urllib to “wget” the file. This process will replace the previous practice of manually looking up maps, saving them, and uploading them. Of course if I trusted Google maps (or my addresses to be correct) I could just have it dynamically get that image when needed instead of storing the image file.

Django Hack: adding extra data to admin interface

A common need I have for Django’s admin interface is to show a little more data for convenience right on the edit page. For example showing a link to a foreign key’s edit page right there. The way I do this is by setting the help_text field in the render_change_form function. I create a new function in my admin class to override render_change_form

class whateverAdmin(admin.modelAdmin):
 def render_change_form(self, request, context, *args, **kwargs):
  context['adminform'].form.fields['someField'].help_text = "Go to edit page " + str(context['original'].anyFunction()) + " (will discard changes)"
  return super(whateverAdmin, self).render_change_form(request, context, args, kwargs)

the anyFunction() is just a function I made to display a URL in my model. Notice the allow_tags line to allow the function to return the html <a> tag

def anyFunction(self):
 try:
  field = self.someField
  urlRes = urlresolvers.reverse('admin:appName_someField_change', args=(field.id,))
  return '</a><a href="http://example.com' + urlRes + '">' + str(field) + '</a>'
 except:
 return ""
anyFunction.allow_tags = True

This link is then very convenient when using the admin interface just to look up information. The render_change_from function is also useful to editing about the admin page. I use it to modify queryset’s for foreign key data as well.

Django

I mentioned the Django app I made in the previous post so I thought I would provide some info about what it is. Essentially the goal was to reduce duplicate work everywhere possible be moving data from spreadsheets and other database’s into one central database. Also to allow a non skilled worker to edit this data, then allow for reporting to remake all the excel sheets that were originally needed. Here’s my setup

Ubuntu 9.04 server running Django, MySQL, and Apache (LAMD?) Data models are defined in Django which automatically makes an Admin interface with some customization options.

data entry

Django also makes short work of authentication with is done against Active Directory. Reporting is done with PyExcelerator and pyRTF to make downloadable excel and rtf documents.

reporting

It’s a pretty basic database but it really saves a lot of time compared to maintaining lots of xls documents and mail merges. Also it allows a technical worker to import exported data from other database into MySQL. Ideally this program could also be linked with other MySQL backend programs. So say I want to use SugarCRM I could symlink the contacts table so both Sugar and Django use the same one for perfect 2-way syncing. The real beauty of this is that it was so quick to develop. This is just been a side project for me. Doing it in PHP or .NET would have easily taken 3 times as long.

Working away

Well I’ve been in nyc for a month now. The first few weeks have been hell with 60+ hour weeks at Cristo Rey but things are finally starting to calm down. At work I’m coordinating transportation for students to get to their work placements.

I’ve completely redid the process in a short time I was there from a bunch of random Excel files and proprietary databases to something more maintainable, a MySQL database with Django. I’m impressed with Django’s ability allow me to make good data centric websites in only a few days. Django’s philosophy of defining data “models” once and having it create the database and administration page automatically is great. I’m then using pyRTF and pyExcelerator to generate reports from the data. We can now enter student, company, and contact data in at one place and have it reflect to all relevant reports such as daily attendance. The admin interface is easy enough to use that students can do data entry with it.

Other new fronts include the possibility of moving from Act by Sage to SugarCRM should further streamline the process. The idea here would be that Sugar has more features and could integrate with my Django database, Outlook, and a smart phone. With some hacking around it looks like I can symlink(yay unix) a “contacts” table used in both Django and SugarCRM to keep them perfectly synced and keep Django happy in it’s data model land without manual SQL needed. I’m happy to be using my skills at the new placement, while also running the day to day activities at the school. Though it’s still a 10+ hour day with some Saturdays making it rather stressful.

Other thing’s I’m looking into are Alfresco content management system, Zimba email server, and SchoolTool. SchoolTool is a decent school administration management tool. It’s written in Zope which is a python based framework. Python is quickly becoming my favorite language. It’s missing a few key features so I might hack on it to make it work. One unsolvable(?) problem with SchoolTool is that it uses ZODB, an object oriented database. This means it would be really hard to integrate it with the other databases I’m using. ln -s can’t save me this time..