Crimemap
Chicagocrime was created in 2005, and was one of the first websites to overlay custom information on a Google map. Today these websites are commonly referred to as "mashups". The website was created by Adrian Holovaty, one of the core Django developers. The site layout and design was created by Wilson Miner, who helped create the design of the Django administration interface. If you haven't already, please browse Chicagocrime now.
Let's ignore what the website looks like, and instead let's concentrate on the data the site presents to the user.
It should be fairly obvious that the basic unit of data for this website is a "crime". Specific information about each crime is available, and is used to allow the user to narrow down the results displayed on the map. We can start to imagine what the database for this website should look like. At a minimum we would need to keep track of the following for each crime:
- Date
- Crime Type
- Latitude
- Longitude
- Street Address
The list is not comprehensive, but you can see that if you want to let the user view the twenty most recent homicides in Chicago, you will need to know the type of each crime so that only homicides can be selected from your database. You will also need to know the date of each crime so the most recent crimes can be selected from the database and arranged in the correct order. To plot the location of each crime on a Google map, you must know the latitude and longitude. If we want to let users view crimes limited to a certain street, we need to know the street address of each crime.
Start Coding
Now that we know the type of data we will need to keep track of, we can start coding our application. Open the Django directory, and double-click on start.bat. This will launch a console window. Depending on where you have placed the download on your computer, your prompt will look slightly different than the prompt shown below. For example, if you have copied the download to a USB key with drive letter E, your prompt will look like this:
E:\Django\>
When using the Django framework it is often convenient to organize the sites we create into projects and applications. A project keeps our important database and configuration settings organized, while an application implements the functionality of the site. A website will often consist of a single project with multiple applications. In our example, we will create a project to handle the settings for our website, and a single application to map crimes onto a Google map.
Type the following at your command prompt, then press enter:
django-admin startproject crimemap
After a few seconds the command should complete. A new directory has been created, named crimemap. Let's switch into this directory by typing the following:
cd crimemap
cd is the DOS command for change directory. Your command prompt should now show that you have navigated into the newly created directory:
E:\Django\crimemap
To see the files created by the startproject command, type the following:
dir
dir is the DOS command for directory, and will list the contents of the current directory. You should see the following files:
manage.py __init__.py settings.py urls.py
These files were created by Django when we executed the startproject command, and are the basic files needed by most projects. The settings.py file is where all of our project's database and configuration settings are stored. manage.py is a utility we will use from the command line to help us update, launch, introspect, and validate our project. The urls.py file allows us to map specific URLs to specific python functions, extracting information from the URLs and passing the extracted information to the function that is called. We will work with this file later in the chapter.
For now we will concentrate on the settings.py file. I have included the notepad++ text editor in the standalone Django environment. Notepad++ is a full-featured open source text editor which is excellent at editing python source code, javascript, html, and pretty much any other file format you can throw at it. To launch the editor and begin editing the settings.py file, type the following:
notepad++ settings.py
This should open the notepad++ text editor, with the settings.py file loaded. There are instructive comments throughout the file, which you should take the time to read.
Python 2.5 has the SQlite database built in, which is what we will use for the projects in this tutorial. Django projects can also use other databases, such as Postgres, Mysql, and Oracle.
Edit the database section of your settings.py file, so it matches the following:
DATABASE_ENGINE = 'sqlite3' DATABASE_NAME = 'crime.db' DATABASE_USER = '' DATABASE_PASSWORD = '' DATABASE_PORT = ''
The changes you just made to the settings file tell Django that you want to use the sqlite3 database engine for the crimemap project. It also tells Django the name of the database to use. The sqlite3 database engine does not need a username or password.
Save your changes to the file, and exit from notepad++.
Our Application
We have created our project and specified a database. We can now create an application to handle the storage of crime data, and the mapping of crimes. At the DOS prompt type the following:
python manage.py startapp mapper
This command should complete quickly. If you list the contents of the crimemap directory again, you will see a sub-directory named mapper which was just created by the startapp command.
Change directories into mapper, and list the content of the directory:
cd mapper E:\django\crimemap\mapper>dir
You should see the following files:
models.py views.py __init__.py
These files were created by the startapp command, and are the basic files needed by an application. models.py is where we will tell Django about the data our application will store. views.py is where we will define the python functions called when specific URLs are requested, as determined in the urls.py file in our project directory.
We will deal with URLs and views later in this chapter. For now, let's edit the models.py file:
notepad++ models.py
You should see the following:
from django.db import models # Create your models here.
Obviously, there is not much here. It's up to us to do the work, because we're the only ones who know what type of data we're keeping track of in our application.
The statement from django.db import models allows us to write python code that uses the Django ORM, or object relational mapper. In Django, we like to think of our data as objects that can be created, sorted, searched, and acted upon in various ways. Relational databases don't treat data as objects, so the ORM translates, or 'maps', the object-oriented data interaction in Django to the relational data stored in the database.
Edit models.py so that it contains the following:
from django.db import models
class Crime(models.Model):
type = models.CharField(max_length=20)
date = models.DateField()
latitude = models.DecimalField(max_digits=8, decimal_places=6)
longitude = models.DecimalField(max_digits=8, decimal_places=6)
address = models.CharField(max_length=50)
At the start of this chapter we decided on the minimum information we would need to store in our database to map crimes. For each type of data object we want to keep track of, we define a class. In this case, we have defined the Crime class, which tells Django how objects of this class should be built. For each specific piece of data within our objects, we define a field. In this case we have defined fields to store the information about specific crimes needed to plot them on a Google map.
Our database will soon contain multiple Crime objects. Each crime object will contain information about the crime type, date, latitude, longitude, and address. The details of each crime will differ, but they will all contain the fields that we described above.
For a complete list of the types of fields an object can contain, see the model-api reference.
Certain field types expect you to provide additional information, or arguments, when they are defined. For example, models.CharField take the max_length argument, so Django will know that data stored in that field can't be longer than the specified number of characters. A list of arguments that can be used when defining fields is also available in the model-api reference.
URLconfs and Views
Now that we have created the model of our data, we need to put in place the rules that define and limit how a user can interact with our data. Django accomplishes this through the use of URLconfs and views.
A URLconf allows us to define how our website responds to URLs requested by the user. In our crimemap example we would like people to be able to display crimes on a Google map, and limit the crimes displayed on the map to certain criteria. The limits placed on the displayed crime should be obvious, and should be based on the URL requested. For example, the following URL should display all crimes that are homicides:
http://www.crimemap.com/crime/homicide
The following URL should display all crimes that are arsons:
http://www.crimemap.com/crime/arson
In a similar fashion, if we wanted allow our users to display all the crimes on a certain date, we might allow the following URL:
http://www.crimemap.com/date/01042007
The django framework includes a powerful way to handle requests for URLs like the ones listed above. You simply provide a list of regular expressions to match the URLs you want your project to handle. Each regular expression in the list is linked to the name of a python function that should be called when a URL is found to match that regular expression.
While this may seem confusing, the details should become clearer as we construct the URL configurations for our crimemap project. At the command prompt, switch into your crimemap project directory:
cd ..
Typing cd .. at the command prompt moves you up in the directory tree, which in this case moved us from the mapper sub-directory into the crimemap directory.
Now edit the urls.py file:
notepad++ urls.py
You should see the following:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
# Example:
# (r'^crimemap/', include('crimemap.foo.urls')),
# Uncomment this for admin:
# (r'^admin/', include('django.contrib.admin.urls')),
)
Similar to models.py, Django provide us very little except an import statement that gives us access to the internal functions needed to configure our URLs.
Let's delete the lines that start with #, which are comments, and edit the file so it contains the following:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^crime/([A-Za-z]+)', 'crimemap.mapper.views.crime_by_type'),
)
I admit that this is not the worlds best regular expression, but it will do for now. When a URL is requested from our project, the requested URL is checked against the list of URL patterns we provide. When a match is found the accompanying function is called. Obviously, we have not written this function yet, but will do so next.
The functions that are called when a URL request is matched are typically called views. Change back into the mapper application directory, and edit views.py. You should see the following:
#Create your views here.
This time Django doesn't provide us with anything by default. We know that we need to define a function named crime_by_type, because that is the function in urls.py that we indicated should be called when a matching URL is requested.
Add the following code to views.py:
from django.shortcuts import render_to_response, get_list_or_404
from crimemap.mapper.models import Crime
def crime_by_type(request, crimeType):
crime_list = get_list_or_404(Crime, type=crimeType)
return render_to_response('crime_by_type.html', {'crime_list': crime_list})
Every view function takes at least one argument, the request. Additional arguments are provided by capture groups from the matching regular expressions. If you look at our regular expression in urls.py, a portion of it is contained within parentheses. This is a capture group, and the contents of the requested URL that match the capture group are passed to our view function.
For example, let's assume someone requests:
http://www.crimemap.com/crime/homicide
The text string homicide is captured by the regular expression in urls.py, and passed to our view function. In the view function definition we give this captured data the name crimeType.
Our function creates a crime_list by asking Django to return all crimes that have a type which matches the requested URL. The function get_list_or_404 uses the Django ORM to query the database, and takes two arguments. In the first argument we tell it what type of objects we are looking for, which in this case is Crime objects. In the second argument we tell it what specific information we would like to match in our Crime objects when generating the list, which in this case is a Crime object with a type matching the URL request.
View function usually render a response, and do this by sending data to a template, which is then passed to the user's web browser. In our view function we take the crime_list we have just generated, and send it to a template named crime_by_type.html.
Change back into the crimemap directory, and make a new directory named templates:
mkdir templates
Change into the templates directory, and start editing a new text file named crime_by_type.html:
notepad++ crime_by_type.html
This opens the notepad++ editor with a blank text file. Add the following:
{% for crime in crime_list %}
{{crime.longitude}},{{ crime.latitude }},{{ crime.address }},{{ crime.type }},{{ crime.date }}
{% endfor %}
Templates are text files, and can contain anything you would normally find in the source code of a web page. Additionally, Django templates can contain a number of custom template tags and variables, which allow the data you send from your view functions to be integrated into the template before being sent to the user.
Template tags are surrounded by {% and %}. A list of template tags can be found in the built-in tag reference.
Variables are surrounded by {{ and }}. In the above example we have sent crime_list to the template from our view function. Using the {% for ... in ... %} template tag, we loop through each item in the list, and access each item's fields using variable notation. When we access each item's fields using variable notation, the values returned replace the variable tags, and the template tags are not rendered.
Data
At this point we actually have a functioning website, but we need to add some real data into our project to demonstrate how all the pieces fit together. Django provides a handy mechanism which allows us to provide initial data in SQL format. Change into the mapper application directory, and create a directory named sql. Now change into that directory, and use notepad++ to create a file named crime.sql:
notepad++ crime.sql
Add the following to this file, then save and exit from notepad++:
INSERT INTO mapper_crime (type, date, longitude, latitude, address) VALUES ('homicide', '2006-01-05', -78.817828, 42.904851, '17 Rapin Place');
INSERT INTO mapper_crime (type, date, longitude, latitude, address) VALUES ('homicide', '2006-04-17', -78.889458,42.897707, '155 Pennsylvania Street');
INSERT INTO mapper_crime (type, date, longitude, latitude, address) VALUES ('homicide', '2006-01-10', -78.884766,42.915257, '446 West Ferry');
INSERT INTO mapper_crime (type, date, longitude, latitude, address) VALUES ('arson', '2006-01-23', -78.849456,42.892344, '54 Grey Street');
This file includes a series of SQL statements which are executed by Django when we initialize our project database. Don't worry about the format of the above data, just recognize that we are adding four crimes into our project database, including three homicides and one arson.
While we are not using it for this project, please note that one of Django's strongest features is the admin interface, which provides a simple and powerful way for developers and site administrators to add and edit data in Django projects. The Django framework implements this powerful feature for us, which is a tremendous time saver during the web development process. You can read more about the admin site in the Django Book, but be aware that the way that this is implemented will change in the near future, and the changes are not reflected yet in all of the documentation.
Final Steps
Now that the code for our project is mostly complete, we can initialize our database and launch the built-in Django development web server to test our project.
We need to make one final set of changes to our project settings. Navigate to the crimemap project directory, and edit the settings.py file with notepad++.
At the very end of the settings.py file, you should see:
INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', )
We already know that a Django website usually, but not always, includes a single project and several applications. The INSTALLED_APPS section of the settings.py file is where we tell Django which applications we are using in our current project. The Django framework has several applications that are used so frequently they have been included by default in the standard settings.py file.
We won't use the functionality provided by these default applications in our current crimemap project, so lets remove them. Next, lets add the application we created, mapper, so that Django will know that it is part of the crimemap project. This section of settings.py should now look like the following:
INSTALLED_APPS = ( 'crimemap.mapper', )
Next, find the TEMPLATE_DIRS section, and make the following changes:
TEMPLATE_DIRS = (
"./templates/"
)
This setting tells Django where are template files are located. Note that we are using relative file paths for this tutorial, and in general you should use absolute file paths for you Django projects.
Our project settings are now complete. Let's initialize the project by executing the following command:
python manage.py syncdb
This should result in the following output:
Creating table mapper_crime Installing custom SQL for mapper.Crime model
When the syncdb command is run, Django looks at INSTALLED_APPS to determine what tables need to be created in the database. In this case Django will find a reference to our mapper application, and create tables in our database to handle our crime model.
After this command completes, execute the following command:
python manage.py runserver :80
The runserver command launches the development webserver built into Django. You should see the following output in the command window:
Validating models... 0 errors found. Django version 0.97-pre, using settings 'crimemap.settings' Development server is running at http://127.0.0.1:80/ Quit the server with CTRL-BREAK
The development webserver is now running on port 80. Open your favorite browser, and type in the following URL:
http://localhost/crime/arson
You should see the following output:
-78.849456,42.892344,54 Grey Street,arson,2006-01-23
This may not look like much, but we are almost done with our project. We have successfully taken a URL request from a web browser and passed it to a view function. Our view function has queried our database, using data extracted from our URL request to narrow down results. Those results were then rendered to the screen through a template that we created.
The only thing left to do is change our template, because the information we are sending to the template is already enough to display points on a Google map.
Quit the development server by pressing the Ctrl and Break keys at the same time. Navigate to the templates directory, and use notepad++ to edit crime_by_type.html. Delete the current contents of this file, and add the following:
<html style="height:100%;">
<head>
<title>Buffalo Crime Map</title>
<script src="http://maps.google.com/maps?file=api&v=2&" type="text/javascript">
</script>
</head>
<body onload="" onunload="GUnload()" style="height:100%;padding:0;margin:0;">
<script type="text/javascript">
var map_long = -78.820000;
var map_lat = 42.938000;
var map_zoom = 5;
var crimes = [{% for crime in crime_list %}[{{crime.longitude}},{{ crime.latitude }},'{{ crime.address }}','{{ crime.type }}','{{ crime.date }}']{% if not forloop.last %},{% endif %}{% endfor %}];
</script>
<div id="map" style="height:100%;width:100%;">
</div>
<script type="text/javascript">
function addCrimeMarker(lng, lat, address, crime_type, crime_date) {
var markerpt = new GPoint(lng, lat);
var marker = new GMarker(markerpt);
var html = '<b>' + crime_type + '</b><br>' + address + '<br>' + crime_date;
GEvent.addListener(marker, "click", function() { marker.openInfoWindowHtml(html); });
map.addOverlay(marker);
}
if (GBrowserIsCompatible()) {
var map_div = document.getElementById("map");
var map = new GMap2(map_div);
map.addControl(new GSmallMapControl());
map.addControl(new GMapTypeControl());
var pt = new GLatLng(top.map_lat, top.map_long);
map.setCenter(pt, 17 - top.map_zoom);
if (crimes) {
for (var i=0; i < crimes.length; i++) {
addCrimeMarker(crimes[i][0], crimes[i][1], crimes[i][2], crimes[i][3], crimes[i][4]);
}
}
}
</script>
</body>
</html>
This tutorial cannot cover the Google Maps API or javascript, but take a closer look at the template and you will see that we are using template tags and variables to get the data from our database into a javascript array, which is used to place the markers on the map. Take a look at the following section, which has been reformatted for improved readability:
var crimes = [
{% for crime in crime_list %}
[{{crime.longitude}},
{{ crime.latitude }},
'{{ crime.address }}',
'{{ crime.type }}',
'{{ crime.date }}']
{% if not forloop.last %},{% endif %}
{% endfor %}
];
This is very similar to the simple template we created earlier in the chapter. There are some formatting changes to make the output valid javascript, and an additional template tag to treat the last item in the list differently. The output from our template tags and variables is substituted into the template, which uses the output as an array of javascript data.
Save your changes to the template, then restart the development server with the runserver command, like we did previously . Now go back to your browser and load the following url again:
http://localhost/crime/arson
You should now see a Google map with a single marker. This marker is at the site of the arson in our database. Now try the following URL:
http://localhost/crime/homicide
You should now see a Google map with three markers, at the precise locations of the homicides in our database.
To exit from the command prompt window, first quit the development server, then type:
exit
You will be asked if you want to terminate the batch job. Just answer yes by typing Y and the window will close.
Static Content
Static content is anything on your webpage which does not change each time the page is served to a user. Images and stylesheets are usually static, and javascript libraries can also be static.
The Django framework is not designed to serve static content, leaving that job to whichever webserver is being used in the production environment. Django is designed to generate dynamic content, so using it to serves static content can be slow and insecure.
Despite the drawbacks to using Django to serve static content, it is often desirable to do so during development. Since Instant Django uses the built in Django development server, there are a few tricks we need to know to get static files served.
We will need to make changes to our template, url configuration, and settings file to get things working.
Let's start with the template. Start up Instant Django, and navigate to your template directory. Edit crime_by_type.html so that it contains the following:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
<title>Buffalo Crime Map</title>
<link rel=Stylesheet href="http://localhost/static/style.css" type="text/css">
<script src="http://maps.google.com/maps?file=api&v=2&" type="text/javascript"></script>
<script src="http://localhost/static/cVerticalMapTypeControl.js"></script>
</head>
<body onload="" onunload="GUnload()">
<script type="text/javascript">
var map_long = -78.820000;
var map_lat = 42.938000;
var map_zoom = 5;
var crimes = [{% for crime in crime_list %}[{{crime.longitude}},{{ crime.latitude }},'{{ crime.address }}','{{ crime.type }}','{{ crime.date }}']{% if not forloop.last %},{% endif %}{% endfor %}];
</script>
<div id="map"></div>
<script type="text/javascript">
function addCrimeMarker(lng, lat, address, crime_type, crime_date) {
var markerpt = new GPoint(lng, lat);
var marker = new GMarker(markerpt);
var html = '<b>' + crime_type + '</b><br>' + address + '<br>' + crime_date;
GEvent.addListener(marker, "click", function() { marker.openInfoWindowHtml(html); });
map.addOverlay(marker);
}
if (GBrowserIsCompatible()) {
var map_div = document.getElementById("map");
var map = new GMap2(map_div);
var pos1 = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(19,170));
map.addControl(new GSmallMapControl(),pos1 );
var pos2 = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(7,90));
map.addControl(new cVerticalMapTypeControl(), pos2);
var pt = new GLatLng(top.map_lat, top.map_long);
map.setCenter(pt, 17 - top.map_zoom);
if (crimes) {
for (var i=0; i < crimes.length; i++) {
addCrimeMarker(crimes[i][0], crimes[i][1], crimes[i][2], crimes[i][3], crimes[i][4]);
}
}
}
</script>
<div id="banner" class="mapElement">
<div id="bannerInner">
<p><b>Buffalo</p><img src="http://localhost/static/buffalo.png"><p>Crime</b></p>
</div>
</div>
<div id="nav">
<h4>Crimes</h4>
<ul class="ul-cat">
<li><a href="http://localhost/crime/homicide/">Homicide<a></li>
<li><a href="http://localhost/crime/drugs">Drugs</a></li>
<li><a href="http://localhost/crime/prostitution/">Prostitution<a></li>
<li><a href="http://localhost/crime/robbery">Armed Robbery</a></li>
<li><a href="http://localhost/crime/automobile_theft">Automobile Theft<a/></li>
<li><a href="http://localhost/crime/arson">Arson</a></li>
</ul>
</div>
<div id="footer">
<p>Nickel Cigar Publications © 2007.</p>
</div>
<div id="disclaimer">
<p>Not associated with the City of Buffalo or the Buffalo Police Department.<p>
</div>
</body>
</html>
Examining this template you can see that we have removed the style information that was embedded in the html of our template, and are now referencing a static CSS file. We have added some additional elements to enhance the look of the page. We are also referencing a static javascript file which contains a custom Google map control, and a static image file. All of the static content is referenced using the same base url:
http://localhost/static/
Change out of the templates directory into the crimemap directory. Now create a directory named static. Change into this directory and create a new file named style.css. Add the following to this file:
* {
padding:0;
margin:0;
}
html, body {
height: 100%;
font-family: arial, sans-serif;
font-size: 12px;
}
h4 {
font-size: 14px;
border-bottom: 1px solid black;
}
a{
text-decoration: none;
color: black;
}
a:hover{
text-decoration: underline;
}
#map {
height: 100%;
width: 100%;
}
#banner {
position: absolute;
top: 7px;
left: 7px;
width: 5em;
}
#bannerInner {
border-style: solid;
border-color: white rgb(176, 176, 176) rgb(176, 176, 176) white;
border-width: 1px;
background-color: white;
text-align: center;
}
#bannerInner p{
margin: 2px;
}
#nav{
position: absolute;
top: 7px;
right: 7px;
width: 207px;
background-color: white;
color: black;
border: 1px solid black;
}
#nav h4, #datenav h4{
margin: 5px;
}
#nav ul, #datenav ul{
margin-top: 10px;
list-style: none;
}
#datenav{
position: absolute;
top:200px;
right:7px;
width:207px;
background-color:white;
border: 1px solid black;
}
#footer{
font-size: 10px;
font-weight: bold;
background-color: white;
border: 1px solid black;
position: absolute;
right: 7px;
width: 207px;
bottom: 65px;
}
#footer p{
margin: 3px;
}
#disclaimer {
font-size: 10px;
font-weight: bold;
background-color: white;
position: absolute;
bottom: 30px;
right: 7px;
width: 207px;
border: 1px solid black;
}
#disclaimer p{
margin: 3px;
}
.mapElement {
border: 1px solid black;
position: absolute;
background-color: white;
text-align: center;
cursor: pointer;
}
.ul-cat {
list-style: none;
margin:0px 0px 25px 5px;
padding:0px;
}
.ul-cat li{
margin: 0px;
padding: 3px 0px 2px 22px;
}
.ul-cat li.selected{
background: url(mini-nav-right.gif) no-repeat left 4px;
}
The style information we removed from our original template is added back with the above CSS, and the new elements we added to our template are also styled.
Now create a new file named cVerticalMapTypeControl.js, and add the following to this file:
function cVerticalMapTypeControl() {
}
cVerticalMapTypeControl.prototype = new GControl();
cVerticalMapTypeControl.prototype.initialize = function(map) {
var cvmptc_ = this;
var container = document.createElement("div");
container.style.position = 'absolute';
var mapButtonContainerDiv = document.createElement("div");
this.setButtonContainerStyle(mapButtonContainerDiv);
var mapButtonDiv = document.createElement("div");
this.setSelectedButtonStyle(mapButtonDiv);
mapButtonContainerDiv.style.top = '0em';
container.appendChild(mapButtonContainerDiv);
mapButtonContainerDiv.appendChild(mapButtonDiv);
mapButtonDiv.appendChild(document.createTextNode("Map"));
GEvent.addDomListener(mapButtonDiv, "click", function() {
map.setMapType(G_NORMAL_MAP);
cvmptc_.setSelectedButtonStyle(mapButtonDiv);
cvmptc_.setUnSelectedButtonStyle(satelliteButtonDiv);
cvmptc_.setUnSelectedButtonStyle(hybridButtonDiv);
});
var satelliteButtonContainerDiv = document.createElement("div");
this.setButtonContainerStyle(satelliteButtonContainerDiv);
var satelliteButtonDiv = document.createElement("div");
this.setUnSelectedButtonStyle(satelliteButtonDiv);
satelliteButtonContainerDiv.style.top = '2em';
container.appendChild(satelliteButtonContainerDiv);
satelliteButtonContainerDiv.appendChild(satelliteButtonDiv);
satelliteButtonDiv.appendChild(document.createTextNode("Satellite"));
GEvent.addDomListener(satelliteButtonDiv, "click", function() {
map.setMapType(G_SATELLITE_MAP);
cvmptc_.setSelectedButtonStyle(satelliteButtonDiv);
cvmptc_.setUnSelectedButtonStyle(mapButtonDiv);
cvmptc_.setUnSelectedButtonStyle(hybridButtonDiv);
});
var hybridButtonContainerDiv = document.createElement("div");
this.setButtonContainerStyle(hybridButtonContainerDiv);
var hybridButtonDiv = document.createElement("div");
this.setUnSelectedButtonStyle(hybridButtonDiv);
hybridButtonContainerDiv.style.top = '4em';
container.appendChild(hybridButtonContainerDiv);
hybridButtonContainerDiv.appendChild(hybridButtonDiv);
hybridButtonDiv.appendChild(document.createTextNode("Hybrid"));
GEvent.addDomListener(hybridButtonDiv, "click", function() {
map.setMapType(G_HYBRID_MAP);
cvmptc_.setSelectedButtonStyle(hybridButtonDiv);
cvmptc_.setUnSelectedButtonStyle(mapButtonDiv);
cvmptc_.setUnSelectedButtonStyle(satelliteButtonDiv);
});
map.getContainer().appendChild(container);
return container;
}
cVerticalMapTypeControl.prototype.getDefaultPosition = function() {
return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(90,7));
}
cVerticalMapTypeControl.prototype.setButtonContainerStyle = function(div) {
div.style.border = '1px solid black';
div.style.position = 'absolute';
div.style.backgroundColor = 'white';
div.style.textAlign = 'center';
div.style.width = '5em';
div.style.cursor= 'pointer';
}
cVerticalMapTypeControl.prototype.setSelectedButtonStyle = function(div) {
div.style.border = 'solid';
div.style.borderColor = 'rgb(176, 176, 176) white white rgb(176, 176, 176)';
div.style.borderWidth = '1px';
div.style.fontSize = '12px';
div.style.fontWeight = 'bold';
}
cVerticalMapTypeControl.prototype.setUnSelectedButtonStyle = function(div) {
div.style.border = 'solid';
div.style.borderColor = 'white rgb(176, 176, 176) rgb(176, 176, 176) white';
div.style.borderWidth = '1px';
div.style.fontSize = '12px';
div.style.fontWeight = 'normal';
}
The above javascript is code I have written to provide a custom map type control. Normal Google map type control buttons give the user the option of viewing a "Map", "Satellite", or "Hybrid" map, with buttons displayed in a horizontal row. The custom javascript code I have written displays them in a vertical column, which I think is more visually appealing.
Our new template and CSS references a static image:
![]()
buffalo.png
Save this image into your static directory.
Now that we have created our static content and updated our template, we need to configure the Django development server to handle it. Edit your settings.py file so that the following variables are set:
# Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" MEDIA_ROOT = './static/' # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). # Examples: "http://media.lawrence.com", "http://example.com/media/" MEDIA_URL = 'static/'
This lets Django now the path to the static content. We are using a simple trick, providing a relative path instead of an absolute path, so that our code remains portable.
The last change we need to make to serve static content is in our URL configuration. Edit urls.py so that it contains the following:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^crime/([A-Za-z]+)', 'crimemap.mapper.views.crime_by_type'),
(r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': 'static'}),
)
We have added an additional pattern that handles requests for static content. Everything should now be in working order. Launch the development server, and navigate to:
http://localhost/crime/homicide
You should now see additional content overlaying our map, including static images, additional html styled by CSS, and the custom map buttons created by our javascript library.
Summary
Hopefully this tutorial has provided a quick and interesting introduction to the Django framework. We have covered the basic steps in creating Django projects:
- Define our data models
- Create our URL schema
- Code our view functions
- Design our templates
Obviously, our project is lacking the details that would make it a functional website, like working navigation, error handling, and security, but we are off to a good start.
If you are interested in learning more about Django, head over to the documentation, or check out the Django Book.
Please let me know if you find errors in the portable environment, or in the text of the tutorial.
