Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Request for comments #2

Open
wants to merge 18 commits into from

2 participants

Gianluca Pacchiella Mikhail Korobov
Gianluca Pacchiella

In this branch I tried to implement some ideas

Media files

Previously the javascript files was included directly in the easy_maps/map.html template but when more than one {% easy_map %} was used, multiple instances of Google maps javascript could create problems (chrome console complains about this).

In @1f0a767 I used the Media class for the widget used in the rendering of the address field.

Settings

Added (@8f71c84 and @bf15559 to be squashed together) a settings.py containing the default to be used when a new instance is created (also used to center the map). Together with the changes in @39645fd is possible to avoid the saving of an instance with an empty address.

Interactivity

I don't like that for see where the marker will be placed I need to save the instance and come back, so I added some javascript functionality: first of all the update button that queries the geolocalization URL (a custom view using JSON that responds with the data used to full the form field)

em1

This is introduced in @5ca4a70 where a geo/ URL is added to the Admin url patterns.

Also I would like to have the coordinates updated when I move the marker on the map (see @d1a0d5a).

Inlines

An admin class to be used with inlines is added with the same functionalities descripted above, this has required some modification on how functions and HTML element classes are named.

There is some example models added in @d1a0d5a that use it.

Multiple markers

The easy_maps tag is improved so to accept also a list of Address instances that will be placed in the map (see @39645fd).

Change list

Since the primary data of this application is geographical data I would like to have a change list page in the admin that contains a map at a zoom level such that all the point are immediately visualized. This (@1755f7d) is a very dirty hack and in future I'd love to implement some interaction between the normal Django table entries and the markers.

Conclusion

This is not code I'm proud of, it's a little messy and should be reorganized but to avoid useless work I need to know what do you think about these ideas.

Mikhail Korobov
Owner

Hi Gianluca,

Thanks for working on this! I'll comment on features now.

Media files

Currently, it is possible to use a custom template with nulled api_js block, e.g.:

# my_map.html
{% extends "easy_maps/map.html" %}
{% block api_js %}{% endblock %}

# user template
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>

{% easy_map ... using "my_map.html" %}
{% easy_map ... using "my_map.html" %}

This doesn't require using custom widget for a form field, and this also doesn't require using a form.
1f0a767 brokes backwards-compatibility by requiring user to include <script> tag manually or using a form with AddressWithMapWidget.

I don't want django-easy-maps to be tied to forms, and I really like that it is possible to just drop {% easy_map "<My address>" 300 300 %} and get a map (e.g. at a "Contacts" page).

Currently, the inlined javascript may become an issue if multiple instances of the same map are displayed (because the template is parametrized by map.pk), but this is rarely an issue (and this limitation still apply in your branch).

But using Media in admin widget is a good idea. Maybe use an another template while rendering widgets, the one which assumes the external javascript is loaded? It is also better to put easy_maps javascript files to static/easy_maps/js or static/easy_maps, not to static/js because the latter may clash with user files.

Settings

+1 for defaults in settings, this may be useful.

Interactivity

This is a really useful feature, but I think it is better to move work to the client side. Shouldn't we use javascript geocoder instead of geopy/json api in this case ( see https://developers.google.com/maps/documentation/geocoding/#ReverseGeocoding )? I'd prefer the interactivity to be implemented purely in javascript.

Inlines

+1

Multiple markers

I like the idea. Currently easy_maps tag accepts either string or Address instance; is it possible to have multiple markers with just strings or form fields? What should syntax look like?

Change list

I like the idea.


By the way, https://github.com/gipi/django-easy-maps/blob/list-addresses/easy_maps_tests/test_app/models.py is not how django-easy-maps was intended to be used :) The idea was to write

class Shop(models.Model):
    address = models.CharField(max_length=255)
    brand = models.ForeignKey(Brand)

(that's what existing sites probably have) and then use {% easy_map shop.address 300 300 %} tag to render the map for shop's address; Address model is more like a geocoder cache with some fine-tuning options.

Gianluca Pacchiella

Thanks for your reply.

Address model is more like a geocoder cache with some fine-tuning options.

this explains a lot to me and I can say that my intention was to improve the admin-side of this application :)

Media Files

IHMO is better to avoid to include the Google's script in the widget and to tell the user to include it in the main template, otherwise we could add an optional version of the tag like

{% easy_map address 300 300 with_js=False %}

(using a custom template for a common case is annoying).

