#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2008-2013 Étienne Loks # 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 . # See the file COPYING for details. """ Extra widgets and fields """ from django import conf 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 from django.utils.encoding import force_unicode from django.utils.html import conditional_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(map_name=''): '''Variable initialization for drawing the map ''' # projection, center and bounds definitions js = u"var epsg_display_projection = new OpenLayers.Projection('EPSG:%d')"\ u";\n" % settings.CHIMERE_EPSG_DISPLAY_PROJECTION js += u"OpenLayers.ImgPath = '%schimere/img/';\n" % settings.STATIC_URL js += u"var epsg_projection = new OpenLayers.Projection('EPSG:%d');\n" % \ settings.CHIMERE_EPSG_PROJECTION js += u"var centerLonLat = new OpenLayers.LonLat(%f,"\ u"%f).transform(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 map_name: js += u"var map_name='%s';\n" % map_name js = u"\n" % js return js def get_map_layers(map_name='', force_default=False): from chimere.models import Map map = None if map_name: try: map = Map.objects.get(urn=map_name) except ObjectDoesNotExist: pass else: try: map = Map.objects.get(default=True) except ObjectDoesNotExist: pass map_layers, default = [], None if not force_default and map and map.layers.count(): map_layers = [[layer.name, layer.layer_code, False] for layer in map.layers.order_by('maplayers__order').all()] def_layer = map.layers.filter(maplayers__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 not force_default and settings.CHIMERE_DEFAULT_MAP_LAYER: map_layers = [(_(u"Default layer"), settings.CHIMERE_DEFAULT_MAP_LAYER, True)] else: map_layers = [(u"Mapnik", "new OpenLayers.Layer.OSM.Mapnik('Mapnik')", True)] return map_layers, default 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\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 %s' % (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 = "
\n" % kwargs['attrs']['id'] rendered += super(ButtonSelectWidget, self).render(*args, **kwargs) rendered += u"\n\n
\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,] class TextareaWidgetBase(forms.Textarea): """ Manage the edition of a text using TinyMCE """ def render(self, *args, **kwargs): 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 = ["%stiny_mce.js" % settings.TINYMCE_URL] def render(self, *args, **kwargs): 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""" """ return mark_safe(rendered) class TextareaWidget(TextareaWidgetBase): """ Manage the edition of a text using TinyMCE """ class Media: js = ["%stiny_mce.js" % settings.TINYMCE_URL, "%schimere/js/textareas.js" % settings.STATIC_URL,] class TextareaAdminWidget(TextareaWidgetBase): class Media: js = ["%stiny_mce.js" % settings.TINYMCE_URL, "%schimere/js/textareas_admin.js" % settings.STATIC_URL,] 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\n" % kwargs['attrs']['id'] return mark_safe(rendered) class NominatimWidget(forms.TextInput): class Media: js = ["%schimere/js/nominatim-widget.js" % settings.STATIC_URL] def render(self, name, value=None, attrs=None, map_name=''): return mark_safe( render_to_string('chimere/blocks/nominatim_widget.html', {'id':name, 'nominatim_url':settings.NOMINATIM_URL} )) class PointChooserWidget(forms.TextInput): """ Manage the edition of point on a map """ class Media: css = { "all": settings.MAP_CSS_URLS[settings.CHIMERE_EDIT_RENDERER] + \ ["%schimere/css/forms.css" % settings.STATIC_URL,] } js = list(settings.JQUERY_JS_URLS) + \ settings.MAP_JS_URLS[settings.CHIMERE_EDIT_RENDERER] def render(self, name, value, attrs=None, map_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 force = False if settings.CHIMERE_EDIT_RENDERER != settings.CHIMERE_VIEW_RENDERER: force = True map_layers, default_map = get_map_layers(map_name, force_default=force) map_layers = [js for n, js, default in map_layers] #TODO: manage maps 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, 'isvalue': bool(value), 'default_map': "true" if default_map else "false", }) % \ (settings.STATIC_URL, settings.CHIMERE_EPSG_DISPLAY_PROJECTION, settings.CHIMERE_EPSG_PROJECTION, settings.CHIMERE_DEFAULT_CENTER, settings.CHIMERE_DEFAULT_ZOOM, settings.STATIC_URL, ", ".join(map_layers) ) ) 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 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[settings.CHIMERE_EDIT_RENDERER] + \ ["%schimere/css/forms.css" % settings.STATIC_URL,] } js = list(settings.JQUERY_JS_URLS) + \ settings.MAP_JS_URLS[settings.CHIMERE_EDIT_RENDERER] + \ ["%schimere/js/edit_route_map.js" % settings.STATIC_URL, "%schimere/js/base.js" % settings.STATIC_URL,] def render(self, name, value, attrs=None, map_name='', routefile_id=None): ''' Render a map and latitude, longitude information field ''' tpl = getMapJS(map_name) map_layers, default_map = get_map_layers(map_name) map_layers = [js for nm, js, default in map_layers] js = """ var resolutions; var zoomOffset; var extra_url = "%s"; OpenLayers.ImgPath = '%schimere/img/'; var EPSG_DISPLAY_PROJECTION = epsg_display_projection = new OpenLayers.Projection('EPSG:%s'); var EPSG_PROJECTION = epsg_projection = new OpenLayers.Projection('EPSG:%s'); var CENTER_LONLAT = centerLonLat = new OpenLayers.LonLat%s.transform(epsg_display_projection, epsg_projection); var DEFAULT_ZOOM = %s; var chimere_init_options = {}; 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.STATIC_URL, settings.CHIMERE_EPSG_DISPLAY_PROJECTION, settings.CHIMERE_EPSG_PROJECTION, settings.CHIMERE_DEFAULT_CENTER, settings.CHIMERE_DEFAULT_ZOOM, ", ".join(map_layers)) if default_map: js += "chimere_init_options['selected_map_layer'] = %d;\n" % \ default_map tpl = u"\n" % js #TODO: manage maps help_create = '' if not value: help_create = u"

