Projet

Général

Profil

0002-maps-map-cell-8454.patch

Voir les différences:

Subject: [PATCH 2/2] maps: map cell (#8454)

 combo/apps/maps/migrations/0002_mapcell.py      | 40 ++++++++++++++
 combo/apps/maps/models.py                       | 73 +++++++++++++++++++++++++
 combo/apps/maps/static/css/map.css              | 32 +++++++++++
 combo/apps/maps/static/js/map.form.js           | 18 ++++++
 combo/apps/maps/static/js/map.js                | 62 +++++++++++++++++++++
 combo/apps/maps/templates/maps/map_widget.html  |  4 ++
 combo/apps/maps/templates/maps/mapcell.html     |  4 ++
 combo/apps/maps/urls.py                         |  4 ++
 combo/apps/maps/views.py                        | 21 ++++++-
 combo/manager/templates/combo/manager_base.html |  4 ++
 combo/settings.py                               |  2 +
 debian/control                                  |  1 +
 setup.py                                        |  1 +
 13 files changed, 265 insertions(+), 1 deletion(-)
 create mode 100644 combo/apps/maps/migrations/0002_mapcell.py
 create mode 100644 combo/apps/maps/static/css/map.css
 create mode 100644 combo/apps/maps/static/js/map.form.js
 create mode 100644 combo/apps/maps/static/js/map.js
 create mode 100644 combo/apps/maps/templates/maps/map_widget.html
 create mode 100644 combo/apps/maps/templates/maps/mapcell.html
combo/apps/maps/migrations/0002_mapcell.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('auth', '0006_require_contenttypes_0002'),
11
        ('data', '0025_jsoncell_varnames_str'),
12
        ('maps', '0001_initial'),
13
    ]
