diff options
Diffstat (limited to 'chimere/widgets.py')
| -rw-r--r-- | chimere/widgets.py | 901 |
1 files changed, 901 insertions, 0 deletions
diff --git a/chimere/widgets.py b/chimere/widgets.py new file mode 100644 index 0000000..6d61965 --- /dev/null +++ b/chimere/widgets.py @@ -0,0 +1,901 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2008-2016 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# See the file COPYING for details. + +""" +Extra widgets and fields +""" + +from json import JSONEncoder + +from django import forms +from django.conf import settings +from django.contrib.gis.db import models +from django.contrib.gis.geos import fromstr +from django.core.exceptions import ObjectDoesNotExist +from django.core.urlresolvers import reverse +from django.forms.widgets import RadioInput, RadioFieldRenderer, flatatt +from django.utils.encoding import force_unicode, smart_unicode +from django.utils.html import conditional_escape, escape +from django.utils.safestring import mark_safe +from django.utils.translation import ugettext as _ +from django.template.loader import render_to_string + +import re + + +def getMapJS(area_name=''): + '''Variable initialization for drawing the map + ''' + # projection, center and bounds definitions + js = u"var epsg_display_projection = 'EPSG:%d';\n" \ + % settings.CHIMERE_EPSG_DISPLAY_PROJECTION + js += u"var epsg_projection = 'EPSG:%d';\n" % \ + settings.CHIMERE_EPSG_PROJECTION + js += u"var centerLonLat = ol.proj.transform("\ + u"[%f, %f], epsg_display_projection, epsg_projection);\n" % \ + settings.CHIMERE_DEFAULT_CENTER + js += u"var media_path = '%s';\n" % settings.MEDIA_URL + js += u"var static_path = '%s';\n" % settings.STATIC_URL + js += u"var map_layer = %s;\n" % settings.CHIMERE_DEFAULT_MAP_LAYER + js += u"var restricted_extent;\n" + + if area_name: + js += u"var area_name='%s';\n" % area_name + js = u"<script type='text/javascript'><!--\n"\ + u"%s// !--></script>\n" % js + return js + + +def get_map_layers(area_name='', get_area_zoom=False): + from chimere.models import Area + area = None + if area_name: + try: + area = Area.objects.get(urn=area_name) + except ObjectDoesNotExist: + pass + else: + try: + area = Area.objects.get(default=True) + except ObjectDoesNotExist: + pass + map_layers, default = [], None + if area and area.layers.count(): + map_layers = [ + [layer.name, layer.layer_code, False, layer.extra_js_code or ''] + for layer in area.layers.order_by('arealayers__order').all()] + def_layer = area.layers.filter(arealayers__default=True) + if def_layer.count(): + def_layer = def_layer.all()[0] + for order, map_layer in enumerate(map_layers): + if map_layer[1] == def_layer.layer_code: + default = order + map_layers[order][2] = True + else: + map_layers[0][2] = True + elif settings.CHIMERE_DEFAULT_MAP_LAYER: + map_layers = [(_(u"Default layer"), settings.CHIMERE_DEFAULT_MAP_LAYER, + True, '')] + else: + map_layers = [(u"OSM", """new ol.layer.Tile({ + style: 'Road', + source: new ol.source.OSM() +})""", True, '')] + if not get_area_zoom: + return map_layers, default + if not area: + return map_layers, default, settings.CHIMERE_DEFAULT_ZOOM + zoom = "[%s]" % ",".join(area.getExtent()) + return map_layers, default, zoom + + +class ChosenSelectWidget(forms.Select): + """ + Chosen select widget. + """ + class Media: + js = ["%schosen/chosen/chosen.jquery.min.js" % settings.STATIC_URL] + css = { + 'all': + ["%schosen/chosen/chosen.css" % settings.STATIC_URL] + } + + def render(self, *args, **kwargs): + if 'attrs' not in kwargs: + kwargs['attrs'] = {} + kwargs['attrs'].update({'class': 'chzn-select'}) + rendered = super(ChosenSelectWidget, self).render(*args, **kwargs) + rendered += u"\n<script type='text/javascript'>\n"\ + u" $('#%s').chosen();\n"\ + u"</script>\n" % kwargs['attrs']['id'] + return mark_safe(rendered) + +""" +JQuery UI button select widget. +""" + + +class ButtonRadioInput(RadioInput): + def render(self, name=None, value=None, attrs=None, choices=()): + name = name or self.name + value = value or self.value + attrs = attrs or self.attrs + if 'id' in self.attrs: + label_for = ' for="%s_%s"' % (self.attrs['id'], self.index) + else: + label_for = '' + choice_label = conditional_escape(force_unicode(self.choice_label)) + return mark_safe(u'%s <label%s>%s</label>' % (self.tag(), label_for, + choice_label)) + + +class ButtonRadioFieldRenderer(RadioFieldRenderer): + def __iter__(self): + for i, choice in enumerate(self.choices): + yield ButtonRadioInput(self.name, self.value, self.attrs.copy(), + choice, i) + + def render(self): + return mark_safe(u'\n'.join([force_unicode(w) for w in self])) + + +class ButtonSelectWidget(forms.RadioSelect): + def __init__(self, *args, **kwargs): + self.renderer = ButtonRadioFieldRenderer + super(ButtonSelectWidget, self).__init__(*args, **kwargs) + + def render(self, *args, **kwargs): + rendered = "<div id='%s'>\n" % kwargs['attrs']['id'] + rendered += super(ButtonSelectWidget, self).render(*args, **kwargs) + rendered += u"\n<script type='text/javascript'>\n"\ + u" $('#%s').buttonset();\n"\ + u"</script>\n</div>\n" % kwargs['attrs']['id'] + return mark_safe(rendered) + + +class ImporterChoicesWidget(forms.Select): + ''' + Importer select widget. + ''' + class Media: + js = ["%schimere/js/importer_interface.js" % settings.STATIC_URL] + +TINYMCE_JS, FULL_TINY_JS, ADMIN_TINY_JS = [], [], [] + +if settings.TINYMCE_URL: + TINYMCE_JS = ["%stiny_mce.js" % settings.TINYMCE_URL] + FULL_TINY_JS = TINYMCE_JS[:] + \ + ["%schimere/js/textareas.js" % settings.STATIC_URL] + ADMIN_TINY_JS = TINYMCE_JS[:] + \ + ["%schimere/js/textareas_admin.js" % settings.STATIC_URL] + + +class TextareaWidgetBase(forms.Textarea): + """ + Manage the edition of a text using TinyMCE + """ + def render(self, *args, **kwargs): + if not TINYMCE_JS: + rendered = super(TextareaWidgetBase, self).render(*args, **kwargs) + return mark_safe(rendered) + if 'attrs' not in kwargs: + kwargs['attrs'] = {} + if 'class' not in kwargs['attrs']: + kwargs['attrs']['class'] = '' + else: + kwargs['attrs']['class'] += ' ' + kwargs['attrs']['class'] += 'mceEditor' + rendered = super(TextareaWidgetBase, self).render(*args, **kwargs) + return mark_safe(rendered) + + +class FullTextareaWidget(TextareaWidgetBase): + """ + Manage the edition of a text using TinyMCE + """ + class Media: + js = TINYMCE_JS + + def render(self, *args, **kwargs): + if not TINYMCE_JS: + rendered = super(FullTextareaWidget, self).render(*args, **kwargs) + return mark_safe(rendered) + + if 'attrs' not in kwargs: + kwargs['attrs'] = {} + if 'class' not in kwargs['attrs']: + kwargs['attrs']['class'] = '' + else: + kwargs['attrs']['class'] += ' ' + kwargs['attrs']['class'] += 'mceEditor' + rendered = super(FullTextareaWidget, self).render(*args, **kwargs) + rendered += u"""<script type='text/javascript'> +tinyMCE.init({ + mode : "textareas", + theme : "advanced", + relative_urls : false, + editor_selector : "mceEditor" +}); +</script> +""" + return mark_safe(rendered) + + +class TextareaWidget(TextareaWidgetBase): + """ + Manage the edition of a text using TinyMCE + """ + class Media: + js = FULL_TINY_JS + + +class TextareaAdminWidget(TextareaWidgetBase): + class Media: + js = ADMIN_TINY_JS + + +class DatePickerWidget(forms.TextInput): + """ + Manage the edition of dates. + JQuery and Jquery-UI are already loaded by default so don't include + them in Media files. + """ + def render(self, *args, **kwargs): + rendered = super(DatePickerWidget, self).render(*args, **kwargs) + rendered += u"\n<script type='text/javascript'>\n"\ + u" $(function() {$('#%s').datepicker("\ + u"{ dateFormat: 'yy-mm-dd' });});\n"\ + u"</script>\n" % kwargs['attrs']['id'] + return mark_safe(rendered) + + +class JQueryAutoComplete(forms.TextInput): + TEMPLATE = "chimere/blocks/JQueryAutoComplete.html" + + def __init__(self, slug, options={}, attrs={}): + """ + Source can be a list containing the autocomplete values or a + string containing the url used for the request. + """ + self.options = None + self.attrs = {} + self.slug = slug + if len(options) > 0: + self.options = JSONEncoder().encode(options) + self.attrs.update(attrs) + + def get_source(self): + # Strange... to be fixed + source = reverse('chimere:property-choices', + kwargs={'property_slug': self.slug}) + return "'{}'".format(source) + + def render(self, name, value=None, attrs=None): + attrs_hidden = self.build_attrs(attrs, name=name) + attrs_select = self.build_attrs(attrs) + selected_value, rendered_value = "", "" + + if value: + val = escape(smart_unicode(value)) + attrs_hidden['value'] = val + attrs_select['value'] = val + selected_value = val + if val: + from chimere.models import PropertyModelChoice + try: + attrs_select['value'] = unicode( + PropertyModelChoice.objects.get( + pk=value, propertymodel__slug=self.slug)) + rendered_value = attrs_select['value'] + except: + attrs_select['value'] = "" + if 'id' not in self.attrs: + attrs_hidden['id'] = 'id_%s' % name + attrs_select['id'] = 'id_select_%s' % name + if 'class' not in attrs_select: + attrs_select['class'] = 'autocomplete' + dct = { + 'attrs_select': flatatt(attrs_select), + 'attrs_hidden': flatatt(attrs_hidden), + 'field_id': name, + 'min_field_id': name.replace('_', ''), + 'options': self.options, + 'source': self.get_source(), + 'selected_value': selected_value, + 'rendered_value': rendered_value + } + + return mark_safe( + render_to_string(self.TEMPLATE, dct)) + + +class NominatimWidget(forms.TextInput): + class Media: + js = ["%schimere/js/nominatim-widget.js" % settings.STATIC_URL] + + def render(self, name, value, attrs=None, area_name=''): + dct = {'id': name, 'nominatim_url': settings.NOMINATIM_URL, + 'label': _(u"Street, City, Country")} + + tpl = u""" +<input type='hidden' name='nominatim_%(id)s_lat' id='nominatim_%(id)s_lat'/> +<input type='hidden' name='nominatim_%(id)s_lon' id='nominatim_%(id)s_lon'/> +<input type='text' class='nominatim-widget' name='nominatim_%(id)s' + id='nominatim_%(id)s' value=""/> +<label class='nominatim-label' id='nominatim_%(id)s_label'> </label> +<script type='text/javascript'> +var default_nominatim_lbl = "%(label)s"; +var nominatim_url = "%(nominatim_url)s"; +$("#nominatim_%(id)s").val(default_nominatim_lbl); +$("#nominatim_%(id)s").click(function(){ + $("#nominatim_%(id)s").val(''); +}); +</script> +""" % dct + return mark_safe(tpl) + + +class PointChooserWidget(forms.TextInput): + """ + Manage the edition of point on a map + """ + class Media: + css = { + "all": settings.MAP_CSS_URLS + + ["%schimere/css/forms.css" % settings.STATIC_URL] + } + js = settings.MAP_JS_URLS + list(settings.JQUERY_JS_URLS) + \ + ["%schimere/js/jquery.chimere.js" % settings.STATIC_URL] + + def render(self, name, value, attrs=None, area_name='', initialized=True): + ''' + Render a map and latitude, longitude information field + ''' + val = '' + value_x, value_y = 0, 0 + if value: + val = str(value) + if hasattr(value, 'x') and hasattr(value, 'y'): + value_x, value_y = value.x, value.y + elif isinstance(value, unicode) and value.startswith('POINT('): + try: + value_x, value_y = value.split('(')[1][:-1].split(' ') + value_x, value_y = float(value_x), float(value_y) + except: + value = None + else: + value = None + map_layers, default_area, zoom = get_map_layers(area_name, + get_area_zoom=True) + extra_js = [extra_js for n, js, default, extra_js in map_layers + if extra_js] + map_layers = [js for n, js, default, xtra_js in map_layers if js] + # TODO: manage area + return mark_safe( + render_to_string('chimere/blocks/live_coordinates.html', + {'lat': _("Latitude"), + 'value_y': value_y, + 'lon': _("Longitude"), + 'value_x': value_x, + 'name': name, + 'val': val, + 'initialized': initialized, + 'extra_js': "\n".join(extra_js), + 'isvalue': bool(value), + 'default_area': "true" if default_area + else "false", + }) % + (settings.STATIC_URL, + settings.CHIMERE_EPSG_DISPLAY_PROJECTION, + settings.CHIMERE_EPSG_PROJECTION, + "[{}, {}]".format(settings.CHIMERE_DEFAULT_CENTER[0], + settings.CHIMERE_DEFAULT_CENTER[1]), + settings.CHIMERE_DEFAULT_ZOOM, + settings.STATIC_URL, + ", ".join(map_layers), + zoom + ) + ) + + +class HiddenPointChooserWidget(PointChooserWidget): + """ + OpenLayers doesn't initialize well on an hidden field so specific JS + must be loaded. + """ + def render(self, *args, **kwargs): + kwargs['initialized'] = False + return super(HiddenPointChooserWidget, self).render(*args, **kwargs) + + +class PointField(models.PointField): + ''' + Set the widget for the form field + ''' + def __init__(self, *args, **kwargs): + self.widget = kwargs.pop('widget') if 'widget' in kwargs \ + else PointChooserWidget + return super(PointField, self).__init__(*args, **kwargs) + + def formfield(self, **keys): + defaults = {'widget': self.widget} + keys.update(defaults) + return super(PointField, self).formfield(**keys) + + def clean(self, value, instance=None): + if len(value) != 2 and self.required: + raise forms.ValidationError(_("Invalid point")) + return value + + +class RouteChooserWidget(forms.TextInput): + """ + Manage the edition of route on a map + """ + class Media: + css = { + "all": settings.MAP_CSS_URLS + + ["%schimere/css/forms.css" % settings.STATIC_URL] + } + js = settings.MAP_JS_URLS + list(settings.JQUERY_JS_URLS) + \ + ["%schimere/js/jquery.chimere.js" % settings.STATIC_URL, + "%schimere/js/edit_route_map.js" % settings.STATIC_URL, + "%schimere/js/base.js" % settings.STATIC_URL] + + def render(self, name, value, attrs=None, area_name='', routefile_id=None): + ''' + Render a map and latitude, longitude information field + ''' + tpl = getMapJS(area_name) + map_layers, default_area = get_map_layers(area_name) + extra_js = [extra_js for n, js, default, extra_js in map_layers + if extra_js] + map_layers = [js for nm, js, default, ext_js in map_layers] + js = u""" + var resolutions; + var zoomOffset; + + var extra_url = "%s"; + epsg_display_projection = 'EPSG:%s'; + var EPSG_DISPLAY_PROJECTION = epsg_display_projection; + epsg_projection = 'EPSG:%s'; + var EPSG_PROJECTION = epsg_projection; + centerLonLat = ol.proj.transform([%f %f], + epsg_display_projection, epsg_projection); + var CENTER_LONLAT = centerLonLat; + var DEFAULT_ZOOM = %s; + var chimere_init_options = {}; + %s + chimere_init_options["map_layers"] = [%s]; + chimere_init_options['dynamic_categories'] = false; + chimere_init_options['edition'] = true; + chimere_init_options['edition_type_is_route'] = true; + chimere_init_options["checked_categories"] = []; + """ % (reverse("chimere:index"), + settings.CHIMERE_EPSG_DISPLAY_PROJECTION, + settings.CHIMERE_EPSG_PROJECTION, + settings.CHIMERE_DEFAULT_CENTER[0], + settings.CHIMERE_DEFAULT_CENTER[1], + settings.CHIMERE_DEFAULT_ZOOM, + u"\n".join(extra_js), + u", ".join(map_layers)) + if default_area: + js += u"chimere_init_options['selected_map_layer'] = %d;\n" % \ + default_area + tpl = u"<script type='text/javascript'><!--\n"\ + u"%s// !--></script>\n" % js + # TODO: manage area + help_create = '' + if not value: + help_create = u"<h3>%s</h3>\n"\ + u"<p>%s</p>\n"\ + u"<p>%s</p>\n"\ + u"<p>%s</p>\n"\ + u"<p>%s</p>\n"\ + u"<p>%s</p>\n" % ( + _(u"Creation mode"), + _(u"To start drawing the route click on the toggle " + u"button: \"Draw\"."), + _(u"Then click on the map to begin the drawing."), + _(u"You can add points by clicking again."), + _(u"To finish the drawing double click. When the drawing " + u"is finished you can edit it."), + _(u"While creating to undo a drawing click again on the " + u"toggle button \"Stop drawing\".")) + help_modify = u"<h3>%s</h3>\n"\ + u"<p>%s</p>\n"\ + u"<p>%s</p>\n"\ + u"<p>%s</p>\n" % ( + _(u"Modification mode"), + _(u"To move a point click on it and drag it to the desired " + u"position."), + _(u"To delete a point move the mouse cursor over it and press " + u"the \"d\" or \"Del\" key."), + _(u"To add a point click in the middle of a segment and drag " + u"the new point to the desired position")) + if not value: + # upload a file + tpl += u"<script type='text/javascript'><!--\n"\ + u" var error_msg = \"%s\";"\ + u"// --></script>" % \ + _(u"Give a name and set category before uploading a file.") + tpl += u'<div id="upload"><a href="#" class="upload-button" '\ + u'onclick="uploadFile(error_msg);return false;">%s</a></div>' \ + % (_(u"Upload a route file (GPX or KML)")) + tpl += u"\n<p id='draw-or'>%s</p>\n" % _(u"or") + tpl += u"<div id='draw-label'><div id='draw-toggle-off' "\ + u"onclick='toggleDraw();'>\n"\ + u"<a href='#' onclick='return false;'>%s</a></div>"\ + u"</div>\n"\ + u"<hr class='spacer'/>" % (_(u"Start \"hand\" drawing")) + style = '' + if value: + style = " style='display:block'" + tpl += u"\n<div class='help-route' id='help-route-modify'%s>%s</div>"\ + u"\n<hr class='spacer'/>\n"\ + u"<input type='hidden' name='%s' id='id_%s' value=\"%s\"/>\n"\ + u"<input type='hidden' name='associated_file_id' "\ + u"id='id_associated_file_id' value=\"%s\"/>\n" % ( + style, help_modify, name, name, value, routefile_id) + if value: + tpl += u"\n<div id='map_edit'></div>" + else: + tpl += "\n<div class='help-route' id='help-route-create'>%s</div>"\ + % help_create + tpl += \ + u"\n<div id='layerSwitcher'></div>\n<div id='map_edit'>\n"\ + u" <div class='map_button'>\n"\ + u" <a href='#' id='button-move-map' class='toggle-button "\ + u"toggle-button-active' "\ + u"onclick='toggleDrawOff();return false;'>%s</a>\n"\ + u"<a href='#' id='button-draw-map' class='toggle-button "\ + u"toggle-button-inactive' "\ + u"onclick='toggleDrawOn();return false;'>%s</a></div>\n"\ + u" </div>" % (_(u"Move on the map"), _(u"Draw")) + tpl += u"<script type='text/javascript'><!--\n" + if not value: + tpl += u"jQuery('#map_edit').hide();" + if value: + tpl += u"jQuery('#map_edit').chimere(chimere_init_options);\n" + val = value + if type(value) == unicode: + try: + val = fromstr(value) + except: + pass + if hasattr(val, 'json'): + tpl += u"\nvar geometry='%s';\n"\ + u"jQuery('#map_edit').chimere('initFeature', geometry);"\ + % val.json + tpl += u"\n// --></script>\n" + return mark_safe(tpl) + + +class RouteField(models.LineStringField): + ''' + Set the widget for the form field + ''' + def formfield(self, **keys): + defaults = {'widget': RouteChooserWidget} + keys.update(defaults) + return super(RouteField, self).formfield(**keys) + + +class AreaWidget(forms.TextInput): + """ + Manage the edition of an area on the map + """ + class Media: + css = { + "all": settings.MAP_CSS_URLS + + ["%schimere/css/forms.css" % settings.STATIC_URL] + } + js = settings.MAP_JS_URLS + [ + "%schimere/js/edit_area.js" % settings.STATIC_URL, + "%schimere/js/base.js" % settings.STATIC_URL] + + def get_bounding_box_from_value(self, value): + ''' + Return upper left lat/lon and lower lat/lon from the input value + ''' + upper_left_lat, upper_left_lon = 0, 0 + lower_right_lat, lower_right_lon = 0, 0 + if not value: + return upper_left_lat, upper_left_lon, lower_right_lat, \ + lower_right_lon + if len(value) == 2: + upper_left = value[0] + lower_right = value[1] + if hasattr(upper_left, 'x') and hasattr(upper_left, 'y'): + upper_left_lon, upper_left_lat = upper_left.x, upper_left.y + elif len(upper_left) == 2: + try: + upper_left_lon = float(upper_left[0]) + upper_left_lat = float(upper_left[1]) + except ValueError: + pass + if hasattr(lower_right, 'x') and hasattr(lower_right, 'y'): + lower_right_lon, lower_right_lat = lower_right.x, \ + lower_right.y + elif len(lower_right) == 2: + lower_right_lon, lower_right_lat = lower_right + try: + lower_right_lon = float(lower_right[0]) + lower_right_lat = float(lower_right[1]) + except ValueError: + pass + return upper_left_lat, upper_left_lon, lower_right_lat, lower_right_lon + + def render(self, name, value, attrs=None, initialized=True): + """ + Render a map + """ + upper_left_lat, upper_left_lon, lower_right_lat, lower_right_lon = \ + self.get_bounding_box_from_value(value) + tpl = getMapJS() + tpl += u"</div>\n"\ + u"<input type='hidden' name='upper_left_lat' id='upper_left_lat' "\ + u"value='%f'/>\n"\ + u"<input type='hidden' name='upper_left_lon' id='upper_left_lon' "\ + u"value='%f'/>\n"\ + u"<input type='hidden' name='lower_right_lat' id='lower_right_lat'"\ + u" value='%f'/>\n"\ + u"<input type='hidden' name='lower_right_lon' id='lower_right_lon'"\ + u" value='%f'/>\n" % ( + upper_left_lat, upper_left_lon, lower_right_lat, + lower_right_lon) + help_msg = _(u"Hold CTRL, click and drag to select area on the map") + tpl += u"<p class='help-osm'>%s</p>\n" % help_msg + tpl += u"<script type='text/javascript'>\n" + tpl += u"function init_map_form (){\ninit('map_edit_area');\n" + if value: + tpl += u"extent = ol.proj.transformExtent([%f, %f, %f, %f], "\ + u"epsg_display_projection, epsg_projection);\n"\ + u"initArea(extent);\n" % ( + upper_left_lon, upper_left_lat, lower_right_lon, + lower_right_lat) + tpl += u"}\n" + if initialized: + tpl += u"$(document).ready(function($) {init_map_form()});\n" + tpl += u"</script>\n" + tpl += u"<div id='map_edit_area'>\n" + return mark_safe(tpl) + + def value_from_datadict(self, data, files, name): + """ + Return the appropriate values + """ + values = [] + for keys in (('upper_left_lon', 'upper_left_lat',), + ('lower_right_lon', 'lower_right_lat')): + value = [] + for key in keys: + val = data.get(key, None) + if not val: + return [] + value.append(val) + values.append(value) + return values + + +class PolygonChooserWidget(forms.TextInput): + """ + Manage the edition of polygon on a map + """ + class Media: + css = { + "all": settings.MAP_CSS_URLS + + ["%schimere/css/forms.css" % settings.STATIC_URL] + } + js = settings.MAP_JS_URLS + list(settings.JQUERY_JS_URLS) + \ + ["%schimere/js/jquery.chimere.js" % settings.STATIC_URL] + + def render(self, name, value, attrs=None, area_name='', initialized=True): + val = '' + if value: + val = str(value) + map_layers, default_area, zoom = get_map_layers(area_name, + get_area_zoom=True) + extra_js = [extra_js for n, js, default, extra_js in map_layers + if extra_js] + map_layers = [js for n, js, default, ext_js in map_layers + if 'OpenLayers' not in js] + tpl = render_to_string( + 'chimere/blocks/polygon_edit.html', + {'name': name, 'val': val, 'initialized': initialized, + 'isvalue': bool(value), + 'default_area': "true" if default_area else "false", + 'value': value + } + ) + return mark_safe(tpl.format( + static_url=settings.STATIC_URL, + display_projection=settings.CHIMERE_EPSG_DISPLAY_PROJECTION, + projection=settings.CHIMERE_EPSG_PROJECTION, + center=list(settings.CHIMERE_DEFAULT_CENTER), + zoom=zoom, + map_layers=u", ".join(map_layers), + extra_js=u"\n".join(extra_js), + )) + + +class PolygonField(models.PolygonField): + ''' + Set the widget for the form field + ''' + def formfield(self, **keys): + defaults = {'widget': PolygonChooserWidget} + keys.update(defaults) + return super(PolygonField, self).formfield(**keys) + +RE_XAPI = re.compile( + '(node|way)\[(.*=.*)\]\[bbox=' + '(-*[0-9]*.[0-9]*,-*[0-9]*.[0-9]*,-*[0-9]*.[0-9]*,-*[0-9]*.[0-9]*)\]') + + +class ImportFiltrWidget(AreaWidget): + """ + Manage the edition of the import source field + """ + class Media: + css = { + "all": settings.MAP_CSS_URLS + + ["%schimere/css/forms.css" % settings.STATIC_URL] + } + js = settings.MAP_JS_URLS + [ + "%schimere/js/edit_area.js" % settings.STATIC_URL, + "%schimere/js/base.js" % settings.STATIC_URL] + + def render(self, name, value, attrs=None): + """ + Render a map + """ + tpl = super(ImportFiltrWidget, self).render(name, value, attrs, + initialized=False) + tpl += u"</div><hr class='spacer'/>" + vals = {'lbl': _(u"Type:"), 'name': name, 'node': _(u"Node"), + 'way': _(u"Way")} + vals['way_selected'] = ' checked="checked"'\ + if self.xapi_type == 'way' else '' + vals['node_selected'] = ' checked="checked"'\ + if self.xapi_type == 'node' else '' + tpl += u"<div class='input-osm'><label>%(lbl)s</label>"\ + u"<input type='radio' name='id_%(name)s_type' "\ + u"id='id_%(name)s_node' value='node'%(node_selected)s/> "\ + u"<label for='id_%(name)s_node'>"\ + u"%(node)s</label> <input type='radio' name='id_%(name)s_type' "\ + u"id='id_%(name)s_way' value='way'%(way_selected)s/> <label "\ + u"for='id_%(name)s_way'>%(way)s</label></div>" % vals + help_msg = _( + u"Enter an OSM \"tag=value\" string such as " + u"\"amenity=pub\". A list of common tag is available " + u"<a href='https://wiki.openstreetmap.org/wiki/Map_Features' " + u" target='_blank'>here</a>.") + tpl += u"<p class='help-osm'>%s</p>\n" % help_msg + tpl += u"<div class='input-osm'><label for='id_%s_tag'>%s</label>"\ + u"<input type='text' id='id_%s_tag' value=\"%s\"/></div>" % ( + name, _(u"Tag:"), name, self.xapi_tag) + tpl += u"<script type='text/javascript'>\n" + tpl += u"var default_xapi='%s';" % settings.CHIMERE_XAPI_URL + tpl += u'var msg_missing_area = "%s";' % \ + _(u"You have to select an area.") + tpl += u'var msg_missing_type = "%s";' % \ + _(u"You have to select a type.") + tpl += u'var msg_missing_filtr = "%s";' % \ + _(u"You have to insert a filter tag.") + tpl += u"</script>\n" + help_msg = _(u"If you change the above form don't forget to refresh " + u"before submit!") + tpl += u"<p class='help-osm errornote'>%s</p>\n" % help_msg + help_msg = _(u"You can put a Folder name of the KML file to filter on " + u"it.") + tpl += u"<p class='help-kml'>%s</p>\n" % help_msg + if not value: + value = '' + tpl += u"<div><textarea id='id_%s' name='id_%s' "\ + u">%s</textarea> <input type='button' id='id_refresh_%s' "\ + u"value='%s' class='input-osm'/>" % (name, name, value, name, + _(u"Refresh")) + return mark_safe(tpl) + + def value_from_datadict(self, data, files, name): + """ + Return the appropriate values + """ + return data.get('id_' + name, None) + + def get_bounding_box_from_value(self, value): + ''' + Return upper left lat/lon, lower lat/lon from the input value. + Get also xapi type and xapi tag + ''' + upper_left_lat, upper_left_lon = 0, 0 + lower_right_lat, lower_right_lon = 0, 0 + self.xapi_type, self.xapi_tag, self.bounding_box = None, '', None + if not value: + return upper_left_lat, upper_left_lon, lower_right_lat, \ + lower_right_lon + xapi_m = RE_XAPI.match(value) + if not xapi_m: + return upper_left_lat, upper_left_lon, lower_right_lat, \ + lower_right_lon + # as the regexp pass, we could be pretty confident + self.xapi_type, self.xapi_tag, self.bounding_box = xapi_m.groups() + upper_left_lon, lower_right_lat, lower_right_lon, upper_left_lat = \ + self.bounding_box.split(',') + return float(upper_left_lat), float(upper_left_lon), \ + float(lower_right_lat), float(lower_right_lon) + + +class AreaField(forms.MultiValueField): + ''' + Set the widget for the form field + ''' + widget = AreaWidget + + def compress(self, data_list): + if not data_list: + return None + return data_list + + +class MultiSelectWidget(forms.SelectMultiple): + class Media: + css = {'all': list(settings.JQUERY_CSS_URLS) + [ + settings.STATIC_URL + 'bsmSelect/css/jquery.bsmselect.css', + settings.STATIC_URL + 'bsmSelect/css/jquery.bsmselect.custom.css', + ] + } + js = list(settings.JQUERY_JS_URLS) + [ + settings.STATIC_URL + 'bsmSelect/js/jquery.bsmselect.js', + settings.STATIC_URL + + 'bsmSelect/js/jquery.bsmselect.compatibility.js', + ] + + def render(self, name, value, attrs=None): + rendered = super(MultiSelectWidget, self).render(name, value, attrs) + rendered += u"<hr class='spacer'/><script type='text/javascript'>\n"\ + u"$.bsmSelect.conf['title'] = \"%(title)s\";\n"\ + u"$(\"#id_%(name)s\").bsmSelect({\n"\ + u" removeLabel: '<strong>X</strong>',\n"\ + u" containerClass: 'bsmContainer',\n"\ + u" listClass: 'bsmList-custom',\n"\ + u" listItemClass: 'bsmListItem-custom',\n"\ + u" listItemLabelClass: 'bsmListItemLabel-custom',\n"\ + u" removeClass: 'bsmListItemRemove-custom'\n"\ + u"});\n"\ + u"</script>\n" % {'name': name, 'title': _("Select...")} + return mark_safe(rendered) + + +class SelectMultipleField(models.ManyToManyField): + ''' + Set the widget for the category field + ''' + def formfield(self, **keys): + self.help_text = "" + defaults = {'widget': MultiSelectWidget} + keys.update(defaults) + return super(SelectMultipleField, self).formfield(**keys) + +from south.modelsinspector import add_introspection_rules +add_introspection_rules([], ["^chimere\.widgets\.PointField"]) +add_introspection_rules([], ["^chimere\.widgets\.SelectMultipleField"]) +add_introspection_rules([], ["^chimere\.widgets\.RouteField"]) +add_introspection_rules([], ["^chimere\.widgets\.PolygonField"]) |