%s

\n"\ u"

%s

\n"\ u"

%s

\n"\ u"

%s

\n"\ u"

%s

\n"\ u"

%s

\n" % (_(u"Creation mode"), _(u"To start drawing the route click on the toggle button: "\ u"\"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 is "\ u"finished you can edit it."), _(u"While creating to undo a drawing click again on the toggle "\ u"button \"Stop drawing\".")) help_modify = u"

%s

\n"\ u"

%s

\n"\ u"

%s

\n"\ u"

%s

\n" % (_(u"Modification mode"), _(u"To move a point click on it and drag it to the desired position."), _(u"To delete a point move the mouse cursor over it and press the "\ u"\"d\" or \"Del\" key."), _(u"To add a point click in the middle of a segment and drag the new "\ u"point to the desired position")) if not value: # upload a file tpl += u"" % ( _(u"Give a name and set category before uploading a file.")) tpl += u'' % ( _(u"Upload a route file (GPX or KML)")) tpl += u"\n

%s

\n" % _(u"or") tpl += u"
\n"\ u"%s
"\ u"
\n"\ u"
" % (_(u"Start \"hand\" drawing")) style = '' if value: style = " style='display:block'" tpl += u"\n
%s
"\ u"\n
\n"\ u"\n"\ u"\n" % ( style, help_modify, name, name, value, routefile_id) if value: tpl += u"\n
" else: tpl += "\n
%s
"\ % help_create tpl += u"\n
\n"\ u"
\n"\ u" %s"\ u"\n"\ u""\ u"%s
\n"\ u"
" % (_(u"Move on the map"), _(u"Draw")) tpl += u"\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 areaon the map """ class Media: css = { "all": settings.MAP_CSS_URLS[settings.CHIMERE_EDIT_RENDERER] + \ ["%schimere/css/forms.css" % settings.STATIC_URL,] } js = settings.MAP_JS_URLS[settings.CHIMERE_EDIT_RENDERER] + \ ["%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"\n"\ u"\n"\ u"\n"\ u"\n"\ u"\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"

%s

\n" % help_msg tpl += u"\n" tpl += u"
\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 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[settings.CHIMERE_EDIT_RENDERER] + \ ["%schimere/css/forms.css" % settings.STATIC_URL,] } js = settings.MAP_JS_URLS[settings.CHIMERE_EDIT_RENDERER] + \ ["%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"

" 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"
"\ u"
" % vals help_msg = _(u"Enter an OSM \"tag=value\" string such as "\ u"\"amenity=pub\". A list of common tag is available "\ u"here.") tpl += u"

%s

\n" % help_msg tpl += u"
"\ u"
" % ( name, _(u"Tag:"), name, self.xapi_tag) tpl += u"\n" help_msg = _(u"If you change the above form don't forget to refresh "\ u"before submit!") tpl += u"

%s

\n" % help_msg help_msg = _(u"You can put a Folder name of the KML file to filter on "\ u"it.") tpl += u"

%s

\n" % help_msg if not value: value = '' tpl += u"
" % (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"
\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"])