1f0a767 brokes backwards-compatibility by requiring user to include script tag manually or using a form with AddressWithMapWidget.

could be milestoned for a next major version a change in that, maybe indicating it in the documentation, obviously if you are ok with this.

Currently, the inlined javascript may become an issue if multiple instances of the same map are displayed (because the template is parametrized by map.pk), but this is rarely an issue (and this limitation still apply in your branch).

I don't understand what you mean, could you make an example?

It is also better to put easy_maps javascript files to static/easy_maps/js or static/easy_maps, not to static/js because the latter may clash with user files.

ok, I'll commit this

Interactivity

If we move the geocoding code client side (hence removing geo.py) can be problematic in browsers without javascript enabled.

Multiple markers

I like the idea. Currently easy_maps tag accepts either string or Address instance; is it possible to have multiple markers with just strings or form fields? What should syntax look like?

{% easy_map "address1" "address2" 300 300 %}

I don't understand the form fields stuffs, could you elaborate that?

BTW since there are some things you are ok with, you could indicate me what commits keep (or modify) so that I can reorganize this branch with only them in it in order to allow you to merge and maybe you could open an issue for each argument in this discussion with the guideline of implementations.

Mikhail Korobov
Owner

Hi @gipi and sorry for a long delay!

Media files

I like the idea of "with_js" attribute. However there are 3 possibilities:

a) include all the js (script tag with a link to Google and our js map initialization code);
b) include only our map init code;
c) include nothing.

"with_js" should do (b) - but then it needs a better name because some js would still be included :) Any ideas?

I think we shouldn't change the default (with_js=True) though.

Currently, the inlined javascript may become an issue if multiple instances of the same map are displayed (because the template is parametrized by map.pk), but this is rarely an issue (and this limitation still apply in your branch).
I don't understand what you mean, could you make an example?

I mean passing the same Address instance to several easy_maps tags on the same page. They would have the same pk and js code may become incorrect. I don't know if this is a real-life issue.

Interactivity

Interactivity wouldn't work without js anyway, would it? Client-side requests make more sense because they provide the same capabilities and don't block the server. I think we should do geocoding (user enters the address) both on server side (when interactivity is not available or the address is hard-coded in template) and on client side (when interactivity is enabled), and we should do reverse geocoding (when user clicks the map) only on client side; the UI is a tricky part for geocoding. I think the implementation may include some hidden fields.

Multiple markers

I mean "model fields" :) The syntax for static addresses looks good for me. But what with querysets? For example, we have some Shops in database; each shop has an "address" TextField. How to create a map with their addresses on the same map?


I think that settings for the default map center are ready (btw, I like EASY_MAPS_CENTER = (45.0677201, 7.6825531) more, but feel free to EASY_MAPS_CENTER_LAT if you want); other things need some work. It'll be great if you split the features so that we can discuss them faster.

Gianluca Pacchiella

ok, I'll create a branch of each feature; I think for the end of the week maybe :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
11 README.rst
Source Rendered
@@ -17,7 +17,9 @@ Installation
17 17 pip install django-easy-maps
18 18
19 19 Then add 'easy_maps' to INSTALLED_APPS and run ``./manage.py syncdb``
20   -(or ``./manage.py migrate easy_maps`` if South is in use)
  20 +(or ``./manage.py migrate easy_maps`` if South is in use). Since there are
  21 +some media files needed to be used, you have to collect the static files
  22 +distributed with this application (using ``./manage collectstatic``).
21 23
22 24 Settings
23 25 ========
@@ -27,6 +29,13 @@ then create a EASY_MAPS_GOOGLE_KEY in your settings.py file::
27 29
28 30 EASY_MAPS_GOOGLE_KEY = "your-google-maps-api-key"
29 31
  32 +If you need a place where center the map when no address is inserted yet add the
  33 +latitudine and longitude to the EASY_MAPS_CENTER_* variables in your settings.py
  34 +like the following::
  35 +
  36 + EASY_MAPS_CENTER_LAT = -41.3
  37 + EASY_MAPS_CENTER_LON = 15.2
  38 +
30 39 Usage
31 40 =====
32 41
55 easy_maps/admin.py
... ... @@ -1,16 +1,59 @@
1 1 from django.contrib import admin
2 2 from django import forms
  3 +from django.conf.urls.defaults import patterns, url
  4 +from django.http import HttpResponse, HttpResponseBadRequest
  5 +from django.views.decorators.csrf import csrf_exempt