14

  
15
    operations = [
16
        migrations.CreateModel(
17
            name='MapCell',
18
            fields=[
19
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
20
                ('placeholder', models.CharField(max_length=20)),
21
                ('order', models.PositiveIntegerField()),
22
                ('slug', models.SlugField(verbose_name='Slug', blank=True)),
23
                ('extra_css_class', models.CharField(max_length=100, verbose_name='Extra classes for CSS styling', blank=True)),
24
                ('public', models.BooleanField(default=True, verbose_name='Public')),
25
                ('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
26
                ('last_update_timestamp', models.DateTimeField(auto_now=True)),
27
                ('title', models.CharField(max_length=150, verbose_name='Title', blank=True)),
28
                ('default_position', models.CharField(default=b'48.83369263315934;2.3233688436448574', max_length=128, blank=True, help_text=b'geographical coords', null=True, verbose_name='Default position')),
29
                ('initial_zoom', models.CharField(default=b'13', max_length=2, verbose_name='Initial zoom level', choices=[(b'0', 'Whole world'), (b'9', 'Wide area'), (b'11', 'Area'), (b'13', 'Town'), (b'16', 'Small road'), (b'19', 'Ant')])),
30
                ('min_zoom', models.CharField(default=b'0', max_length=2, verbose_name='Minimal zoom level', choices=[(b'0', 'Whole world'), (b'9', 'Wide area'), (b'11', 'Area'), (b'13', 'Town'), (b'16', 'Small road'), (b'19', 'Ant')])),
31
                ('max_zoom', models.CharField(default=19, max_length=2, verbose_name='Maximal zoom level', choices=[(b'0', 'Whole world'), (b'9', 'Wide area'), (b'11', 'Area'), (b'13', 'Town'), (b'16', 'Small road'), (b'19', 'Ant')])),
32
                ('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)),
33
                ('layer', models.ManyToManyField(to='maps.MapLayer')),
34
                ('page', models.ForeignKey(to='data.Page')),
35
            ],
36
            options={
37
                'verbose_name': 'Map Cell',
38
            },
39
        ),
40
    ]
combo/apps/maps/models.py
17 17

  
18 18
from django.db import models
19 19
from django.utils.translation import ugettext_lazy as _
20
from django.core.urlresolvers import reverse_lazy
21
from django import forms
22
from django import template
23

  
24
from combo.data.models import CellBase
25
from combo.data.library import register_cell_class
26

  
27
zoom_levels = [ ('0', _('Whole world')),
28
                ('9', _('Wide area')),
29
                ('11', _('Area')),
30
                ('13', _('Town')),
31
                ('16', _('Small road')),
32
                ('19', _('Ant')),]
20 33

  
21 34
from combo.utils import requests
22 35

  
23 36

  
37
class MapWidget(forms.TextInput):
38
    template_name = 'maps/map_widget.html'
39

  
40
    def render(self, name, value, attrs):
41
        final_attrs = self.build_attrs(attrs, name=name, value=value,
42
                                       type='hidden')
43
        cell_form_template = template.loader.get_template(self.template_name)
44
        return cell_form_template.render(final_attrs)
45

  
46

  
24 47
class MapLayer(models.Model):
25 48
    label = models.CharField(_('Label'), max_length=128)
26 49
    geojson_url = models.URLField(_('Geojson URL'))
......
46 69
                feature['properties']['label'] = self.label
47 70
                feature['properties']['icon'] = self.icon
48 71
        return features
72

  
73

  
74
@register_cell_class
75
class MapCell(CellBase):
76
    title = models.CharField(_('Title'), max_length=150, blank=True)
77
    default_position = models.CharField(_('Default position'), null=True, blank=True,
78
                                        default='48.83369263315934;2.3233688436448574',
79
                                        max_length=128)
80
    initial_zoom = models.CharField(_('Initial zoom level'), max_length=2,
81
                                    choices=zoom_levels, default='13')
82
    min_zoom = models.CharField(_('Minimal zoom level'), max_length=2,
83
                                   choices=zoom_levels, default='0')
84
    max_zoom = models.CharField(_('Maximal zoom level'), max_length=2,
85
                                choices=zoom_levels, default=19)
86
    layer = models.ManyToManyField(MapLayer, verbose_name=_('Layers'))
87

  
88
    template_name = 'maps/mapcell.html'
89

  
90
    class Meta:
91
        verbose_name = _('Map Cell')
92

  
93
    class Media:
94
        js = ('xstatic/leaflet.js', 'js/map.js')
95
        css = {'all': ('xstatic/leaflet.css', 'xstatic/css/font-awesome.min.css',
96
                       'css/map.css')}
97

  
98
    def get_default_form_class(self):
99
        fields = ('title', 'default_position', 'initial_zoom', 'min_zoom',
100
                  'max_zoom', 'layer')
101
        lat, lng = self.default_position.split(';')
102
        widgets = {'layer': forms.widgets.CheckboxSelectMultiple,
103
                   'default_position': MapWidget(attrs={'init_lat': lat,
104
                                                        'init_lng': lng})}
105
        return forms.models.modelform_factory(self.__class__, fields=fields,
106
                                             widgets=widgets)
107

  
108

  
109
    @classmethod
110
    def is_enabled(cls):
111
        return MapLayer.objects.count() > 0
112

  
113
    def get_cell_extra_context(self, context):
114
        ctx = super(MapCell, self).get_cell_extra_context(context)
115
        ctx['title'] = self.title
116
        ctx['initial_lat'], ctx['initial_lng'] = self.default_position.split(';');
117
        ctx['initial_zoom'] = self.initial_zoom
118
        ctx['min_zoom'] = self.min_zoom
119
        ctx['max_zoom'] = self.max_zoom
120
        ctx['geojson_url'] = reverse_lazy('cell-geojson', kwargs={'cell_id': self.pk})
121
        return ctx
combo/apps/maps/static/css/map.css
1
div#mapcell {
2
    height: 60vh;
3
}
4

  
5
/* leaflet styles */
6

  
7
div.leaflet-div-icon span {
8
    width: 2.3rem;
9
    height: 2.3rem;
10
    display: block;
11
    left: -1rem;
12
    top: -1rem;
13
    position: relative;
14
    border-radius: 11rem 6rem 0.8rem;
15
    transform: scale(1, 1.3) rotate(45deg);
16
    border: 1px solid #aaa;
17
}
18

  
19
div.leaflet-popup-content span {
20
    display: block;
21
}
22

  
23
div.leaflet-popup-content span.field-label {
24
    font-weight: bold;
25
    text-transform: capitalize;
26
}
27

  
28
div.leaflet-div-icon span i:before {
29
    display: inline-block;
30
    margin: 7px 8px;
31
    transform: scale(1.1) rotate(-45deg);
32
}
combo/apps/maps/static/js/map.form.js
1
$(window).on('load', function() {
2
    $('div#mapcell_widget').each(function() {
3
        var $map_widget = $(this);
4
        var map_options = Object();
5
        map_options.zoom = 13;
6
        var map = L.map($(this).attr('id'), map_options);
7
        map.marker = null;
8
        latlng = [$map_widget.data('init-lat'), $map_widget.data('init-lng')];
9
        map.marker = L.marker(latlng);
10
        map.marker.addTo(map);
11
        map.setView(latlng, map_options.zoom);
12
        L.tileLayer(
13
            'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
14
            {
15
                attribution: 'Map data &copy; <a href="https://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
16
            }).addTo(map);
17
    })
18
});
combo/apps/maps/static/js/map.js
1
$(window).on('load', function() {
2
    $('div#mapcell').each(function() {
3
        var $map_widget = $(this);
4
        var map_options = Object();
5
        var initial_zoom = parseInt($map_widget.data('initial-zoom'));
6
        if (! isNaN(initial_zoom)) {
7
            map_options.zoom = initial_zoom;
8
        } else {
9
            map_options.zoom = 13;
10
        }
11
        var max_zoom = parseInt($map_widget.data('max_zoom'));
12
        if (!isNaN(max_zoom)) map_options.maxZoom = max_zoom;
13
        var min_zoom = parseInt($map_widget.data('min-zoom'));
14
        if (!isNaN(min_zoom)) map_options.minZoom = min_zoom;
15
        var latlng = [$map_widget.data('init-lat'), $map_widget.data('init-lng')];
16
        var geojson_url = $map_widget.data('geojson-url');
17
        var map = L.map($(this).attr('id'), map_options);
18
        var store_position_selector = $map_widget.data('store-position');
19
        map.setView(latlng, map_options.zoom);
20

  
21
        L.tileLayer(
22
            'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
23
            {
24
                attribution: 'Map data &copy; <a href="https://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
25
            }).addTo(map);
26
        if (store_position_selector) {
27
            map.marker = L.marker(latlng);
28
            map.marker.addTo(map);
29
            var hidden = $('input#' + store_position_selector);
30
            map.on('click', function(e) {
31
                map.marker.setLatLng(e.latlng);
32
                hidden.val(e.latlng.lat + ';' + e.latlng.lng);
33
            });
34
        }
35
        if (geojson_url) {
36
            $.getJSON(geojson_url, function(data) {
37
                var geo_json = L.geoJson(data, {
38
                    onEachFeature: function(feature, layer) {
39
                        if (feature.display_fields) {
40
                            var popup = '';
41
                            $.each(feature.display_fields, function(key, value) {
42
                                popup += '<p class="popup-field"><span class="field-label">' + key + '</span>';
43
                                popup += '<span class="field-value">' + value + '</span></p>';
44
                            });
45
                        } else {
46
                            var popup = '<p class="popup-field">' + feature.properties.label + '</p>';
47
                        }
48
                        layer.bindPopup(popup);
49
                    },
50
                    pointToLayer: function (feature, latlng) {
51
                        var markerStyles = "background-color: "+feature.properties.colour+";";
52
                        marker = L.divIcon({iconAnchor: [0, 24],
53
                                            html: '<span style="' + markerStyles + '"><i class="'+feature.properties.icon+'" style="color:'+feature.properties.icon_colour+'"></i></span>'});
54
                        return L.marker(latlng, {icon: marker});
55
                    }
56
                });
57
                map.fitBounds(geo_json.getBounds());
58
                geo_json.addTo(map);
59
            });
60
        }
61
    });
62
});
combo/apps/maps/templates/maps/map_widget.html
1
<div id="mapcell" data-init-lat="{{ init_lat }}" data-init-lng="{{ init_lng }}" data-store-position="{{ id }}">
2
</div>
3

  
4
<input type="{{ type }}" name="{{ name }}" value="{{ value }}" id="{{ id }}" />
combo/apps/maps/templates/maps/mapcell.html
1
{% load i18n %}
2
<h2>{{ title }}</h2>
3
<div id="mapcell" data-initial-zoom="{{ initial_zoom }}" data-min-zoom="{{ min_zoom }}" data-max-zoom="{{ max_zoom }}" data-init-lat="{{ initial_lat }}" data-init-lng="{{ initial_lng }}" data-geojson-url="{{ geojson_url }}">
4
</div>
combo/apps/maps/urls.py
21 21
from .manager_views import (ManagerHomeView, LayersManagerView, LayerAddView,
22 22
                LayerEditView, LayerDeleteView)
23 23

  
24
from .views import GeojsonView
25

  
24 26
maps_manager_urls = [
25 27
    url('^$', ManagerHomeView.as_view(), name='maps-manager-homepage'),
26 28
    url('^layers/$', LayersManagerView.as_view(), name='maps-manager-layers-list'),
......
36 38
urlpatterns = [
37 39
    url(r'^manage/maps/', decorated_includes(manager_required,
38 40
        include(maps_manager_urls))),
41
    url(r'^maps/geojson/(?P<cell_id>\w+)/$', GeojsonView.as_view(),
42
        name='cell-geojson'),
39 43
]
combo/apps/maps/views.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
from django.shortcuts import render
17
import json
18

  
19
from django.views.generic.base import View
20
from django.http import HttpResponse
21

  
22
from combo.utils import requests
23

  
24
from .models import MapCell
25

  
26

  
27
class GeojsonView(View):
28

  
29
    def get(self, request, *args, **kwargs):
30
        cell = MapCell.objects.get(pk=kwargs['cell_id'])
31
        geojson = {'type': 'FeatureCollection', 'features': []}
32
        for layer in cell.layer.all():
33
            geojson['features'] += layer.get_geojson()
34

  
35
        content_type = 'application/json'
36
        return HttpResponse(json.dumps(geojson), content_type=content_type)
combo/manager/templates/combo/manager_base.html
3 3

  
4 4
{% block css %}
5 5
<link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}css/combo.manager.css"/>
6
<link rel="stylesheet" type="text/css" media="all" href="{% static "xstatic/leaflet.css" %}"></script>
7
<link rel="stylesheet" type="text/css" media="all" href="{% static "css/map.css" %}"></script>
6 8
{% endblock %}
7 9
{% block page-title %}{% firstof site_title "Combo" %}{% endblock %}
8 10
{% block site-title %}{% firstof site_title "Combo" %}{% endblock %}
......
31 33
<script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
32 34
<script src="{% static "js/combo.manager.js" %}"></script>
33 35
<script src="{% static "js/jquery.colourpicker.js" %}"></script>
36
<script src="{% static "xstatic/leaflet.js" %}"></script>
37
<script src="{% static "js/map.js" %}"></script>
34 38
{% endblock %}
combo/settings.py
78 78
    'combo.apps.maps',
79 79
    'haystack',
80 80
    'xstatic.pkg.chartnew_js',
81
    'xstatic.pkg.leaflet',
82
    'xstatic.pkg.font_awesome',
81 83
)
82 84

  
83 85
INSTALLED_APPS = plugins.register_plugins_apps(INSTALLED_APPS)
debian/control
16 16
    python-feedparser,
17 17
    python-django-cmsplugin-blurp,
18 18
    python-xstatic-chartnew-js,
19
    python-xstatic-leaflet,
19 20
    python-eopayment (>= 1.9),
20 21
    python-django-haystack (>= 2.4.0)
21 22
Recommends: python-django-mellon, python-whoosh
setup.py
111 111
        'django-jsonfield',
112 112
        'requests',
113 113
        'XStatic-ChartNew.js',
114
        'XStatic-Leaflet',
114 115
        'eopayment>=1.13',
115 116
        'python-dateutil',
116 117
        'djangorestframework>=3.3, <3.4',
117
-