This is the story of a guy, Google and Django. After a hard fought battle with jQuery, the guy takes on Google Maps. After conquering them, he doesn’t feel right leaving all of the Javascript embedded in his templates. This is his journey to liberate them.
Work has started and is continuing on what I’m labeling 25.1. Yes I’m treating this as an app, but more on that later. ;) Part of 25.1 involves Google Maps, which like jQuery is something that would usually lead to me wanting to decapitate myself. Thankfully, Google helped and sample code is the shit.
But first, a little history. Like Jeff, I’ve been going on a geocoding spree. I’ve made it a habit to accurately geocode all of my Flickr photos and then bring them to Django using django-syncr. Unfortunately, django-syncr isn’t working for me and it doesn’t seem like it’s going to be fixed anytime soon, so it seems like 25.1 will be waiting on that. Anyway. The landing page of “Phoenix Hunting” has a map of all of the cities I have registered. Minus the Flickr photos, that number stands at nine. Adding the Flickr photos doubles that number.
Now if you haven’t dealt with Google Maps before, each time you place a marker, you have to tell it a few things. The most important of which is where you’re going to put it, specifically using latitude and longitude. Then you have a few line for the marker code itself, a few more lines if you’re going to add an information window, etc. It gets kind of long when you’re throwing in the kitchen sink. Thankfully, loops save the day.
{% for city in cities %}
point{{ city.id }} = new GLatLng({{ city.location.latitude }}, {{ city.location.longitude }});
markerOptions = { clickable:true, draggable:false };
marker{{ city.id }} = new GMarker(point{{ city.id }}, markerOptions);
map.addOverlay(marker{{ city.id }});
marker{{ city.id }}.info_window_content = '<p><strong>{{ city.full_name }}</strong></p>';
GEvent.addListener(marker{{ city.id }}, "click", function() {
map.setCenter(point{{ city.id }}, 6);
});
{% endfor %}
Google Maps - City Loop (Javascript + Django)
Alright, I’ll stop pulling your leg. Of course you’d use a loop for something like this if you’re reading the data from your database.
The problem for the rookie (me) was that I didn’t want all this code sitting in my HTML template. Sure, now it’s just a housekeeping thing, but if I’m going to exile all the rest of my CSS and Javscript to the confines of their own files, Google should get no special treatment. Besides, that loop times about 20, just looks bad, am I right? Then I remembered seeing this in Django’s documentation:
A template is simply a text file. It can generate any text-based format (HTML, XML, CSV, etc.).
Alright, but how. Google couldn’t help me there. I couldn’t just use a normal JS file since I needed it to loop for the different values. But every time I searched for something about “creating Javascript with Django,” I got endless amounts of articles about how the Dojo framework had adapted Django’s template language for Javascript. Great, but that does me no good. So I opened the Django book and saw the following on page 132:
def author_list_plaintext(request):
response = list_detail.object_list(
request,
queryset = Author.objects.all(),
mimetype = 'text/plain',
template_name = 'books/author_list.txt'
)
return response
Plaintext Author List (Python)
Woohoo, getting somewhere! Just need to change the mimetype and the template_name and we’re in business. So for this problem, I put the following in my views.py:
def location_index_js(request):
"""
Returns a generated Javascript file for use with the location index. It
includes all the logic for rendering the appropriate Google Map as well
as all the applicable markers.
"""
from django.views.generic.list_detail import object_list
cities = City.objects.all()
return object_list(
request,
queryset = cities,
mimetype = 'text/javascript',
template_name = 'geo/javascript/location_index.js',
extra_context = {
'cities': cities,
}
)
View for Location Index's Javascript (Python)
So I’m using a generic view, which I’ve always used to generate HTML, to generate the Javascript for the map. Again, I just made sure that the mimetype was correct. So how do we actually use this thing? Head on over to your urls.py and point a pattern to it. I used the following:
urlpatterns = patterns('',
url(
regex = '^js/$',
view = location_index_js,
name = 'location-index-js',
)
)
URLConf for Location Index's Javascript (Python)
If you’re not keen on using directories to point to Javascript files or the syntax I’ve used above, you could even do this:
(r'^location-index.js$', location_index_js),
I had no idea prior to tonight that you could point a URL pattern directly at a file and it’s something that could definitely come in handy later on. Note that location-index.js doesn’t actually exist in the filesystem. It’s rendered when called for, just like the page you see now.
After that, all I had to do was to point the new Javascript reference in my template and it worked! Obsessive-compulsive urges satisfied! I later expanded by applying the above to each of the detail pages. To be honest, other than getting the squeaky clean sensation, doing this little exercise really reminded me that Django’s views and templating language can do more than just show HTML pages. It’s almost common sense to the everyday developer, but it’s really helpful for the rookie, especially if said rookie is trying to move on from building blogs using the framework. ;)
Note: I am not an expert at this, I’m learning just as much as you are. I just happen to want to chronicle things in case it could possibly help somebody else. So, if you know I’m doing something completely stupid, please feel free to correct me and I’ll update the post accordingly.
Nice to see you experimenting and finding out how flexible Django is. I know how liberated you must feel coming from a WordPress background.
The only thing I might say is that you may want to cache this view (if you have caching set-up) seeing as it has the potential to cause lots and lots of database queries (iterating over every object in a table could get costly depending on how many objects you have). As I’m sure you know that’s just a simple matter of using the
django.views.decorators.cache.cache_pagedecorator. This data isn’t something that is likely to change terribly often anyway, and if it’s out of date by an hour or so it isn’t really the end of the world as we know it.It’s great to see more people sharing their findings like this, like you say it can be hard to find information as specific as this with Django for rookies. I really should blog more as I have some interesting things to write about… oh well, when I get some time (and a new site that isn’t broken) I will.
Anyway, looking forward to 25.1!
Waah, why didn’t Syncr exist six months ago? Would have saved me so much time (Assuming it works). ;_;
Nice find.
Oh, and the fix to the problem you’re having with django-syncr seems like a pretty simple problem and a 3-line fix (wrap the block around line 82 in a
try... except AttributeErrorto return the unpopulated exif dictionary if that happens).DM me on Twitter and I’ll be happy to send you a diff or tell you how to fix it if you don’t want to wait for it to be fixed in the project itself.
Thanks for the heads up on caching Oliver. It’s definitely something I can’t see as an afterthought especially with things like this. Oh and I’ve DMed you, but I’ll try your fix in the meantime. :)
Your first step on your way to APIs in many ways ;)
Started doing something similar to this in a RoR app I’m slowly building.
Hey Bryan, couldn’t find your email address so I thought I’d leave a comment. I made some fixes to django-syncr this morning, if you can test it out that’d be awesome… I think it should take care of the problems you were having… lemme know.