рдирдорд╕реНрдХрд╛рд░ред рдпрд╣ рдХрд╛рд░реНрдп django рдкрд░рд┐рдпреЛрдЬрдирд╛рдУрдВ рдореЗрдВ рд╕реЗ рдПрдХ рдореЗрдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рд▓рд┐рдП рдЬрд┐рдпреЛрд▓реЛрдХреЗрд╢рди (рдЧреВрдЧрд▓ рдореИрдкреНрд╕ v3) рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛, рдореИрдВ рдЕрдкрдирд╛ рд╕рдорд╛рдзрд╛рди рд╕рд╛рдЭрд╛ рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВред
рдЖрд╡рд╢реНрдпрдХ рдХрд╛рд░реНрдпрд╢реАрд▓рддрд╛:
- рд╡рд░реНрддрдорд╛рди рд╕реНрдерд┐рддрд┐ рдХреЗ рдПрдХ рдорд╛рд░реНрдХрд░ рдХреЗ рд╕рд╛рде рдореИрдк рдЖрдЙрдЯрдкреБрдЯ, рдХреНрд▓рд┐рдХ рдЗрд╡реЗрдВрдЯ рджреНрд╡рд╛рд░рд╛ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдорд╛рд░реНрдХрд░ (рдЦреАрдВрдЪрдиреЗ) рдХреЛ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛
- рдкрддрд╛ рджреНрд╡рд╛рд░рд╛ рдЦреЛрдЬреЗрдВ (рд╕реНрд╡рддрдГ рдкреВрд░реНрдг)
- рдирд┐рд░реНрджреЗрд╢рд╛рдВрдХ рдФрд░ рдкрддреЗ рджреЛрдиреЛрдВ рдХреЛ рд╕рд╣реЗрдЬрдирд╛ (рдпрджрд┐ рдпрд╣ рдПрдХ рдЬрдЧрд╣ рд╣реИ)
рд╣рдореЗрд╢рд╛ рдХреА рддрд░рд╣, рд╡рд┐рдХрд╛рд╕ рд╕рдорд╛рди рд╕рдорд╛рдзрд╛рдиреЛрдВ рдХреА рдЦреЛрдЬ рдХреЗ рд╕рд╛рде рд╢реБрд░реВ рд╣реБрдЖ, рдФрд░ рдЙрджрд╛рд╣рд░рдг рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХрд╛ рдПрдХ рд╣рд┐рд╕реНрд╕рд╛ рджрд┐рдпрд╛ рдЧрдпрд╛, рдЬрд┐рд╕рдХрд╛ рдирд╛рдо рдерд╛ 1 рдЕрдВрдХред
рд╕реНрдирд┐рдкреЗрдЯ рд╕реЗ рд▓рд┐рдВрдХ рдХрд░реЗрдВ ред рдЗрд╕рдореЗрдВ рдХрдИ рдХрдорд┐рдпрд╛рдВ рдереАрдВред рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдореЗрдВ рдХрд╕реНрдЯрдо рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдлрд╝реАрд▓реНрдб рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП, рдореИрдВрдиреЗ рдорд╛рдирдХ рдореЙрдбреНрдпреВрд▓ AUTH_PROFILE_MODULE рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ред рддрджрдиреБрд╕рд╛рд░, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓ рдХреЛ рд╕рдВрдкрд╛рджрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдкреИрдирд▓ рдореЗрдВ, рдореИрдВрдиреЗ рдмреНрд▓реЙрдХреЛрдВ (admin.StackedInline) рдХреЗ рд╕рд╛рде рдЗрдирд▓рд╛рдЗрди рдлрд╝реАрд▓реНрдб рдЬреЛрдбрд╝реЗред рдЗрди рдЗрдирд▓рд╛рдЗрди рдмреНрд▓реЙрдХ рдХреЗ рд▓рд┐рдП рдорд╛рд░реНрдХрдЕрдк рдЬрдирд░реЗрдЯ рдХрд░рддреЗ рд╕рдордп, django рдЗрдирдкреБрдЯ рдХреЗ рд▓рд┐рдП рдЖрдИрдбреА рдореЗрдВ "-" рд╕рдВрдХреЗрдд рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИред рдкреНрд░рддреНрдпреЗрдХ рдмреНрд▓реЙрдХ рдореЗрдВ рдЙрдкрд╕рд░реНрдЧ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдПред рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ, рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рдЬрд╛рдирддреЗ рд╣реИрдВ, рдлрд╝рдВрдХреНрд╢рди рдирд╛рдореЛрдВ рдореЗрдВ "-" рдХрд╛ рдЙрдкрдпреЛрдЧ рдкрд╕рдВрдж рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдкрд╣рд▓реА рдЪреАрдЬ рдЬреЛ рд╣рдо рдХрд░рддреЗ рд╣реИрдВ, рд╡рд╣ рд╕рднреА рд╕рдВрдХреЗрддреЛрдВ рдХреЛ "-" рдлрд╝рдВрдХреНрд╢рди рдирд╛рдо "_" рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рдХрд░рддрд╛ рд╣реИред
functionName=name.replace('-', '_')
рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдирд┐рд░реНрджреЗрд╢рд╛рдВрдХ рдПрдХ рд╕реНрдЯреНрд░рд┐рдВрдЧ "x, y" рдХреЗ рд░реВрдк рдореЗрдВ рд╕рд╣реЗрдЬреЗ рдЧрдП рдереЗ, рдЬрд┐рд╕рдХреЗ рдмрд╛рдж рдЖрдЙрдЯрдкреБрдЯ рдХреЗ рд▓рд┐рдП рдПрдХ рд╡рд┐рднрд╛рдЬрди рдерд╛ред рдпрд╣ рдПрдХ рд╡рд┐рд░реЛрдзрд╛рднрд╛рд╕ рдХрд╛ рдХрд╛рд░рдг рдмрдирддрд╛ рд╣реИ рдЬрдм рдХреНрд╖реЗрддреНрд░ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдкрд░ рднреА рдпрд╣ рдкрддрд╛ рдЪрд▓рддрд╛ рд╣реИ рдХрд┐ рдпреЗ рд╡рд╣реА рдЕрд▓реНрдкрд╡рд┐рд░рд╛рдо рд╣реЛ рд╕рдХрддреЗ рд╣реИрдВред рдПрдХ рд╕рдорд╛рдзрд╛рди рдХреЗ рд░реВрдк рдореЗрдВ, рдПрдХ рдФрд░ рд╕реНрдирд┐рдкреЗрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ рдЬреЛ рдХрд┐ JSON рдСрдмреНрдЬреЗрдХреНрдЯреНрд╕ рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП TextField рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рд╕рдВрднрд╡ рдмрдирд╛рддрд╛ рд╣реИред
рд╕реНрдирд┐рдкреЗрдЯ рд╕реЗ рд▓рд┐рдВрдХ рдХрд░реЗрдВ ред рдЗрд╕ рдкреНрд░рдХрд╛рд░, рдирд┐рд░реНрджреЗрд╢рд╛рдВрдХ рдФрд░ рдкрддреЗ JSON рдСрдмреНрдЬреЗрдХреНрдЯ рдХреЗ рд░реВрдк рдореЗрдВ рд╕рд╣реЗрдЬреЗ рдЧрдП рдереЗ:
value = {'lat': lat, 'lng': lng, 'address': address}
рд╕рд░реНрд╡рд░ рдкрд░ рдкреНрд░рджрд╛рди рдХрд░рддреЗ рд╕рдордп рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг:
if value is None: lat, lng, address = DEFAULT_LAT, DEFAULT_LNG, DEFAULT_ADDRESS value = {'lat': lat, 'lng': lng, 'address': address} else: lat, lng, address = float(value['lat']), float(value['lng']), value['address'] curLocation = json.dumps(value, cls=DjangoJSONEncoder)
рдЧреНрд░рд╛рд╣рдХ рдкрдХреНрд╖ рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг:
function savePosition_%(functionName)s(point, address) { var input = document.getElementById("id_%(name)s"); var location = {'lat': point.lat().toFixed(6), 'lng': point.lng().toFixed(6)}; location.address = '%(defAddress)s'; if (address) { location.address = address; } input.value = JSON.stringify(location); map_%(functionName)s.panTo(point); }
рд╡рд░реНрддрдорд╛рди рдЬрд┐рдпреЛрд▓реЛрдХреЗрд╢рди рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдлрд╝реАрд▓реНрдб рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛:
html += '<br /><label>%s: </label><span>%s</span>' % (u' ', address)
2 рдЕрдВрдХ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, google.maps.Geocoder рдФрд░ jQuery рд╕реНрд╡рддрдГ рдкреВрд░реНрдг рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛:
google.maps.event.addListener(marker, 'dragend', function(mouseEvent) { geocoder.geocode({'latLng': mouseEvent.latLng}, function(results, status) { if (status == google.maps.GeocoderStatus.OK && results[0]) { $('#address_%(name)s').val(results[0].formatted_address); savePosition_%(functionName)s(mouseEvent.latLng, results[0].formatted_address); } else { savePosition_%(functionName)s(mouseEvent.latLng); } }); }); google.maps.event.addListener(map_%(functionName)s, 'click', function(mouseEvent){ marker.setPosition(mouseEvent.latLng); geocoder.geocode({'latLng': mouseEvent.latLng}, function(results, status) { if (status == google.maps.GeocoderStatus.OK && results[0]) { $('#address_%(name)s').val(results[0].formatted_address); savePosition_%(functionName)s(mouseEvent.latLng, results[0].formatted_address); } else { savePosition_%(functionName)s(mouseEvent.latLng); } }); }); $('#address_%(name)s').autocomplete({ source: function(request, response) { geocoder.geocode({'address': request.term}, function(results, status) { response($.map(results, function(item) { return { value: item.formatted_address, location: item.geometry.location } })); }) }, select: function(event, ui) { marker.setPosition(ui.item.location); savePosition_%(functionName)s(ui.item.location, ui.item.value); } });
рдЦреЛрдЬ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдлрд╝реАрд▓реНрдб рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛:
html += '<label>%s: </label><input id="address_%s" type="text"/>' % (u' ', name)
рдпрд╣ рд╕рдм рдПрдХ рд╕рд╛рде рд░рдЦрдХрд░ рд╣рдореЗрдВ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдЕрдВрддрд┐рдо рд╕реНрдирд┐рдкреЗрдЯ рдорд┐рд▓рд╛:
from django.conf import settings from main.JSONField import JSONField from django.core.serializers.json import DjangoJSONEncoder from django.utils import simplejson as json DEFAULT_WIDTH = 300 DEFAULT_HEIGHT = 300 DEFAULT_LAT = 55.75 DEFAULT_LNG = 37.62 DEFAULT_ADDRESS = u'( )' class LocationWidget(forms.TextInput): def __init__(self, *args, **kw): self.map_width = kw.get("map_width", DEFAULT_WIDTH) self.map_height = kw.get("map_height", DEFAULT_HEIGHT) super(LocationWidget, self).__init__(*args, **kw) self.inner_widget = forms.widgets.HiddenInput() def render(self, name, value, *args, **kwargs): if value is None: lat, lng, address = DEFAULT_LAT, DEFAULT_LNG, DEFAULT_ADDRESS value = {'lat': lat, 'lng': lng, 'address': address} else: lat, lng, address = float(value['lat']), float(value['lng']), value['address'] curLocation = json.dumps(value, cls=DjangoJSONEncoder) js = ''' <script type="text/javascript"> //<![CDATA[ var map_%(functionName)s; function savePosition_%(functionName)s(point, address) { var input = document.getElementById("id_%(name)s"); var location = {'lat': point.lat().toFixed(6), 'lng': point.lng().toFixed(6)}; location.address = '%(defAddress)s'; if (address) { location.address = address; } input.value = JSON.stringify(location); map_%(functionName)s.panTo(point); } function load_%(functionName)s() { var point = new google.maps.LatLng(%(lat)f, %(lng)f); var options = { zoom: 13, center: point, mapTypeId: google.maps.MapTypeId.ROADMAP }; map_%(functionName)s = new google.maps.Map(document.getElementById("map_%(name)s"), options); geocoder = new google.maps.Geocoder(); var marker = new google.maps.Marker({ map: map_%(functionName)s, position: point, draggable: true }); google.maps.event.addListener(marker, 'dragend', function(mouseEvent) { geocoder.geocode({'latLng': mouseEvent.latLng}, function(results, status) { if (status == google.maps.GeocoderStatus.OK && results[0]) { $('#address_%(name)s').val(results[0].formatted_address); savePosition_%(functionName)s(mouseEvent.latLng, results[0].formatted_address); } else { savePosition_%(functionName)s(mouseEvent.latLng); } }); }); google.maps.event.addListener(map_%(functionName)s, 'click', function(mouseEvent){ marker.setPosition(mouseEvent.latLng); geocoder.geocode({'latLng': mouseEvent.latLng}, function(results, status) { if (status == google.maps.GeocoderStatus.OK && results[0]) { $('#address_%(name)s').val(results[0].formatted_address); savePosition_%(functionName)s(mouseEvent.latLng, results[0].formatted_address); } else { savePosition_%(functionName)s(mouseEvent.latLng); } }); }); $('#address_%(name)s').autocomplete({ source: function(request, response) { geocoder.geocode({'address': request.term}, function(results, status) { response($.map(results, function(item) { return { value: item.formatted_address, location: item.geometry.location } })); }) }, select: function(event, ui) { marker.setPosition(ui.item.location); savePosition_%(functionName)s(ui.item.location, ui.item.value); } }); } $(document).ready(function(){ load_%(functionName)s(); }); //]]> </script> ''' % dict(functionName=name.replace('-', '_'), name=name, lat=lat, lng=lng, defAddress=DEFAULT_ADDRESS) html = self.inner_widget.render("%s" % name, "%s" % curLocation, dict(id='id_%s' % name)) html += '<div id="map_%s" style="width: %dpx; height: %dpx"></div>' % (name, self.map_width, self.map_height) html += '<label>%s: </label><input id="address_%s" type="text"/>' % (u' ', name) html += '<br /><label>%s: </label><span>%s</span>' % (u' ', address) return mark_safe(js + html) class Media: css = {'all': ( 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/redmond/jquery-ui.css', settings.MEDIA_URL+'css/main.css', )} js = ( 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js', 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js', 'http://maps.google.com/maps/api/js?sensor=false', ) class LocationField(JSONField): def formfield(self, **kwargs): defaults = {'widget': LocationWidget} return super(LocationField, self).formfield(**defaults)
рдкреБрдирд╢реНрдЪ
Main.css рдореЗрдВ рдирд┐рд╣рд┐рдд рд╣реИ:
.ui-autocomplete li { list-style-type: none; }
рдпрд╣ рд╡реНрдпрд╡рд╕реНрдерд╛рдкрдХ рдкреИрдирд▓ рдореЗрдВ рдХреИрд╕рд╛ рджрд┐рдЦрддрд╛ рд╣реИ:

рдЖрдк рд╕рднреА рдХреЛ рдзрдиреНрдпрд╡рд╛рдж!