summaryrefslogtreecommitdiff
path: root/chimere/widgets.py
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@peacefrogs.net>2012-02-15 16:59:28 +0100
committerÉtienne Loks <etienne.loks@peacefrogs.net>2012-02-15 16:59:28 +0100
commitf88541bedcffdfaff485ef71287be88a58c745c2 (patch)
tree87e9fcd59da5d687d2954ae99d9f511df55058f2 /chimere/widgets.py
parent8ccdaf23128fbe563658ca0d9d74d2ffd831b68d (diff)
downloadChimère-f88541bedcffdfaff485ef71287be88a58c745c2.tar.bz2
Chimère-f88541bedcffdfaff485ef71287be88a58c745c2.zip
Large reorganization (refs #316), south migration script to new model names (refs #319)
Diffstat (limited to 'chimere/widgets.py')
-rw-r--r--chimere/widgets.py357
1 files changed, 357 insertions, 0 deletions
diff --git a/chimere/widgets.py b/chimere/widgets.py
new file mode 100644
index 0000000..ec63ea3
--- /dev/null
+++ b/chimere/widgets.py
@@ -0,0 +1,357 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008-2011 É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 django import forms
+from django.conf import settings
+from django.contrib.gis.db import models
+from django.contrib.gis.geos import fromstr
+from django.utils.safestring import mark_safe
+from django.utils.translation import ugettext as _
+
+URL_OSM_CSS = ["http://www.openlayers.org/api/theme/default/style.css"]
+URL_OSM_JS = [settings.MEDIA_URL+"OpenLayers.js",
+ "http://www.openstreetmap.org/openlayers/OpenStreetMap.js"]
+
+def getMapJS(area_name=''):
+ '''Variable initialization for drawing the map
+ '''
+ # projection, center and bounds definitions
+ js = u"var epsg_display_projection = new OpenLayers.Projection('EPSG:%d')\
+;\n" % settings.EPSG_DISPLAY_PROJECTION
+ js += u"var epsg_projection = new OpenLayers.Projection('EPSG:%d');\n" % \
+ settings.EPSG_PROJECTION
+ js += u"var centerLonLat = new OpenLayers.LonLat(%f,\
+%f).transform(epsg_display_projection, epsg_projection);\n" % \
+ settings.DEFAULT_CENTER
+ js += u"var media_path = '%s';\n" % settings.MEDIA_URL
+ js += u"var map_layer = %s;\n" % settings.MAP_LAYER
+ js += u"var restricted_extent;\n"
+
+ if area_name:
+ js += u"var area_name='%s';\n" % area_name
+ if settings.RESTRICTED_EXTENT:
+ restricted_extent_str = [str(coord) \
+ for coord in settings.RESTRICTED_EXTENT]
+ js += u"restricted_extent = new OpenLayers.Bounds(%s);\n" %\
+ ", ".join(restricted_extent_str)
+ js = u"""<script type="text/javascript"><!--
+%s// !--></script>
+""" % js
+ return js
+
+class TextareaWidget(forms.Textarea):
+ """
+ Manage the edition of a text using TinyMCE
+ """
+ class Media:
+ js = ["%stiny_mce.js" % settings.TINYMCE_URL,
+ "%stextareas.js" % settings.MEDIA_URL,]
+
+class PointChooserWidget(forms.TextInput):
+ """
+ Manage the edition of point on a map
+ """
+ class Media:
+ css = {
+ "all": URL_OSM_CSS + ["%sforms.css" % settings.MEDIA_URL,]
+ }
+ js = URL_OSM_JS + ["%sedit_map.js" % settings.MEDIA_URL,
+ "%sbase.js" % settings.MEDIA_URL,]
+
+ def render(self, name, value, attrs=None, area_name=''):
+ '''
+ Render a map and latitude, longitude information field
+ '''
+ val = '0'
+ 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
+ tpl = getMapJS(area_name)
+ tpl += u'<script src="%sedit_map.js"></script>\n' % settings.MEDIA_URL
+ tpl += u"""<div id='map_edit'></div>
+<div id='live_lonlat'>
+<p><label for='live_latitude'>%s</label>\
+<input type='texte' name='live_latitude' id='live_latitude' size='8' \
+disabled='true' value='%f'/></p>
+<p><label for='live_longitude'>%s</label><input type='texte' \
+name='live_longitude' id='live_longitude' size='8' disabled='true' \
+value='%f'/></p>
+</div>
+<input type='hidden' name='%s' id='id_%s' value="%s"/>
+""" % (_("Latitude"), value_y, _("Longitude"), value_x, name, name, val)
+ tpl += "<script type='text/javascript'><!--\n"
+ tpl += "init();\n"
+ if value:
+ tpl += '''var mylonlat = new OpenLayers.LonLat(%f,%f);
+putMarker(mylonlat.transform(epsg_display_projection,
+ map.getProjectionObject()).clone(), true);
+''' % (value_x, value_y)
+ tpl += """// --></script>
+<hr class='spacer'/>
+"""
+ return mark_safe(tpl)
+
+class PointField(models.PointField):
+ '''
+ Set the widget for the form field
+ '''
+ def formfield(self, **keys):
+ defaults = {'widget': PointChooserWidget}
+ 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": URL_OSM_CSS + ["%sforms.css" % settings.MEDIA_URL,]
+ }
+ js = ["%sedit_route_map.js" % settings.MEDIA_URL,
+ "%sbase.js" % settings.MEDIA_URL,] + URL_OSM_JS
+
+ def render(self, name, value, attrs=None, area_name='', routefile_id=None):
+ '''
+ Render a map and latitude, longitude information field
+ '''
+ tpl = getMapJS(area_name)
+ help_create = ''
+ if not value:
+ help_create = """<h3>%s</h3>
+<p>%s</p>
+<p>%s</p>
+<p>%s</p>
+<p>%s</p>
+<p>%s</p>""" % (_(u"Creation mode"),
+_(u"To start drawing the route click on the toggle 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 is finished you can \
+edit it."),
+_(u"While creating to undo a drawing click again on the toggle button \"Stop \
+drawing\"."))
+ help_modify = """<h3>%s</h3>
+<p>%s</p>
+<p>%s</p>
+<p>%s</p>""" % (_(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 \"d\" or \
+\"Del\" key."),
+_(u"To add a point click in the middle of a segment and drag the new point to \
+the desired position"))
+ tpl += u'<script src="%sedit_route_map.js"></script>\n' % \
+ settings.MEDIA_URL
+ if not value:
+ # upload a file
+ tpl += u"""<script type='text/javascript'><!--
+ var error_msg = "%s";
+// --></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' \
+onclick='toggleDraw();'>
+<a href='#' onclick='return false;'>%s</a></div>
+</div>
+<hr class='spacer'/>""" % (_(u"Start \"hand\" drawing"))
+ if value:
+ tpl += """
+<div id='map_edit'></div>"""
+ else:
+ tpl += """
+<div id='map_edit'>
+ <div class='map_button'>
+ <a href='#' id='button-move-map' class='toggle-button toggle-button-active' onclick='toggleDrawOff();return false;'>%s</a>
+ <a href='#' id='button-draw-map' class='toggle-button toggle-button-inactive' onclick='toggleDrawOn();return false;'>%s</a></div>
+ </div>""" % (_(u"Move on the map"), _(u"Draw"))
+ tpl += '''
+<div class='help-route' id='help-route-create'>%s</div>''' % help_create
+ style = ''
+ if value:
+ style = " style='display:block'"
+ tpl += """
+<div class='help-route' id='help-route-modify'%s>%s</div>
+<hr class='spacer'/>
+<input type='hidden' name='%s' id='id_%s' value="%s"/>
+<input type='hidden' name='associated_file_id' id='id_associated_file_id' \
+value="%s"/>
+""" % (style, help_modify, name, name, value, routefile_id)
+ tpl += "<script type='text/javascript'><!--\n"
+ if not value:
+ tpl += "document.getElementById('map_edit').style.display = 'None';"
+ if value:
+ tpl += "init();\n"
+ val = value
+ if type(value) == unicode:
+ try:
+ val = fromstr(value)
+ except:
+ pass
+ if hasattr(val, 'json'):
+ tpl += """
+var geometry='%s';
+initFeature(geometry);""" % val.json
+ tpl += """
+// --></script>
+"""
+ 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": URL_OSM_CSS + ["%sforms.css" % settings.MEDIA_URL,]
+ }
+ js = URL_OSM_JS + ["%sedit_area.js" % settings.MEDIA_URL,
+ "%sbase.js" % settings.MEDIA_URL,]
+
+ def render(self, name, value, attrs=None):
+ """
+ Render a map
+ """
+ upper_left_lat, upper_left_lon = 0, 0
+ lower_right_lat, lower_right_lon = 0, 0
+ if value:
+ 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
+ if hasattr(lower_right, 'x') and hasattr(lower_right, 'y'):
+ lower_right_lon, lower_right_lat = lower_right.x, \
+ lower_right.y
+ tpl = getMapJS()
+ tpl += u"""<div id='map_edit'></div>
+<input type='hidden' name='upper_left_lat' id='upper_left_lat' value='%f'/>
+<input type='hidden' name='upper_left_lon' id='upper_left_lon' value='%f'/>
+<input type='hidden' name='lower_right_lat' id='lower_right_lat' value='%f'/>
+<input type='hidden' name='lower_right_lon' id='lower_right_lon' value='%f'/>
+""" % (upper_left_lat, upper_left_lon, lower_right_lat, lower_right_lon)
+ tpl += """<script type='text/javascript'><!--
+init();"""
+ if value:
+ tpl += """var extent = new OpenLayers.Bounds(%f, %f, %f, %f);
+extent.transform(epsg_display_projection, epsg_projection);
+map.zoomToExtent(extent, true);""" % (upper_left_lon, upper_left_lat,
+ lower_right_lon, lower_right_lat)
+ tpl += """// --></script>
+<hr class='spacer'/>
+"""
+ 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 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': (
+ settings.MEDIA_URL + 'jquery/bsmSelect/css/jquery.bsmselect.css',
+ settings.MEDIA_URL + 'jquery/css/jquery.bsmselect.custom.css',
+ )
+ }
+ js = (
+ settings.MEDIA_URL + 'jquery/bsmSelect/js/jquery.bsmselect.js',
+ settings.MEDIA_URL + 'jquery/bsmSelect/js/jquery.bsmselect.compatibility.js',
+ )
+
+ def render(self, name, value, attrs=None):
+ rendered = super(MultiSelectWidget, self).render(name, value, attrs)
+ return mark_safe(rendered + u'''<hr class='spacer'/><script type="text/javascript">
+$.bsmSelect.conf['title'] = "%(title)s";
+$("#id_%(name)s").bsmSelect({
+ removeLabel: '<strong>X</strong>',
+ containerClass: 'bsmContainer',
+ listClass: 'bsmList-custom',
+ listItemClass: 'bsmListItem-custom',
+ listItemLabelClass: 'bsmListItemLabel-custom',
+ removeClass: 'bsmListItemRemove-custom'
+});
+</script>''' % {'name':name, 'title':_("Select...")})
+
+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"])