├── .github └── workflows │ ├── django.yml │ └── python-publish.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.rst ├── README_kr.rst ├── __init__.py ├── django_google_maps ├── __init__.py ├── fields.py ├── models.py ├── static │ └── django_google_maps │ │ ├── css │ │ └── google-maps-admin.css │ │ └── js │ │ └── google-maps-admin.js ├── templates │ └── django_google_maps │ │ └── widgets │ │ └── map_widget.html ├── tests │ ├── __init__.py │ ├── test_app │ │ ├── __init__.py │ │ └── models.py │ ├── test_geolocation_field.py │ ├── test_geopt_field.py │ ├── test_typename.py │ └── test_widget.py └── widgets.py ├── manage.py ├── requirements ├── requirements.txt └── test.txt ├── sample ├── __init__.py ├── admin.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── templates │ └── sample │ │ └── index.html ├── tests.py └── views.py ├── settings.py ├── setup.py └── urls.py /.github/workflows/django.yml: -------------------------------------------------------------------------------- 1 | name: Django CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | max-parallel: 4 15 | matrix: 16 | python-version: ['3.8', '3.9', '3.10'] 17 | django-version: ['2.2', '3.2', '4.0'] 18 | include: 19 | - python-version: '3.7' 20 | django-version: '2.2' 21 | - python-version: '3.7' 22 | django-version: '3.2' 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v2 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | - name: Install Dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install -r requirements/test.txt 34 | pip install -r requirements/requirements.txt 35 | pip install -q Django==${{ matrix.django-version }} 36 | - name: Run Tests 37 | run: | 38 | coverage run --source=django_google_maps manage.py test 39 | coverage report --show-missing 40 | flake8 django_google_maps --max-line-length=120 --max-complexity=4 41 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | sample_db 3 | build/* 4 | *.egg-info 5 | dist 6 | .coverage 7 | venv/ 8 | *.pyc 9 | *.pyo 10 | .tox/* 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.13.0] - 2022-03-22 2 | - Added: Django 4.0 support 3 | - Added: Python 3.10 support 4 | 5 | ## [0.12.4] - 2021-04-06 6 | - Added: Django 3.2 support 7 | 8 | ## [0.12.3] - 2021-04-04 9 | - Fixed: Fixed bug catching malformed lat/lon in `GeoPt` field 10 | - Fixed: Removed redundant `STATIC_URL` in `GoogleMapsAddressWidget` 11 | - Changed: Updated css for map to be responsive in the admin 12 | - Changed: Moved CI to Github Actions 13 | 14 | ## [0.12.2] - 2020-08-05 15 | - Added: Django 3.1 support 16 | 17 | ## [0.12.1] - 2020-02-03 18 | - Fixed: Fixed install requirement for Django 3 19 | 20 | ## [0.12.0] - 2020-02-02 21 | - Changed: Updated for `from_db_value` `context` parameter deprecation 22 | - Added: Django 3.0 support 23 | - Removed: Python 2.0 support & django compatibility helpers 24 | 25 | ## [0.11.0] - 2019-04-19 26 | - Added: Django 2.2 support 27 | - Removed: Django 1.10 28 | 29 | ## [0.10.1] - 2018-02-20 30 | - Fixed: Install issue - `setup.py` now properly reads in requirements 31 | 32 | ## [0.10.0] - 2018-02-15 33 | - Added: Allow MapType to be customized 34 | 35 | ## [0.9.0] - 2018-01-19 36 | - Added: Google Places Autocomplete 37 | 38 | ## [0.8.0] - 2018-01-12 39 | - Added: Django 2.0 support 40 | - Removed: python 2.7 support 41 | 42 | ## [0.7.0] - 2017-04-10 43 | - Added: Django 1.11 support (new widget rendering) 44 | - Removed: Django <= 1.10 support 45 | 46 | ## [0.6.0] - 2016-11-28 47 | - Added: Django 1.10 support 48 | - Removed: Django <= 1.7 support 49 | 50 | ## [0.5.0] - 2016-08-12 51 | - Added: Allow using Google Maps API Key 52 | - Changed: jquery version to 3.1.0 53 | 54 | ## [0.4.0] - 2016-07-05 55 | - Added: Python 3 Compatibility 56 | - Fixed: Adjusted widgets imports to maintain backwards compatibility 57 | 58 | ## [0.3.0] - 2015-04-09 59 | - Added: Travis integration 60 | - Added: Django 1.7 & 1.8 support 61 | - Added: additional test coverage 62 | - Added: Map updates affecting geolocation now triggers change event on geolocation field 63 | - Changed: jquery version to 1.11.2 64 | 65 | ## [0.2.3] - 2013-10-08 66 | - Changed: Google Maps JS to always load over https 67 | 68 | ## [0.2.2] - 2012-12-13 69 | - Added: Django 1.4 support 70 | 71 | ## [0.2.1] - 2011-06-28 72 | - Added: `__eq__` method for GeoPt field to allow comparison 73 | 74 | ## [0.2.0] - 2011-06-27 75 | - Initial version 76 | 77 | 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Aaron Madison 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | 2 | include LICENSE 3 | include README.md 4 | include CHANGELOG.md 5 | include MANIFEST.in 6 | include requirements/*.txt 7 | 8 | recursive-include django_google_maps/static * 9 | recursive-include django_google_maps/templates * 10 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ``django-google-maps`` is a simple application that provides the basic 2 | hooks into google maps V3 api for use in Django models from Django 3 | version 1.11+. 4 | 5 | Starting with ``django-google-maps`` version (0.7.0), Django 1.11+ is 6 | required because Django changed their widget template rendering system. 7 | Version 0.8.0 supports Django 2.0+, and as such removes support for 8 | Python 2.7 9 | 10 | I’m using this to allow someone from the admin panels to type a freeform 11 | address, have the address geocoded on change and plotted on the map. If 12 | the location is not 100% correct, the user can drag the marker to the 13 | correct spot and the geo coordinates will update. 14 | 15 | Status 16 | ~~~~~~ 17 | 18 | |Build Status| 19 | 20 | USAGE: 21 | ------ 22 | 23 | - include the ``django_google_maps`` app in your ``settings.py`` 24 | 25 | - Add your Google Maps API Key in your ``settings.py`` as 26 | ``GOOGLE_MAPS_API_KEY`` 27 | 28 | - create a model that has both an address field and geolocation field 29 | 30 | .. code:: python 31 | 32 | from django.db import models 33 | from django_google_maps import fields as map_fields 34 | 35 | class Rental(models.Model): 36 | address = map_fields.AddressField(max_length=200) 37 | geolocation = map_fields.GeoLocationField(max_length=100) 38 | 39 | - in the ``admin.py`` include the following as a formfield_override 40 | 41 | .. code:: python 42 | 43 | from django.contrib import admin 44 | from django_google_maps import widgets as map_widgets 45 | from django_google_maps import fields as map_fields 46 | 47 | class RentalAdmin(admin.ModelAdmin): 48 | formfield_overrides = { 49 | map_fields.AddressField: {'widget': map_widgets.GoogleMapsAddressWidget}, 50 | } 51 | 52 | - To change the map type (``hybrid`` by default), you can add an html 53 | attribute on the ``AddressField`` widget. The list of allowed values 54 | is: ``hybrid``, ``roadmap``, ``satellite``, ``terrain`` 55 | 56 | .. code:: python 57 | 58 | from django.contrib import admin 59 | from django_google_maps import widgets as map_widgets 60 | from django_google_maps import fields as map_fields 61 | 62 | class RentalAdmin(admin.ModelAdmin): 63 | formfield_overrides = { 64 | map_fields.AddressField: { 65 | 'widget': map_widgets.GoogleMapsAddressWidget(attrs={'data-map-type': 'roadmap'})}, 66 | } 67 | 68 | - To change the autocomplete options, you can add an html attribute on 69 | the ``AddressField`` widget. See 70 | https://developers.google.com/maps/documentation/javascript/places-autocomplete#add_autocomplete 71 | for a list of available options 72 | 73 | .. code:: python 74 | 75 | import json from django.contrib import admin 76 | from django_google_maps import widgets as map_widgets 77 | from django_google_maps import fields as map_fields 78 | 79 | class RentalAdmin(admin.ModelAdmin): formfield_overrides = { 80 | map_fields.AddressField: { ‘widget’: 81 | map_widgets.GoogleMapsAddressWidget(attrs={ 82 | ‘data-autocomplete-options’: json.dumps({ ‘types’: [‘geocode’, 83 | ‘establishment’], ‘componentRestrictions’: { 84 | 'country': 'us' 85 | } 86 | }) 87 | }) 88 | }, 89 | } 90 | 91 | That should be all you need to get started. 92 | 93 | I also like to make the geolocation field readonly in the admin so a user 94 | (myself) doesn't accidentally change it to a nonsensical value. There is 95 | validation on the field so you can't enter an incorrect value, but you could 96 | enter something that is not even close to the address you intended. 97 | 98 | When you're displaying the address back to the user, just request the map 99 | using the geocoordinates that were saved in your model. Maybe sometime when 100 | I get around to it I'll see if I can create a method that will build that 101 | into the model. 102 | 103 | .. |Build Status| image:: https://travis-ci.org/madisona/django-google-maps.png 104 | :target: https://travis-ci.org/madisona/django-google-maps -------------------------------------------------------------------------------- /README_kr.rst: -------------------------------------------------------------------------------- 1 | ``django-google-maps``\ 는 Django 버전 1.11+에서 Django 모델 용 V3 API에 2 | 대한 기본 훅을 제공하는 간단한 애플리케이션입니다. 3 | 4 | ``django-google-maps`` 버전 (0.7.0)부터는 Django가 위젯 템플릿 렌더링 5 | 시스템을 변경했기 때문에 Django 1.11+가 필요합니다. 버전 0.8.0은 Django 6 | 2.0+를 지원하며 Python 2.7 지원을 제거합니다. 7 | 8 | 저는 관리자 패널의 누군가가 자유 형식 주소를 입력하고, 주소가 9 | 변경되고지도 상에 그려 지도록 허용하고 있습니다. 위치가 100 % 정확하지 10 | 않은 경우 사용자는 마커를 올바른 지점으로 드래그 할 수 있으며 지리적 11 | 좌표가 업데이트됩니다. 12 | 13 | 상태 14 | --- 15 | 16 | |Build Status| 17 | 18 | 용법: 19 | ----- 20 | 21 | -``settings.py``\ 에\ ``django_google_maps`` 앱을 포함 시키십시오. - 22 | Google Maps API 키를\ ``settings.py``\ 에 ’GOOGLE_MAPS_API_KEY\` (으)로 23 | 추가하십시오. - 주소 필드와 위치 정보 필드가 모두있는 모델을 만듭니다. 24 | 25 | .. code:: python 26 | 27 | from django.db import models 28 | from django_google_maps import fields as map_fields 29 | 30 | class Rental(models.Model): 31 | address = map_fields.AddressField(max_length=200) 32 | geolocation = map_fields.GeoLocationField(max_length=100) 33 | 34 | -``admin.py``\ 에 다음을 formfield_override로 포함하십시오. 35 | 36 | .. code:: python 37 | 38 | from django.contrib import admin 39 | from django_google_maps import widgets as map_widgets 40 | from django_google_maps import fields as map_fields 41 | 42 | class RentalAdmin(admin.ModelAdmin): 43 | formfield_overrides = { 44 | map_fields.AddressField: {'widget': map_widgets.GoogleMapsAddressWidget}, 45 | } 46 | 47 | -지도 유형 (기본적으로\ ``hybrid ')을 변경하려면,``\ AddressField\` 48 | 위젯에 html 속성을 추가 할 수 있습니다. 허용되는 값 목록은 ‘하이브리드’, 49 | ‘로드맵’, ‘위성’, ’지형’입니다. 50 | 51 | .. code:: python 52 | 53 | from django.contrib import admin 54 | from django_google_maps import widgets as map_widgets 55 | from django_google_maps import fields as map_fields 56 | 57 | class RentalAdmin(admin.ModelAdmin): 58 | formfield_overrides = { 59 | map_fields.AddressField: { 60 | 'widget': map_widgets.GoogleMapsAddressWidget(attrs={'data-map-type': 'roadmap'})}, 61 | } 62 | 63 | 그게 당신이 시작하는 데 필요한 모든 것이어야합니다. 64 | 65 | 나는 또한 관리자가 읽기 전용으로 geolocation 필드를 만들어 사용자 66 | (우연히)가 무의미한 값으로 변경하지 않도록하고 싶습니다. 입력란에 유효성 67 | 검사가 있으므로 잘못된 값을 입력 할 수는 없지만 의도 한 주소와 거의 68 | 일치하지 않는 내용을 입력 할 수 있습니다. 69 | 70 | 사용자에게 다시 주소를 표시 할 때 모델에 저장된 지오 코디네이터를 71 | 사용하여지도를 요청하십시오. 어쩌면 언젠가는 내가 그 모델을 구축 할 72 | 방법을 만들 수 있는지 살펴볼 것입니다. 73 | 74 | .. |Build Status| image:: https://travis-ci.org/madisona/django-google-maps.png 75 | :target: https://travis-ci.org/madisona/django-google-maps -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madisona/django-google-maps/00675c85176dcd7c5e4524ad4e8102832a1c5db5/__init__.py -------------------------------------------------------------------------------- /django_google_maps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madisona/django-google-maps/00675c85176dcd7c5e4524ad4e8102832a1c5db5/django_google_maps/__init__.py -------------------------------------------------------------------------------- /django_google_maps/fields.py: -------------------------------------------------------------------------------- 1 | # The core of this module was adapted from Google AppEngine's 2 | # GeoPt field, so I've included their copyright and license. 3 | # 4 | # Copyright 2007 Google Inc. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | from django.core import exceptions 20 | from django.db import models 21 | from django.utils.encoding import force_str 22 | 23 | __all__ = ('AddressField', 'GeoLocationField') 24 | 25 | 26 | def typename(obj): 27 | """Returns the type of obj as a string. More descriptive and specific than 28 | type(obj), and safe for any object, unlike __class__.""" 29 | if hasattr(obj, '__class__'): 30 | return getattr(obj, '__class__').__name__ 31 | else: 32 | return type(obj).__name__ 33 | 34 | 35 | class GeoPt(object): 36 | """A geographical point.""" 37 | 38 | lat = None 39 | lon = None 40 | 41 | def __init__(self, lat, lon=None): 42 | """ 43 | If the model field has 'blank=True' or 'null=True' then 44 | we can't always expect the GeoPt to be instantiated with 45 | a valid value. In this case we'll let GeoPt be instantiated 46 | as an empty item, and the string representation should be 47 | an empty string instead of 'lat,lon'. 48 | """ 49 | if not lat: 50 | return 51 | 52 | if lon is None: 53 | lat, lon = self._split_geo_point(lat) 54 | self.lat = self._validate_geo_range(lat, 90) 55 | self.lon = self._validate_geo_range(lon, 180) 56 | 57 | def __str__(self): 58 | if self.lat is not None and self.lon is not None: 59 | return "%s,%s" % (self.lat, self.lon) 60 | return '' 61 | 62 | def __eq__(self, other): 63 | if isinstance(other, GeoPt): 64 | return bool(self.lat == other.lat and self.lon == other.lon) 65 | 66 | def __len__(self): 67 | return len(force_str(self)) 68 | 69 | def _split_geo_point(self, geo_point): 70 | """splits the geo point into lat and lon""" 71 | try: 72 | lat, lon = geo_point.split(',') 73 | return lat, lon 74 | except (AttributeError, ValueError): 75 | m = 'Expected a "lat,long" formatted string; received %s (a %s).' 76 | raise exceptions.ValidationError(m % (geo_point, 77 | typename(geo_point))) 78 | 79 | def _validate_geo_range(self, geo_part, range_val): 80 | try: 81 | geo_part = float(geo_part) 82 | if abs(geo_part) > range_val: 83 | m = 'Must be between -%s and %s; received %s' 84 | raise exceptions.ValidationError(m % (range_val, range_val, 85 | geo_part)) 86 | except (TypeError, ValueError): 87 | raise exceptions.ValidationError( 88 | 'Expected float, received %s (a %s).' % (geo_part, 89 | typename(geo_part))) 90 | return geo_part 91 | 92 | 93 | class AddressField(models.CharField): 94 | pass 95 | 96 | 97 | class GeoLocationField(models.CharField): 98 | """ 99 | A geographical point, specified by floating-point latitude and longitude 100 | coordinates. Often used to integrate with mapping sites like Google Maps. 101 | May also be used as ICBM coordinates. 102 | 103 | This is the georss:point element. In XML output, the coordinates are 104 | provided as the lat and lon attributes. See: http://georss.org/ 105 | 106 | Serializes to ','. Raises BadValueError if it's passed an invalid 107 | serialized string, or if lat and lon are not valid floating points in the 108 | ranges [-90, 90] and [-180, 180], respectively. 109 | """ 110 | description = "A geographical point, specified by floating-point latitude and longitude coordinates." 111 | 112 | def __init__(self, *args, **kwargs): 113 | kwargs['max_length'] = 100 114 | super(GeoLocationField, self).__init__(*args, **kwargs) 115 | 116 | def from_db_value(self, value, *args, **kwargs): 117 | return self.to_python(value) 118 | 119 | def to_python(self, value): 120 | if isinstance(value, GeoPt): 121 | return value 122 | return GeoPt(value) 123 | 124 | def get_prep_value(self, value): 125 | """prepare the value for database query""" 126 | if value is None: 127 | return None 128 | return force_str(self.to_python(value)) 129 | 130 | def value_to_string(self, obj): 131 | value = self.value_from_object(obj) 132 | return self.get_prep_value(value) 133 | -------------------------------------------------------------------------------- /django_google_maps/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madisona/django-google-maps/00675c85176dcd7c5e4524ad4e8102832a1c5db5/django_google_maps/models.py -------------------------------------------------------------------------------- /django_google_maps/static/django_google_maps/css/google-maps-admin.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 848px) { 2 | .map_canvas_wrapper {margin-left: 170px;} 3 | .main.shifted .map_canvas_wrapper {margin-left: 0;} 4 | } 5 | @media (min-width: 1118px) { 6 | .main.shifted .map_canvas_wrapper {margin-left: 170px;} 7 | } 8 | 9 | #id_address {width: 40em;} 10 | .map_canvas_wrapper {width: 100%;} 11 | #map_canvas {width: 100%; height: 40em;} 12 | -------------------------------------------------------------------------------- /django_google_maps/static/django_google_maps/js/google-maps-admin.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Integration for Google Maps in the django admin. 4 | 5 | How it works: 6 | 7 | You have an address field on the page. 8 | Enter an address and an on change event will update the map 9 | with the address. A marker will be placed at the address. 10 | If the user needs to move the marker, they can and the geolocation 11 | field will be updated. 12 | 13 | Only one marker will remain present on the map at a time. 14 | 15 | This script expects: 16 | 17 | 18 | 19 | 20 | 21 | 22 | */ 23 | 24 | function googleMapAdmin() { 25 | 26 | var autocomplete; 27 | var geocoder = new google.maps.Geocoder(); 28 | var map; 29 | var marker; 30 | 31 | var geolocationId = 'id_geolocation'; 32 | var addressId = 'id_address'; 33 | 34 | var self = { 35 | initialize: function() { 36 | var lat = 0; 37 | var lng = 0; 38 | var zoom = 2; 39 | // set up initial map to be world view. also, add change 40 | // event so changing address will update the map 41 | var existinglocation = self.getExistingLocation(); 42 | 43 | if (existinglocation) { 44 | lat = existinglocation[0]; 45 | lng = existinglocation[1]; 46 | zoom = 18; 47 | } 48 | 49 | var latlng = new google.maps.LatLng(lat,lng); 50 | var myOptions = { 51 | zoom: zoom, 52 | center: latlng, 53 | mapTypeId: self.getMapType() 54 | }; 55 | map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); 56 | if (existinglocation) { 57 | self.setMarker(latlng); 58 | } 59 | 60 | autocomplete = new google.maps.places.Autocomplete( 61 | /** @type {!HTMLInputElement} */(document.getElementById(addressId)), 62 | self.getAutoCompleteOptions()); 63 | 64 | // this only triggers on enter, or if a suggested location is chosen 65 | // todo: if a user doesn't choose a suggestion and presses tab, the map doesn't update 66 | autocomplete.addListener("place_changed", self.codeAddress); 67 | 68 | // don't make enter submit the form, let it just trigger the place_changed event 69 | // which triggers the map update & geocode 70 | $("#" + addressId).keydown(function (e) { 71 | if (e.keyCode == 13) { // enter key 72 | e.preventDefault(); 73 | return false; 74 | } 75 | }); 76 | }, 77 | 78 | getMapType : function() { 79 | // https://developers.google.com/maps/documentation/javascript/maptypes 80 | var geolocation = document.getElementById(addressId); 81 | var allowedType = ['roadmap', 'satellite', 'hybrid', 'terrain']; 82 | var mapType = geolocation.getAttribute('data-map-type'); 83 | 84 | if (mapType && -1 !== allowedType.indexOf(mapType)) { 85 | return mapType; 86 | } 87 | 88 | return google.maps.MapTypeId.HYBRID; 89 | }, 90 | 91 | getAutoCompleteOptions : function() { 92 | var geolocation = document.getElementById(addressId); 93 | var autocompleteOptions = geolocation.getAttribute('data-autocomplete-options'); 94 | 95 | if (!autocompleteOptions) { 96 | return { 97 | types: ['geocode'] 98 | }; 99 | } 100 | 101 | return JSON.parse(autocompleteOptions); 102 | }, 103 | 104 | getExistingLocation: function() { 105 | var geolocation = document.getElementById(geolocationId).value; 106 | if (geolocation) { 107 | return geolocation.split(','); 108 | } 109 | }, 110 | 111 | codeAddress: function() { 112 | var place = autocomplete.getPlace(); 113 | 114 | if(place.geometry !== undefined) { 115 | self.updateWithCoordinates(place.geometry.location); 116 | } 117 | else { 118 | geocoder.geocode({'address': place.name}, function(results, status) { 119 | if (status == google.maps.GeocoderStatus.OK) { 120 | var latlng = results[0].geometry.location; 121 | self.updateWithCoordinates(latlng); 122 | } else { 123 | alert("Geocode was not successful for the following reason: " + status); 124 | } 125 | }); 126 | } 127 | }, 128 | 129 | updateWithCoordinates: function(latlng) { 130 | map.setCenter(latlng); 131 | map.setZoom(18); 132 | self.setMarker(latlng); 133 | self.updateGeolocation(latlng); 134 | }, 135 | 136 | setMarker: function(latlng) { 137 | if (marker) { 138 | self.updateMarker(latlng); 139 | } else { 140 | self.addMarker({'latlng': latlng, 'draggable': true}); 141 | } 142 | }, 143 | 144 | addMarker: function(Options) { 145 | marker = new google.maps.Marker({ 146 | map: map, 147 | position: Options.latlng 148 | }); 149 | 150 | var draggable = Options.draggable || false; 151 | if (draggable) { 152 | self.addMarkerDrag(marker); 153 | } 154 | }, 155 | 156 | addMarkerDrag: function() { 157 | marker.setDraggable(true); 158 | google.maps.event.addListener(marker, 'dragend', function(new_location) { 159 | self.updateGeolocation(new_location.latLng); 160 | }); 161 | }, 162 | 163 | updateMarker: function(latlng) { 164 | marker.setPosition(latlng); 165 | }, 166 | 167 | updateGeolocation: function(latlng) { 168 | document.getElementById(geolocationId).value = latlng.lat() + "," + latlng.lng(); 169 | $("#" + geolocationId).trigger('change'); 170 | } 171 | }; 172 | 173 | return self; 174 | } 175 | 176 | $(document).ready(function() { 177 | var googlemap = googleMapAdmin(); 178 | googlemap.initialize(); 179 | }); 180 | -------------------------------------------------------------------------------- /django_google_maps/templates/django_google_maps/widgets/map_widget.html: -------------------------------------------------------------------------------- 1 | {% include "django/forms/widgets/text.html" %} 2 |
-------------------------------------------------------------------------------- /django_google_maps/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madisona/django-google-maps/00675c85176dcd7c5e4524ad4e8102832a1c5db5/django_google_maps/tests/__init__.py -------------------------------------------------------------------------------- /django_google_maps/tests/test_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madisona/django-google-maps/00675c85176dcd7c5e4524ad4e8102832a1c5db5/django_google_maps/tests/test_app/__init__.py -------------------------------------------------------------------------------- /django_google_maps/tests/test_app/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django_google_maps.fields import AddressField, GeoLocationField 3 | 4 | 5 | class Person(models.Model): 6 | address = AddressField(max_length=100) 7 | geolocation = GeoLocationField(blank=True) 8 | -------------------------------------------------------------------------------- /django_google_maps/tests/test_geolocation_field.py: -------------------------------------------------------------------------------- 1 | from django import test 2 | from django_google_maps.tests.test_app import models 3 | from django_google_maps.fields import GeoPt 4 | 5 | 6 | class GeoLocationFieldTests(test.TestCase): 7 | def test_getting_lat_lon_from_model_given_string(self): 8 | sut_create = models.Person.objects.create(geolocation='45,90') 9 | sut = models.Person.objects.get(pk=sut_create.pk) 10 | self.assertEqual(45, sut.geolocation.lat) 11 | self.assertEqual(90, sut.geolocation.lon) 12 | 13 | def test_getting_lat_lon_from_model_given_pt(self): 14 | sut_create = models.Person.objects.create(geolocation=GeoPt('45,90')) 15 | sut = models.Person.objects.get(pk=sut_create.pk) 16 | self.assertEqual(45, sut.geolocation.lat) 17 | self.assertEqual(90, sut.geolocation.lon) 18 | 19 | def test_getting_lat_lon_from_model_in_db_given_string(self): 20 | sut_create = models.Person.objects.create(geolocation='45,90') 21 | sut = models.Person.objects.get(pk=sut_create.pk) 22 | self.assertEqual(45, sut.geolocation.lat) 23 | self.assertEqual(90, sut.geolocation.lon) 24 | 25 | def test_exact_match_query(self): 26 | sut = models.Person.objects.create(geolocation='45,90') 27 | result = models.Person.objects.get(geolocation__exact=GeoPt('45,90')) 28 | self.assertEqual(result, sut) 29 | 30 | def test_in_match_query(self): 31 | sut = models.Person.objects.create(geolocation='45,90') 32 | result = models.Person.objects.get(geolocation__in=[GeoPt('45,90')]) 33 | self.assertEqual(result, sut) 34 | 35 | def test_value_to_string_with_point(self): 36 | sut = models.Person.objects.create(geolocation=GeoPt('45,90')) 37 | field = models.Person._meta.fields[-1] 38 | self.assertEqual('45.0,90.0', field.value_to_string(sut)) 39 | 40 | def test_value_to_string_with_string(self): 41 | sut = models.Person.objects.create(geolocation='45,90') 42 | field = models.Person._meta.fields[-1] 43 | self.assertEqual('45.0,90.0', field.value_to_string(sut)) 44 | 45 | def test_get_prep_value_returns_none_when_none(self): 46 | field = models.Person._meta.fields[-1] 47 | result = field.get_prep_value(None) 48 | self.assertEqual(None, result) 49 | -------------------------------------------------------------------------------- /django_google_maps/tests/test_geopt_field.py: -------------------------------------------------------------------------------- 1 | from django import test 2 | from django.core import exceptions 3 | from django.utils.encoding import force_str 4 | 5 | from django_google_maps import fields 6 | 7 | 8 | class GeoPtFieldTests(test.TestCase): 9 | def test_sets_lat_lon_on_initialization(self): 10 | geo_pt = fields.GeoPt("15.001,32.001") 11 | self.assertEqual(15.001, geo_pt.lat) 12 | self.assertEqual(32.001, geo_pt.lon) 13 | 14 | def test_uses_lat_comma_lon_as_unicode_representation(self): 15 | lat_lon_string = "15.001,32.001" 16 | geo_pt = fields.GeoPt(lat_lon_string) 17 | self.assertEqual(lat_lon_string, force_str(geo_pt)) 18 | 19 | def test_two_GeoPts_with_same_lat_lon_should_be_equal(self): 20 | geo_pt_1 = fields.GeoPt("15.001,32.001") 21 | geo_pt_2 = fields.GeoPt("15.001,32.001") 22 | self.assertEqual(geo_pt_1, geo_pt_2) 23 | 24 | def test_two_GeoPts_with_different_lat_should_not_be_equal(self): 25 | geo_pt_1 = fields.GeoPt("15.001,32.001") 26 | geo_pt_2 = fields.GeoPt("20.001,32.001") 27 | self.assertNotEqual(geo_pt_1, geo_pt_2) 28 | 29 | def test_two_GeoPts_with_different_lon_should_not_be_equal(self): 30 | geo_pt_1 = fields.GeoPt("15.001,32.001") 31 | geo_pt_2 = fields.GeoPt("15.001,62.001") 32 | self.assertNotEqual(geo_pt_1, geo_pt_2) 33 | 34 | def test_is_not_equal_when_comparison_is_not_GeoPt_object(self): 35 | geo_pt_1 = fields.GeoPt("15.001,32.001") 36 | geo_pt_2 = "15.001,32.001" 37 | self.assertNotEqual(geo_pt_1, geo_pt_2) 38 | 39 | def test_allows_GeoPt_instantiated_with_empty_string(self): 40 | geo_pt = fields.GeoPt('') 41 | self.assertEqual(None, geo_pt.lat) 42 | self.assertEqual(None, geo_pt.lon) 43 | 44 | def test_uses_empty_string_as_unicode_representation_for_empty_GeoPt(self): 45 | geo_pt = fields.GeoPt('') 46 | self.assertEqual('', force_str(geo_pt)) 47 | 48 | def test_splits_geo_point_on_comma(self): 49 | pt = fields.GeoPt("15.001,32.001") 50 | self.assertEqual('15.001', str(pt.lat)) 51 | self.assertEqual('32.001', str(pt.lon)) 52 | 53 | def test_raises_error_when_attribute_error_on_split(self): 54 | class Fake(object): 55 | pass 56 | 57 | with self.assertRaises(exceptions.ValidationError): 58 | fields.GeoPt(Fake) 59 | 60 | def test_raises_error_when_type_error_on_split(self): 61 | with self.assertRaises(exceptions.ValidationError): 62 | x, y = fields.GeoPt("x,x") 63 | 64 | def test_returns_float_value_when_valid_value(self): 65 | geo_pt = fields.GeoPt('45.005,180') 66 | self.assertEqual(45.005, geo_pt.lat) 67 | 68 | def test_raises_exception_when_value_is_out_of_upper_range(self): 69 | with self.assertRaises(exceptions.ValidationError): 70 | fields.GeoPt('180,180') 71 | 72 | def test_raises_exception_when_value_is_out_of_lower_range(self): 73 | with self.assertRaises(exceptions.ValidationError): 74 | fields.GeoPt('-180,180') 75 | 76 | def test_len_returns_len_of_unicode_value(self): 77 | geo_pt = fields.GeoPt("84,12") 78 | self.assertEqual(9, len(geo_pt)) 79 | 80 | def test_raises_exception_not_enough_values_to_unpack(self): 81 | with self.assertRaises(exceptions.ValidationError): 82 | fields.GeoPt('22') 83 | 84 | def test_raises_exception_too_many_values_to_unpack(self): 85 | with self.assertRaises(exceptions.ValidationError): 86 | fields.GeoPt('22,50,90') 87 | -------------------------------------------------------------------------------- /django_google_maps/tests/test_typename.py: -------------------------------------------------------------------------------- 1 | from django import test 2 | from django_google_maps.fields import typename 3 | 4 | 5 | class TypeNameTests(test.TestCase): 6 | def test_simple_type_returns_type_name_as_string(self): 7 | self.assertEqual('str', typename("x")) 8 | 9 | def test_class_object(self): 10 | class X: 11 | pass 12 | 13 | self.assertEqual('type', typename(X)) 14 | -------------------------------------------------------------------------------- /django_google_maps/tests/test_widget.py: -------------------------------------------------------------------------------- 1 | from django import test 2 | from django.conf import settings 3 | from django_google_maps.widgets import GoogleMapsAddressWidget 4 | 5 | 6 | class WidgetTests(test.TestCase): 7 | def test_render_returns_xxxxxxx(self): 8 | widget = GoogleMapsAddressWidget() 9 | results = widget.render('name', 'value', attrs={'a1': 1, 'a2': 2}) 10 | expected = '' 11 | expected += '
' 12 | expected += '
' 13 | self.assertHTMLEqual(expected, results) 14 | 15 | def test_render_returns_blank_for_value_when_none(self): 16 | widget = GoogleMapsAddressWidget() 17 | results = widget.render('name', None, attrs={'a1': 1, 'a2': 2}) 18 | expected = '' 19 | expected += '
' 20 | expected += '
' 21 | self.assertHTMLEqual(expected, results) 22 | 23 | def test_maps_js_uses_api_key(self): 24 | widget = GoogleMapsAddressWidget() 25 | google_maps_js = "https://maps.google.com/maps/api/js?key={}&libraries=places".format( 26 | settings.GOOGLE_MAPS_API_KEY) 27 | self.assertEqual(google_maps_js, widget.Media().js[1]) 28 | -------------------------------------------------------------------------------- /django_google_maps/widgets.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.forms import widgets 3 | 4 | 5 | class GoogleMapsAddressWidget(widgets.TextInput): 6 | """a widget that will place a google map right after the #id_address field""" 7 | template_name = "django_google_maps/widgets/map_widget.html" 8 | 9 | class Media: 10 | css = { 11 | 'all': ('django_google_maps/css/google-maps-admin.css', ) 12 | } 13 | js = ( 14 | 'https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js', 15 | 'https://maps.google.com/maps/api/js?key={}&libraries=places'.format( 16 | settings.GOOGLE_MAPS_API_KEY), 17 | 'django_google_maps/js/google-maps-admin.js', 18 | ) 19 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | 12 | 13 | # #!/usr/bin/env python 14 | # from django.core.management import execute_manager 15 | # import imp 16 | # try: 17 | # imp.find_module('settings') # Assumed to be in the same directory. 18 | # except ImportError: 19 | # import sys 20 | # sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) 21 | # sys.exit(1) 22 | 23 | # import settings 24 | 25 | # if __name__ == "__main__": 26 | # execute_manager(settings) 27 | -------------------------------------------------------------------------------- /requirements/requirements.txt: -------------------------------------------------------------------------------- 1 | Django>=2.2 2 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | flake8 2 | coverage 3 | mock 4 | -------------------------------------------------------------------------------- /sample/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madisona/django-google-maps/00675c85176dcd7c5e4524ad4e8102832a1c5db5/sample/__init__.py -------------------------------------------------------------------------------- /sample/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.forms.widgets import TextInput 3 | 4 | from django_google_maps.widgets import GoogleMapsAddressWidget 5 | from django_google_maps.fields import AddressField, GeoLocationField 6 | 7 | from sample import models 8 | 9 | 10 | class SampleModelAdmin(admin.ModelAdmin): 11 | formfield_overrides = { 12 | AddressField: { 13 | 'widget': GoogleMapsAddressWidget 14 | }, 15 | GeoLocationField: { 16 | 'widget': TextInput(attrs={ 17 | 'readonly': 'readonly' 18 | }) 19 | }, 20 | } 21 | 22 | 23 | admin.site.register(models.SampleModel, SampleModelAdmin) 24 | -------------------------------------------------------------------------------- /sample/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from sample.models import SampleModel 3 | from django_google_maps.widgets import GoogleMapsAddressWidget 4 | 5 | 6 | class SampleForm(forms.ModelForm): 7 | 8 | class Meta(object): 9 | model = SampleModel 10 | fields = ['address', 'geolocation'] 11 | widgets = { 12 | "address": GoogleMapsAddressWidget, 13 | } 14 | -------------------------------------------------------------------------------- /sample/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django_google_maps.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='SampleModel', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 17 | ('address', django_google_maps.fields.AddressField(max_length=100)), 18 | ('geolocation', django_google_maps.fields.GeoLocationField(max_length=100, blank=True)), 19 | ], 20 | options={}, 21 | bases=(models.Model, ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /sample/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madisona/django-google-maps/00675c85176dcd7c5e4524ad4e8102832a1c5db5/sample/migrations/__init__.py -------------------------------------------------------------------------------- /sample/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from django_google_maps.fields import AddressField, GeoLocationField 4 | 5 | 6 | class SampleModel(models.Model): 7 | address = AddressField(max_length=100) 8 | geolocation = GeoLocationField(blank=True) 9 | 10 | def __str__(self): 11 | return self.address 12 | -------------------------------------------------------------------------------- /sample/templates/sample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{form.media}} 4 | 5 | {{form.as_p}} 6 | 7 | -------------------------------------------------------------------------------- /sample/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | 13 | def test_basic_addition(self): 14 | """ 15 | Tests that 1 + 1 always equals 2. 16 | """ 17 | self.assertEqual(1 + 1, 2) 18 | -------------------------------------------------------------------------------- /sample/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import FormView 2 | 3 | from sample.forms import SampleForm 4 | 5 | 6 | class SampleFormView(FormView): 7 | form_class = SampleForm 8 | template_name = "sample/index.html" 9 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for google_maps project. 2 | 3 | DEBUG = True 4 | 5 | ADMINS = ( 6 | # ('Your Name', 'your_email@example.com'), 7 | ) 8 | 9 | MANAGERS = ADMINS 10 | 11 | DATABASES = { 12 | 'default': { 13 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 14 | 'NAME': 'sample_db', # Or path to database file if using sqlite3. 15 | 'USER': '', # Not used with sqlite3. 16 | 'PASSWORD': '', # Not used with sqlite3. 17 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 18 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 19 | } 20 | } 21 | 22 | # Local time zone for this installation. Choices can be found here: 23 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 24 | # although not all choices may be available on all operating systems. 25 | # On Unix systems, a value of None will cause Django to use the same 26 | # timezone as the operating system. 27 | # If running in a Windows environment this must be set to the same as your 28 | # system time zone. 29 | TIME_ZONE = 'America/Chicago' 30 | 31 | # Language code for this installation. All choices can be found here: 32 | # http://www.i18nguy.com/unicode/language-identifiers.html 33 | LANGUAGE_CODE = 'en-us' 34 | 35 | SITE_ID = 1 36 | 37 | # If you set this to False, Django will make some optimizations so as not 38 | # to load the internationalization machinery. 39 | USE_I18N = True 40 | 41 | # If you set this to False, Django will not format dates, numbers and 42 | # calendars according to the current locale 43 | USE_L10N = True 44 | 45 | # Absolute filesystem path to the directory that will hold user-uploaded files. 46 | # Example: "/home/media/media.lawrence.com/media/" 47 | MEDIA_ROOT = '' 48 | 49 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 50 | # trailing slash. 51 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 52 | MEDIA_URL = '' 53 | 54 | # Absolute path to the directory static files should be collected to. 55 | # Don't put anything in this directory yourself; store your static files 56 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 57 | # Example: "/home/media/media.lawrence.com/static/" 58 | STATIC_ROOT = '' 59 | 60 | # URL prefix for static files. 61 | # Example: "http://media.lawrence.com/static/" 62 | STATIC_URL = '/static/' 63 | 64 | # URL prefix for admin static files -- CSS, JavaScript and images. 65 | # Make sure to use a trailing slash. 66 | # Examples: "http://foo.com/static/admin/", "/static/admin/". 67 | ADMIN_MEDIA_PREFIX = '/static/admin/' 68 | 69 | # Additional locations of static files 70 | STATICFILES_DIRS = ( 71 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 72 | # Always use forward slashes, even on Windows. 73 | # Don't forget to use absolute paths, not relative paths. 74 | ) 75 | 76 | # List of finder classes that know how to find static files in 77 | # various locations. 78 | STATICFILES_FINDERS = ( 79 | 'django.contrib.staticfiles.finders.FileSystemFinder', 80 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 81 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 82 | ) 83 | 84 | TEMPLATES = [ 85 | { 86 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 87 | 'DIRS': [], 88 | 'APP_DIRS': True, 89 | 'OPTIONS': { 90 | 'context_processors': [ 91 | 'django.contrib.auth.context_processors.auth', 92 | 'django.contrib.messages.context_processors.messages', 93 | ] 94 | }, 95 | }, 96 | ] 97 | 98 | # Make this unique, and don't share it with anybody. 99 | SECRET_KEY = '%hi+eb@u)t)o_qk^#y&eje%*65ghba=1xulgk$_zfx5#&b3$g4' 100 | 101 | 102 | MIDDLEWARE = ( 103 | 'django.middleware.common.CommonMiddleware', 104 | 'django.contrib.sessions.middleware.SessionMiddleware', 105 | 'django.middleware.csrf.CsrfViewMiddleware', 106 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 107 | 'django.contrib.messages.middleware.MessageMiddleware', 108 | ) 109 | 110 | ROOT_URLCONF = 'urls' 111 | 112 | 113 | INSTALLED_APPS = ( 114 | 'django.contrib.auth', 115 | 'django.contrib.contenttypes', 116 | 'django.contrib.sessions', 117 | 'django.contrib.sites', 118 | 'django.contrib.messages', 119 | 'django.contrib.staticfiles', 120 | # Uncomment the next line to enable the admin: 121 | 'django.contrib.admin', 122 | # Uncomment the next line to enable admin documentation: 123 | # 'django.contrib.admindocs', 124 | 'django_google_maps', 125 | 'sample', 126 | 'django_google_maps.tests.test_app' 127 | ) 128 | 129 | # A sample logging configuration. The only tangible logging 130 | # performed by this configuration is to send an email to 131 | # the site admins on every HTTP 500 error. 132 | # See http://docs.djangoproject.com/en/dev/topics/logging for 133 | # more details on how to customize your logging configuration. 134 | LOGGING = { 135 | 'version': 1, 136 | 'disable_existing_loggers': False, 137 | 'handlers': { 138 | 'mail_admins': { 139 | 'level': 'ERROR', 140 | 'class': 'django.utils.log.AdminEmailHandler' 141 | } 142 | }, 143 | 'loggers': { 144 | 'django.request': { 145 | 'handlers': ['mail_admins'], 146 | 'level': 'ERROR', 147 | 'propagate': True, 148 | }, 149 | } 150 | } 151 | 152 | 153 | GOOGLE_MAPS_API_KEY = 'SAMPLE_KEY' 154 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from setuptools import setup 4 | 5 | README = os.path.join(os.path.dirname(__file__), 'README.rst') 6 | LONG_DESCRIPTION = open(README, 'r').read() 7 | CLASSIFIERS = [ 8 | "Development Status :: 4 - Beta", 9 | "Environment :: Web Environment", 10 | "Framework :: Django", 11 | "Framework :: Django", 12 | "Framework :: Django :: 2.2", 13 | "Framework :: Django :: 3.2", 14 | "Framework :: Django :: 4.0", 15 | "Intended Audience :: Developers", 16 | "License :: OSI Approved :: BSD License", 17 | "Operating System :: OS Independent", 18 | "Programming Language :: Python", 19 | "Programming Language :: Python :: 3.7", 20 | "Programming Language :: Python :: 3.8", 21 | "Programming Language :: Python :: 3.9", 22 | "Programming Language :: Python :: 3.10", 23 | "Topic :: Software Development", 24 | "Topic :: Software Development :: Libraries :: Application Frameworks", 25 | ] 26 | 27 | setup( 28 | name="django-google-maps", 29 | version='0.13.0', 30 | author="Aaron Madison", 31 | author_email="aaron.l.madison@gmail.com", 32 | description="Plugs google maps V3 api into Django admin.", 33 | long_description=LONG_DESCRIPTION, 34 | url="https://github.com/madisona/django-google-maps", 35 | packages=("django_google_maps",), 36 | include_package_data=True, 37 | install_requires=open('requirements/requirements.txt').read().splitlines(), 38 | tests_require=open('requirements/test.txt').read().splitlines(), 39 | classifiers=CLASSIFIERS, 40 | zip_safe=False, 41 | ) 42 | -------------------------------------------------------------------------------- /urls.py: -------------------------------------------------------------------------------- 1 | 2 | import django 3 | from django.contrib import admin 4 | admin.autodiscover() 5 | 6 | 7 | if django.get_version() >= '2.0.0': 8 | from django.urls import re_path as url 9 | else: 10 | from django.conf.urls import url 11 | from sample.views import SampleFormView 12 | 13 | urlpatterns = [ 14 | url(r'^admin/', admin.site.urls), 15 | url(r'^$', SampleFormView.as_view()), 16 | ] 17 | --------------------------------------------------------------------------------