diff options
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 |
commit | f88541bedcffdfaff485ef71287be88a58c745c2 (patch) | |
tree | 87e9fcd59da5d687d2954ae99d9f511df55058f2 /chimere/widgets.py | |
parent | 8ccdaf23128fbe563658ca0d9d74d2ffd831b68d (diff) | |
download | Chimè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.py | 357 |
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"]) |