diff options
| author | etienne <etienne@9215b0d5-fb2c-4bbd-8d3e-bd2e9090e864> | 2008-10-22 22:39:53 +0000 |
|---|---|---|
| committer | etienne <etienne@9215b0d5-fb2c-4bbd-8d3e-bd2e9090e864> | 2008-10-22 22:39:53 +0000 |
| commit | 030ed86130fabc78695355021b5a0586df3e77b9 (patch) | |
| tree | bd06e4842c67577829cd433d7621bd22dac12776 /main | |
| download | Chimère-030ed86130fabc78695355021b5a0586df3e77b9.tar.bz2 Chimère-030ed86130fabc78695355021b5a0586df3e77b9.zip | |
git-svn-id: http://www.peacefrogs.net/svn/chimere/trunk@1 9215b0d5-fb2c-4bbd-8d3e-bd2e9090e864
Diffstat (limited to 'main')
| -rw-r--r-- | main/__init__.py | 0 | ||||
| -rw-r--r-- | main/actions.py | 15 | ||||
| -rw-r--r-- | main/admin.py | 35 | ||||
| -rw-r--r-- | main/forms.py | 77 | ||||
| -rw-r--r-- | main/models.py | 163 | ||||
| -rw-r--r-- | main/views.py | 106 | ||||
| -rw-r--r-- | main/widgets.py | 97 |
7 files changed, 493 insertions, 0 deletions
diff --git a/main/__init__.py b/main/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/main/__init__.py diff --git a/main/actions.py b/main/actions.py new file mode 100644 index 0000000..385b53c --- /dev/null +++ b/main/actions.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Actions available in the main interface +""" +from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth import models + +main_path = 'chimere/' + +class Action: + def __init__(self, id, path, label): + self.id, self.path, self.label = id, main_path + path, label + +actions = [Action('view', '', _('View')), Action('edit', 'edit', _('Add'))] diff --git a/main/admin.py b/main/admin.py new file mode 100644 index 0000000..0ec485f --- /dev/null +++ b/main/admin.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Settings for administration pages +""" + +from chimere.main.models import Category, Icon, SubCategory, Marker, \ + PropertyModel, Property +from chimere.main.widgets import PointChooserWidget +from chimere.main.forms import MarkerAdminForm +from django.contrib import admin + +class MarkerAdmin(admin.ModelAdmin): + """ + Specialized the Point field. + """ + search_fields = ("name",) + list_display = ('name', 'subcategory', 'status') + list_filter = ('status', 'subcategory') + form = MarkerAdminForm + + """ + def formfield_for_dbfield(self, db_field, **kwargs): + if db_field.name == 'point': + kwargs['widget'] = PointChooserWidget + return super(MarkerAdmin, self).formfield_for_dbfield(db_field, + **kwargs) +""" +# register of differents database fields +admin.site.register(Category) +admin.site.register(Icon) +admin.site.register(SubCategory) +admin.site.register(Marker, MarkerAdmin) +admin.site.register(PropertyModel) +admin.site.register(Property) diff --git a/main/forms.py b/main/forms.py new file mode 100644 index 0000000..2951c90 --- /dev/null +++ b/main/forms.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Forms +""" +from django import forms +from django.contrib.gis.db import models + +from chimere.main.models import Marker, PropertyModel, Property + +class MarkerAdminForm(forms.ModelForm): + """ + Main form for marker + """ + # declare properties + for property in PropertyModel.objects.filter(available=True): + exec('property_%d_%d = forms.CharField(label="%s", widget=forms.%s, \ +required=False)' % (property.order, property.id, property.name, + PropertyModel.TYPE_WIDGET[property.type])) + class Meta: + model = Marker + + def __init__(self, *args, **keys): + """ + Custom initialization method in order to manage properties + """ + if 'instance' in keys and keys['instance']: + instance = keys['instance'] + property_dct = {} + for pm in PropertyModel.objects.filter(available=True): + property = instance.getProperty(pm) + if property: + property_dct[pm.getNamedId()] = property.value + if 'initial' in keys: + keys['initial'].update(property_dct) + else: + keys['initial'] = property_dct + super(MarkerAdminForm, self).__init__(*args, **keys) + + def save(self, *args, **keys): + """ + Custom save method in order to manage associeted properties + """ + new_marker = super(MarkerAdminForm, self).save(*args, **keys) + if 'status' not in self.cleaned_data: + new_marker.status = 'S' + new_marker.save() + # save each property + for propertymodel in PropertyModel.objects.filter(available=True): + properties = Property.objects.filter(marker=new_marker, + propertymodel=propertymodel) + # new property + if not properties: + new_property = Property.objects.create(marker=new_marker, + propertymodel=propertymodel, + value=self.cleaned_data['property_%d_%d' % ( + propertymodel.order, propertymodel.id)]) + new_property.save() + else: + # in case of multiple edition as the same time delete arbitrary + # the others + if len(properties) > 1: + for property in properties[1:]: + property.delete() + property = properties[0] + property.value = self.cleaned_data['property_%d_%d' % ( + propertymodel.order, propertymodel.id)] + property.save() + return new_marker + +class MarkerForm(MarkerAdminForm): + """ + Form for the edit page + """ + class Meta: + model = Marker + exclude = ('status',) diff --git a/main/models.py b/main/models.py new file mode 100644 index 0000000..6d673d8 --- /dev/null +++ b/main/models.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Models description +""" +from django.utils.translation import ugettext_lazy as _ + +from django.contrib.gis.db import models +from django.contrib import admin + +from chimere import settings +from chimere.main.widgets import PointField + +class Category(models.Model): + """Category of Point Of Interest (POI) + """ + name = models.CharField(_("Name"), max_length=150) + available = models.BooleanField(_("Available")) + order = models.IntegerField(_("Order")) + def __unicode__(self): + return self.name + class Meta: + ordering = ["order"] + verbose_name = _("Category") + +class Icon(models.Model): + '''Icon + ''' + name = models.CharField(_("Name"), max_length=150) + image = models.ImageField(_("Image"), upload_to='icons') + def __unicode__(self): + return self.name + class Meta: + verbose_name = _("Icon") + +class SubCategory(models.Model): + '''Sub-category + ''' + category = models.ForeignKey(Category, verbose_name=_("Category")) + name = models.CharField(_("Name"), max_length=150) + available = models.BooleanField(_("Available")) + icon = models.ForeignKey(Icon, verbose_name=_("Icon")) + order = models.IntegerField(_("Order")) + def __unicode__(self): + return u"%s / %s" % (self.category.name, self.name) + class Meta: + ordering = ["category", "order"] + verbose_name = _("Subcategory") + + def getAvailable(): + '''Get list of tuples with first the category and second the associated + subcategories + ''' + sub_categories = {} + for sub_category in SubCategory.objects.filter(category__available=True, + available=True): + if sub_category.category not in sub_categories: + sub_categories[sub_category.category] = [] + sub_categories[sub_category.category].append(sub_category) + return [(category, sub_cats) for category, sub_cats \ + in sub_categories.items()] + getAvailable = staticmethod(getAvailable) + +class Marker(models.Model): + '''Marker for a POI + ''' + name = models.CharField(_("Name"), max_length=150) + subcategory = models.ForeignKey(SubCategory, verbose_name=_("Subcategory")) + point = PointField(_("Localisation")) + picture = models.ImageField(_("Image"), upload_to='upload', blank=True) + STATUS = (('S', _('Submited')), + ('A', _('Available')), + ('D', _('Disabled')),) + STATUS_DCT = {} + for key, label in STATUS: + STATUS_DCT[key] = label + status = models.CharField(_("Status"), max_length=1, choices=STATUS) + objects = models.GeoManager() + + def __unicode__(self): + return self.name + + class Meta: + ordering = ('subcategory__category', 'subcategory', 'status', 'name') + verbose_name = _("Point of interest") + + def getLatitude(self): + '''Return the latitude + ''' + return self.point.y + + def getLongitude(self): + '''Return the longitude + ''' + return self.point.x + + def getProperty(self, propertymodel, safe=None): + """Get the property of an associated property model. + If safe set to True, verify if the property is available + """ + if safe and not propertymodel.available: + return + try: + property = Property.objects.get(propertymodel=propertymodel, + marker=self) + except Property.DoesNotExist: + return + return property + + def getProperties(self): + """Get all the property availables + """ + properties = [] + for pm in PropertyModel.objects.filter(available=True): + property = self.getProperty(pm) + if property: + properties.append(property) + return properties + + def getGeoJSON(self): + '''Return a GeoJSON string + ''' + return """{"type":"Feature", "geometry":{"type":"Point", "crs": "EPSG:%(epsg)d", "coordinates":[%(longitude)s, %(latitude)s]}, "properties":{"pk": %(id)d, "name": "%(name)s", "icon_path":"%(icon_path)s"}}""" \ + % {'id':self.id, 'name':self.name, 'icon_path':self.subcategory.icon.image, +'latitude':self.getLatitude(), 'longitude':self.getLongitude(), +'epsg':settings.EPSG_PROJECTION} + +class PropertyModel(models.Model): + '''Model for a property + ''' + name = models.CharField(_("Name"), max_length=150) + order = models.IntegerField(_("Order")) + available = models.BooleanField(_("Available")) + TYPE = (('T', _('Text')), + ('L', _('Long text')), + ('P', _('Password'))) + TYPE_WIDGET = {'T':'TextInput', + 'L':'Textarea', + 'P':'PasswordInput'} + type = models.CharField(_("Type"), max_length=1, choices=TYPE) + def __unicode__(self): + return self.name + class Meta: + ordering = ('order',) + verbose_name = _("Property model") + + def getNamedId(self): + '''Get the name used as named id (easily sortable) + ''' + return 'property_%d_%d' % (self.order, self.id) + +class Property(models.Model): + '''Property for a POI + ''' + marker = models.ForeignKey(Marker, verbose_name=_("Point of interest")) + propertymodel = models.ForeignKey(PropertyModel, + verbose_name=_("Property model")) + value = models.CharField(_("Value"), max_length=1000) + def __unicode__(self): + return "%s : %s" % (str(self.propertymodel), self.value) + class Meta: + verbose_name = _("Property") + diff --git a/main/views.py b/main/views.py new file mode 100644 index 0000000..c0503ad --- /dev/null +++ b/main/views.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Views of the project +""" + +import datetime + +from django.shortcuts import render_to_response +from django.http import HttpResponseRedirect, HttpResponse +from django.core import serializers + +from chimere import settings +from chimere.main.actions import actions +from chimere.main.models import SubCategory, PropertyModel, Marker +from chimere.main.widgets import getMapJS, PointChooserWidget, URL_OSM_JS +from chimere.main.forms import MarkerForm + +def index(request): + """ + Main page + """ + subcategories = SubCategory.getAvailable() + # by default all subcategories are checked + for cat, sub_cats in subcategories: + for sub_category in sub_cats: + sub_category.selected = True + extra_js = "" + for url in URL_OSM_JS + ["%smain_map.js" % settings.MEDIA_URL]: + extra_js += '<script src="%s"></script>\n' % url + response_dct = {'actions':actions, 'action_selected':'view', + 'error_message':'', + 'sub_categories':subcategories, + 'extra_head':extra_js + getMapJS(), + 'media_path':settings.MEDIA_URL, + } + return render_to_response('main_map.html', response_dct) + +def edit(request): + """ + Edition page + """ + # If the form has been submited + if request.method == 'POST': + form = MarkerForm(request.POST, request.FILES) + # All validation rules pass + if form.is_valid(): + marker = form.save() + # set the submited status + marker.status = 'S' + marker.save() + return HttpResponseRedirect('/chimere/submited') + else: + # An unbound form + form = MarkerForm() + # get the « manualy » declared_fields. Ie: properties + declared_fields = form.declared_fields.keys() + response_dct = {'actions':actions, 'action_selected':'edit', + 'error_message':'', + 'media_path':settings.MEDIA_URL, + 'form':form, + 'extra_head':form.media, + 'sub_categories':SubCategory.getAvailable(), + 'point_widget':PointChooserWidget().render('point', None), + 'properties':declared_fields + } + # manualy populate the custom widget + if 'subcategory' in form.data and form.data['subcategory']: + response_dct['current_category'] = int(form.data['subcategory']) + return render_to_response('edit.html', response_dct) + +def submited(request): + """ + Successful submission page + """ + response_dct = {'actions':actions, 'action_selected':'edit', + 'media_path':settings.MEDIA_URL,} + return render_to_response('submited.html', response_dct) + +def getDetail(request, marker_id): + ''' + Get the detail for a marker + ''' + try: + marker = Marker.objects.filter(id=int(marker_id), status='A')[0] + except (ValueError, IndexError): + return HttpResponse('no results') + response_dct= {'media_path':settings.MEDIA_URL, 'marker':marker} + return render_to_response('detail.html', response_dct) + +def getMarkers(request, category_ids): + ''' + Get the JSON for a marker + ''' + try: + query = Marker.objects.filter(status='A') + query = query.extra(where=['subcategory_id IN (%s)' % \ + ",".join(category_ids.split('_'))]) + except: + return HttpResponse('no results') + markers = list(query) + if not markers: + return HttpResponse('no results') + data = '{"type": "FeatureCollection", "features":[%s]}' % \ + ",".join([marker.getGeoJSON() for marker in markers]) + return HttpResponse(data) diff --git a/main/widgets.py b/main/widgets.py new file mode 100644 index 0000000..4f78fdc --- /dev/null +++ b/main/widgets.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Extra widgets and fields +""" + +from django import forms +from django.utils.safestring import mark_safe +from django.utils.translation import ugettext_lazy as _ + +from chimere import settings +from django.contrib.gis.db import models + +URL_OSM_JS = ["http://www.openlayers.org/api/OpenLayers.js", + "http://openstreetmap.org/openlayers/OpenStreetMap.js"] + +def getMapJS(): + '''Variable initialization for drawing the map + ''' + # projection, center and bounds definitions + js = u"epsg_display_projection = new OpenLayers.Projection('EPSG:%d');\n" %\ + settings.EPSG_DISPLAY_PROJECTION + js += u"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 maxExtent = new OpenLayers.Bounds(%f, %f, %f, %f);\n" % \ + settings.MAP_BOUNDS + js += u"var media_path = '%s';\n" % settings.MEDIA_URL + js = u"""<script type="text/javascript"><!-- +%s// !--></script> +""" % js + return js + +class PointChooserWidget(forms.TextInput): + """ + Manage the edition of point on a map + """ + class Media: + css = { + "all": ("%sforms.css" % settings.MEDIA_URL,) + } + js = ["%sedit_map.js" % settings.MEDIA_URL] + URL_OSM_JS + + def render(self, name, value, attrs=None): + ''' + 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() + 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'><!-- +init(); +""" + if value: + tpl += '''var mylonlat = new OpenLayers.LonLat(%f,%f); +putMarker(mylonlat.transform(epsg_display_projection, + map.getProjectionObject()).clone()); +''' % (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) |
