How to do it...

The administration of the Location model is as simple as it can be. Perform the
following steps:

  1. Let's create the administration settings for the Location model. Note that we are using the get_fieldsets() method to define the field sets, with a description rendered from a template, as follows:
# locations/admin.py
from django.contrib import admin
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _

from .models import Location


class LocationAdmin(admin.ModelAdmin):
save_on_top = True
list_display = ("title", "street", "description")
search_fields = ("title", "street", "description")

def get_fieldsets(self, request, obj=None):
map_html = render_to_string("admin/includes/map.html")
fieldsets = [
(_("Main Data"), {"fields": ("title",
"description")}),
(_("Address"), {"fields": ("street",
"street2",
"postal_code",
"city",
"country",
"latitude",
"longitude")}),
(_("Map"), {"description": map_html, "fields": []}),
]
return fieldsets


admin.site.register(Location, LocationAdmin)
  1. To create a custom change form template, add a new change_form.html file under admin/locations/location/, in your templates directory. This template will extend from the default admin/change_form.html template, and will overwrite the extrastyle and field_sets blocks, as follows:
{# templates/admin/locations/location/change_form.html #}
{% extends "admin/change_form.html" %}
{% load i18n static admin_modify admin_static admin_urls %}

{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css"
href="{% static 'site/css/location.css' %}" />
{% endblock %}

{% block field_sets %}
{% for fieldset in adminform %}
{% include "admin/includes/fieldset.html" %}
{% endfor %}
<script type="text/javascript"
src="http://maps.google.com/maps/api/js?language=en"></script>
<script type="text/javascript"
src="{% static 'site/js/location.js' %}"></script>
{% endblock %}
  1. Then, we have to create the template for the map that will be inserted in the Map field set, as follows:
{# templates/admin/locations/includes/map.html #}
{% load i18n %}
<div class="form-row map">
<div class="canvas">
<!-- THE GMAPS WILL BE INSERTED HERE DYNAMICALLY -->
</div>
<ul class="locations"></ul>
<div class="btn-group">
<button type="button"
class="btn btn-default locate-address">
{% trans "Locate address" %}
</button>
<button type="button"
class="btn btn-default remove-geo">
{% trans "Remove from map" %}
</button>
</div>
</div>
  1. Of course, the map won't be styled by default. Therefore, we will have to add some CSS, as shown in the following code:
/* static/locations/css/map.css */
.map {
box-sizing: border-box;
width: 98%;
}
.map .canvas,
.map ul.locations,
.map .btn-group {
margin: 1rem 0;
}
.map .canvas {
border: 1px solid #000;
box-sizing: padding-box;
height: 0;
padding-bottom: calc(9 / 16 * 100%); /* 16:9 aspect ratio */
width: 100%;
}
.map .canvas:before {
color: #eee;
color: rgba(0, 0, 0, 0.1);
content: "map";
display: block;
font-size: 5rem;
line-height: 5rem;
margin-top: -25%;
padding-top: calc(50% - 2.5rem);
text-align: center;
}
.map ul.locations {
padding: 0;
}
.map ul.locations li {
border-bottom: 1px solid #ccc;
list-style: none;
}
.map ul.locations li:first-child {
border-top: 1px solid #ccc;
}
.map .btn-group .btn.remove-geo {
float: right;
}
  1. Next, let's create a change_form.js JavaScript file, which will need to be added to the project's static files, either by directly copying or by using the collectstatic management command. We don't want to pollute the environment with global variables; therefore, we will start with a closure, to make a private scope for variables and functions.
A closure is a function scope within which variables that are not accessible to the outer scope can be defined, but where the enclosing scope variables can be accessed.

We will be using jQuery in this file (as jQuery comes with the contributed administration system and makes the work easy and cross-browser), as follows:

// static/locations/js/change_form.js
(function ($, undefined) {
    var gettext = window.gettext || function (val) {
        return val;
    };
var $map, $foundLocations, $lat, $lng, $street, $street2,
$city, $country, $postalCode, gMap, gMarker; // ...this is where all the further JavaScript functions go... }(django.jQuery));
  1. We will create JavaScript functions and add them to change_form.js, one by one. The getAddress4search() function will collect the address string from the address fields that can be used later for geocoding, as follows:
function getAddress4search() {
var sStreetAddress2 = $street2.val();
if (sStreetAddress2) {
sStreetAddress2 = " " + sStreetAddress2;
}

return [
$street.val() + sStreetAddress2,
$city.val(),
$country.val(),
$postalCode.val()
].join(", ");
}
  1. The updateMarker() function will take the latitude and longitude arguments and draw or move a marker on the map. It will also make the marker draggable, as follows:
function updateMarker(lat, lng) {
var point = new google.maps.LatLng(lat, lng);

if (!gMarker) {
gMarker = new google.maps.Marker({
position: point,
map: gMap
});
}

gMarker.setPosition(point);
gMap.panTo(point, 15);
gMarker.setDraggable(true);

google.maps.event.addListener(gMarker, "dragend", function() {
var point = gMarker.getPosition();
updateLatitudeAndLongitude(point.lat(), point.lng());
});
}
  1. The updateLatitudeAndLongitude() function, referenced in the preceding dragend event listener, takes the latitude and longitude arguments and updates the values for the fields with the id_latitude and id_longitude IDs, as follows:
function updateLatitudeAndLongitude(lat, lng) {
var precision = 1000000;
$lat.val(Math.round(lng * precision) / precision);
$lng.val(Math.round(lat * precision) / precision);
}
  1. The autocompleteAddress() function gets the results from Google Maps geocoding, and lists them under the map, in order to select the correct one; or, if there is just one result, it updates the geographical position and address fields, as follows:
function autocompleteAddress(results) {
var $item = $('<li/>');
var $link = $('<a href="#"/>');

$foundLocations.html("");
results = results || [];

if (results.length) {
results.forEach(function (result, i) {
$link.clone()
.html(result.formatted_address)
.click(function (event) {
event.preventDefault();
updateAddressFields(result.address_components);

var point = result.geometry.location;
updateLatitudeAndLongitude(
point.lat(), point.lng());
updateMarker(point.lat(), point.lng());
$foundLocations.hide();
})
.appendTo($item.clone().appendTo($foundLocations));
});
$link.clone()
.html(gettext("None of the above"))
.click(function(event) {
event.preventDefault();
$foundLocations.hide();
})
.appendTo($item.clone().appendTo($foundLocations));
} else {
$foundLocations.hide();
}
}
  1. The updateAddressFields() function takes a nested dictionary, with the address components as an argument, and fills in all of the address fields, as follows:
function updateAddressFields(addressComponents) {
var streetName, streetNumber;
var typeActions = {
"locality": function(obj) {
$city.val(obj.long_name);
},
"street_number": function(obj) {
streetNumber = obj.long_name;
},
"route": function(obj) {
streetName = obj.long_name;
},
"postal_code": function(obj) {
$postalCode.val(obj.long_name);
},
"country": function(obj) {
$country.val(obj.short_name);
}
};

addressComponents.forEach(function(component) {
var action = typeActions[component.types[0]];
if (typeof action === "function") {
action(component);
}
});

if (streetName) {
var streetAddress = streetName;
if (streetNumber) {
streetAddress += " " + streetNumber;
}
$street.val(streetAddress);
}
}
  1. Finally, we have the initialization function, which is called on the page load. It attaches the onclick event handlers to the buttons, creates a Google Map, and, initially, marks the geoposition that is defined in the latitude and longitude fields, as follows:
$(function(){
$map = $(".map");

$foundLocations = $map.find("ul.locations").hide();
$lat = $("#id_latitude");
$lng = $("#id_longitude");
$street = $("#id_street");
$street2 = $("#id_street2");
$city = $("#id_city");
$country = $("#id_country");
$postalCode = $("#id_postal_code");

$map.find("button.locate-address")
.click(function(event) {
var oGeocoder = new google.maps.Geocoder();
oGeocoder.geocode(
{address: getAddress4search()},
function (results, status) {
if (status === google.maps.GeocoderStatus.OK) {
autocompleteAddress(results);
} else {
autocompleteAddress(false);
}
}
);
});

$map.find("button.remove-geo")
.click(function() {
$lat.val("");
$lng.val("");
gMarker.setMap(null);
gMarker = null;
});

    gMap = new google.maps.Map($map.find(".canvas").get(0), {
scrollwheel: false,
zoom: 16,
center: new google.maps.LatLng(51.511214, -0.119824),
disableDoubleClickZoom: true
});

google.maps.event.addListener(gMap, "dblclick", function(event) {
var lat = event.latLng.lat();
var lng = event.latLng.lng();
updateLatitudeAndLongitude(lat, lng);
updateMarker(lat, lng);
});

if ($lat.val() && $lng.val()) {
updateMarker($lat.val(), $lng.val());
}
});
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.103.8