3 6
4 7 from .models import Address
5 8 from .widgets import AddressWithMapWidget
  9 +from .geo import geolocalize
  10 +
  11 +import simplejson
  12 +
  13 +class AddressAdminForm(forms.ModelForm):
  14 + class Meta:
  15 + widgets = {
  16 + 'address': AddressWithMapWidget({'class': 'vTextField'})
  17 + }
6 18
7 19 class AddressAdmin(admin.ModelAdmin):
8   - list_display = ['address', 'computed_address', 'latitude', 'longitude', 'geocode_error']
9   - list_filter = ['geocode_error']
10 20 search_fields = ['address']
  21 + form = AddressAdminForm
  22 + class Media:
  23 + js = (
  24 + 'https://maps.google.com/maps/api/js?sensor=false',
  25 + 'js/easy_maps.js',
  26 + )
  27 +
  28 + def get_urls(self):
  29 + """Add a view that serves geolocalized data on POST request
  30 + """
  31 + urls = super(AddressAdmin, self).get_urls()
  32 + my_urls = patterns('',
  33 + url(r'^geo/$', self.admin_site.admin_view(self.get_geo), name='address_json'),
  34 + )
  35 + return my_urls + urls
11 36
12   - class form(forms.ModelForm):
13   - class Meta:
14   - widgets = {
15   - 'address': AddressWithMapWidget({'class': 'vTextField'})
  37 + # FIXME: add CSRF protection look at https://docs.djangoproject.com/en/1.4/ref/contrib/csrf/#ajax
  38 + # for example in passing a CSRF token
  39 + @csrf_exempt
  40 + def get_geo(self, request):
  41 + """Return a json that will be used to insert correct value
  42 + into the model form.
  43 + """
  44 + if request.method != "POST" or not request.POST.has_key('address') or request.POST['address'] == '':
  45 + return HttpResponseBadRequest()
  46 +
  47 + computed_address, latitude, longitude, geocode_error = geolocalize(request.POST["address"])
  48 + return HttpResponse(simplejson.dumps(
  49 + {
  50 + 'computed_address': computed_address,
  51 + 'latitude': latitude,
  52 + 'longitude': longitude,
  53 + 'geocode_error': geocode_error,
16 54 }
  55 + ), content_type='application/json')
  56 +
  57 +class AddressInlineAdmin(admin.StackedInline):
  58 + extra = 1
  59 + form = AddressAdminForm
20 easy_maps/geo.py
... ... @@ -0,0 +1,20 @@
  1 +from django.conf import settings
  2 +from django.utils.encoding import smart_str
  3 +
  4 +from geopy import geocoders
  5 +
  6 +def geolocalize(address):
  7 + """From an address return the values needed to fullify an Address model form
  8 + """
  9 + try:
  10 + if hasattr(settings, "EASY_MAPS_GOOGLE_KEY") and settings.EASY_MAPS_GOOGLE_KEY:
  11 + g = geocoders.Google(settings.EASY_MAPS_GOOGLE_KEY)
  12 + else:
  13 + g = geocoders.Google(resource='maps')
  14 + s_address = smart_str(address)
  15 + computed_address, (latitude, longitude,) = g.geocode(s_address, exactly_one=False)[0]
  16 + geocode_error = False
  17 + except (UnboundLocalError, ValueError,geocoders.google.GQueryError):
  18 + geocode_error = True
  19 +
  20 + return computed_address, latitude, longitude, geocode_error
35 easy_maps/models.py
... ... @@ -1,29 +1,22 @@
1   -from django.conf import settings
2 1 from django.db import models
3   -from django.utils.encoding import smart_str
4   -from geopy import geocoders
  2 +
  3 +from .geo import geolocalize
  4 +from . import settings
  5 +
5 6
6 7 class Address(models.Model):
7 8 address = models.CharField(max_length=255, db_index=True)
8 9 computed_address = models.CharField(max_length=255, null=True, blank=True)
9   - latitude = models.FloatField(null=True, blank=True)
10   - longitude = models.FloatField(null=True, blank=True)
  10 + latitude = models.FloatField(default=settings.EASY_MAPS_CENTER_LAT, null=True, blank=True)
  11 + longitude = models.FloatField(default=settings.EASY_MAPS_CENTER_LON, null=True, blank=True)
11 12 geocode_error = models.BooleanField(default=False)
12 13
13 14 def fill_geocode_data(self):
14 15 if not self.address:
15 16 self.geocode_error = True
16 17 return
17   - try:
18   - if hasattr(settings, "EASY_MAPS_GOOGLE_KEY") and settings.EASY_MAPS_GOOGLE_KEY:
19   - g = geocoders.Google(settings.EASY_MAPS_GOOGLE_KEY)
20   - else:
21   - g = geocoders.Google(resource='maps')
22   - address = smart_str(self.address)
23   - self.computed_address, (self.latitude, self.longitude,) = g.geocode(address, exactly_one=False)[0]
24   - self.geocode_error = False
25   - except (UnboundLocalError, ValueError,geocoders.google.GQueryError):
26   - self.geocode_error = True
  18 +
  19 + self.computed_address, self.latitude, self.longitude, self.geocode_error = geolocalize(self.address)
27 20
28 21 def save(self, *args, **kwargs):
29 22 # fill geocode data if it is unknown
@@ -38,3 +31,15 @@ class Meta:
38 31 verbose_name = "EasyMaps Address"
39 32 verbose_name_plural = "Address Geocoding Cache"
40 33
  34 + def json(self):
  35 + """Returns a JSON representation of the address data to be used
  36 + with the javascript in a template.
  37 + """
  38 + import simplejson
  39 + dic = {
  40 + 'address': self.address,
  41 + 'computed_address': self.computed_address,
  42 + 'latitude': self.latitude,
  43 + 'longitude': self.longitude,
  44 + }
  45 + return simplejson.dumps(dic)
4 easy_maps/settings.py
... ... @@ -0,0 +1,4 @@
  1 +from django.conf import settings
  2 +
  3 +EASY_MAPS_CENTER_LAT = getattr(settings, 'EASY_MAPS_CENTER_LAT', -34.397)
  4 +EASY_MAPS_CENTER_LON = getattr(settings, 'EASY_MAPS_CENTER_LON', 150.644)
59 easy_maps/static/js/easy_maps.js
... ... @@ -0,0 +1,59 @@
  1 +// will contain the boundary of the map.
  2 +var g_lat_long_bound = new google.maps.LatLngBounds();
  3 +
  4 +function easy_maps_set_form_value(id_prefix) {
  5 + return function (computed_address, lat, lng, error) {
  6 + document.getElementById(id_prefix + 'computed_address').value = computed_address;
  7 + document.getElementById(id_prefix + 'latitude').value = lat
  8 + document.getElementById(id_prefix + 'longitude').value = lng;
  9 + document.getElementById(id_prefix + 'geocode_error').value = error;
  10 + };
  11 +}
  12 +
  13 +function easy_maps_bind_button (id_prefix) {
  14 + django.jQuery.post(
  15 + // FIXME: this is hardcoded
  16 + '/admin/easy_maps/address/geo/', {
  17 + //'{% url admin:address_json %}', {
  18 + 'address': document.getElementById(id_prefix + 'address').value
  19 + },
  20 + function(data) {
  21 + easy_maps_set_form_value(id_prefix)(
  22 + data["computed_address"],
  23 + data["latitude"],
  24 + data["longitude"],
  25 + data["geocode_error"]
  26 + );
  27 + var center = new google.maps.LatLng(data["latitude"], data["longitude"]);
  28 + marker.setPosition(center);
  29 + map.setCenter(center);
  30 + }
  31 + );
  32 +
  33 + return false;
  34 +}
  35 +
  36 +function easy_maps_add_listener(id_prefix, marker) {
  37 + // update the coordinate on marker dragging
  38 + google.maps.event.addListener(marker, 'dragend', function(evt) {
  39 + var ll = marker.getPosition();
  40 + // FIXME: fix id names
  41 + document.getElementById(id_prefix + 'latitude').value = ll.lat();
  42 + document.getElementById(id_prefix + 'longitude').value = ll.lng();
  43 + });
  44 +}
  45 +
  46 +function easy_maps_add_marker(map, marker) {
  47 + var latlng = new google.maps.LatLng(marker.latitude, marker.longitude);
  48 + var marker = new google.maps.Marker({
  49 + position: latlng,
  50 + map: map,
  51 + draggable: true,
  52 + title: marker.address
  53 + });
  54 +
  55 + // add marker's coordinate to the boundary
  56 + g_lat_long_bound.extend(latlng);
  57 +
  58 + return marker;
  59 +}
6 easy_maps/templates/admin/easy_maps/change_list.html
... ... @@ -0,0 +1,6 @@
  1 +{% extends "admin/change_list.html" %}
  2 +{% load easy_maps_tags %}
  3 +{% block result_list %}
  4 + {{block.super}}
  5 + {% easy_map cl.query_set 900 700 %}
  6 +{% endblock %}
47 easy_maps/templates/easy_maps/map.html
... ... @@ -1,21 +1,15 @@
1   -{% with map.latitude|stringformat:"f" as lat %}
2   -{% with map.longitude|stringformat:"f" as long %}
3   -
4   -{% block api_js %}
5   - <!-- Google Maps API javascript -->
6   - <script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>
7   -{% endblock %}
  1 +{% load easy_maps_tags %}
  2 +{% with latitude|stringformat:"f" as lat %}
  3 +{% with longitude|stringformat:"f" as long %}
8 4
9 5 {% block html %}
10 6 <!-- HTML map container -->
11   - <div id="map-canvas-{{ map.pk }}"
12   - {% if width and map.latitude and not map.geocode_error %}
13   - style="width: {{ width }}px; height: {{ height }}px;"
14   - {% endif %}
  7 + <div id="map-canvas-{{ id }}"
  8 + style="width: {{ width }}px; height: {{ height }}px;"
15 9 class="easy-map-googlemap">
16 10 {% block noscript %}
17 11 <noscript>
18   - <img alt="Map of {{ map.address }}" src="https://maps.google.com/maps/api/staticmap?center={{ lat }},{{ long }}&zoom={{ zoom }}&markers={{ lat }},{{ long }}&size={{ width }}x{{ height }}&sensor=false">
  12 + <img alt="Map of {{ address }}" src="https://maps.google.com/maps/api/staticmap?center={{ lat }},{{ long }}&zoom={{ zoom }}&markers={{ lat }},{{ long }}&size={{ width }}x{{ height }}&sensor=false">
19 13 </noscript>
20 14 {% endblock noscript %}
21 15 </div>
@@ -24,10 +18,9 @@
24 18 {% block map_js %}
25 19 <!-- Map creation script -->
26 20 <script type="text/javascript">
27   - function initialize_map_{{ map.pk }}() {
  21 + function initialize_map_{{ id_safe }}() {
28 22 var latlng = new google.maps.LatLng({{ lat }}, {{ long }});
29   - var mapElem = document.getElementById("map-canvas-{{ map.pk }}");
30   -
  23 + var mapElem = document.getElementById("map-canvas-{{ id }}");
31 24 {% block map_options_js %}
32 25 var mapOptions = {
33 26 zoom: {{ zoom }},
@@ -39,20 +32,30 @@
39 32 var map = new google.maps.Map(mapElem, mapOptions);
40 33
41 34 {% block extra_js %}
42   - var marker = new google.maps.Marker({
43   - position: latlng,
44   - map: map,
45   - title: "{{ map.address }}"
46   - });
  35 + {% if markers %}
  36 + {% for marker in markers %}
  37 + var marker = easy_maps_add_marker(map, {{marker.json|safe}});
  38 + {% endfor %}
  39 +
  40 + {% comment %}use the zoom level passed as argument if there is only one marker{% endcomment %}
  41 + {% if markers|length > 1 %}
  42 + // display all the markers
  43 + // http://blog.shamess.info/2009/09/29/zoom-to-fit-all-markers-on-google-maps-api-v3/
  44 + map.fitBounds(g_lat_long_bound);
  45 + {% else %}
  46 + easy_maps_add_listener("{{ id }}", marker);
  47 + {% endif %}
  48 + {% endif %}
  49 +
47 50 {% endblock %}
48 51 }
49 52
50 53 {% block map_loading_js %}
51 54 // initialize the map after page loading
52   - google.maps.event.addDomListener(window, 'load', initialize_map_{{ map.pk }});
  55 + google.maps.event.addDomListener(window, 'load', initialize_map_{{ id_safe }});
53 56 {% endblock %}
54 57 </script>
55 58 {% endblock %}
56 59
57 60 {% endwith %}
58   -{% endwith %}
  61 +{% endwith %}
30 easy_maps/templatetags/easy_maps_tags.py
... ... @@ -1,14 +1,24 @@
1 1 #coding: utf-8
2 2 from django import template
3 3 from django.template.loader import render_to_string
4   -from easy_maps.models import Address
  4 +from ..models import Address
  5 +from .. import settings
  6 +
5 7 register = template.Library()
6 8
7 9 @register.tag
8 10 def easy_map(parser, token):
9 11 """
10 12 The syntax:
  13 +
11 14 {% easy_map <address> [<width> <height>] [<zoom>] [using <template_name>] %}
  15 +
  16 + or
  17 +
  18 + {% easy_map <addresses> [<width> <height>] [<zoom>] [using <template_name>] %}
  19 +
  20 + where in the second case you pass a queryset containing the addresses to be
  21 + visualized.
12 22 """
13 23 width, height, zoom, template_name = None, None, None, None
14 24 params = token.split_contents()
@@ -42,16 +52,26 @@ def __init__(self, address, width, height, zoom, template_name):
42 52
43 53 def render(self, context):
44 54 try:
45   - address = self.address.resolve(context)
  55 + address = self.address.resolve(context) or ''
46 56 template_name = self.template_name.resolve(context)
47 57
48   - map, _ = Address.objects.get_or_create(address=address or '')
  58 + if isinstance(address, basestring):
  59 + # if not exists the searched address then created an unsaved instance
  60 + try:
  61 + address = Address.objects.get(address=address)
  62 + except:
  63 + address = Address(address=address)
  64 +
  65 + address = [address,]
  66 +
49 67 context.update({
50   - 'map': map,
  68 + 'markers': address,
51 69 'width': self.width,
52 70 'height': self.height,
  71 + # FIXME: if the list is empty?
  72 + 'latitude': address[0].latitude,
  73 + 'longitude': address[0].longitude,
53 74 'zoom': self.zoom,
54   - 'template_name': template_name
55 75 })
56 76 return render_to_string(template_name, context_instance=context)
57 77 except template.VariableDoesNotExist:
21 easy_maps/widgets.py
@@ -2,8 +2,25 @@
2 2 from django.template import Template, Context
3 3
4 4 class AddressWithMapWidget(TextInput):
  5 + class Media:
  6 + js = (
  7 + 'https://maps.google.com/maps/api/js?sensor=false',
  8 + 'js/easy_maps.js',
  9 + )
5 10 def render(self, name, value, attrs=None):
  11 + # retrieve the field's id otherwise it's not possible
  12 + # to use correctly the JS
  13 + _id = attrs["id"]
  14 +
  15 + # we assume two conditions on 'id'
  16 + assert _id.find('id_') == 0
  17 +
  18 + find_id = _id.find('address')
  19 + assert find_id > 0
  20 +
  21 + _id = _id[:find_id]
6 22 default_html = super(AddressWithMapWidget, self).render(name, value, attrs)
7   - map_template = Template("{% load easy_maps_tags %}{% easy_map address 700 200 16 %}")
8   - context = Context({'address': value})
  23 + map_template = Template("""<button type='button' onclick='easy_maps_bind_button("{{ id }}")'>update</button>{% load easy_maps_tags %}{% easy_map address 700 200 16 %}""")
  24 +
  25 + context = Context({'id': _id, 'id_safe': _id.replace('-', '_'), 'address': value})
9 26 return default_html + map_template.render(context)
2  easy_maps_tests/settings.py
@@ -28,3 +28,5 @@
28 28 # 'devserver',
29 29 'south',
30 30 )
  31 +
  32 +EASY_MAPS_CENTER = (45.0677201, 7.6825531)
26 easy_maps_tests/test_app/models.py
... ... @@ -1 +1,27 @@
1 1 #hello, testrunner!
  2 +from django.db import models
  3 +from django.contrib import admin
  4 +
  5 +from easy_maps.models import Address
  6 +from easy_maps.admin import AddressInlineAdmin
  7 +
  8 +class Brand(models.Model):
  9 + name = models.CharField(max_length=100)
  10 +
  11 + def count(self):
  12 + return self.shop_set.count()
  13 +
  14 +class Shop(Address):
  15 + brand = models.ForeignKey(Brand)
  16 +
  17 +class ShopInlineAdmin(AddressInlineAdmin):
  18 + model = Shop
  19 +
  20 +class BrandAdmin(admin.ModelAdmin):
  21 + list_display = ['name', 'count',]
  22 + model = Brand
  23 + inlines = [
  24 + ShopInlineAdmin,
  25 + ]
  26 +
  27 +admin.site.register(Brand, BrandAdmin)

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.