summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authoretienne <etienne@9215b0d5-fb2c-4bbd-8d3e-bd2e9090e864>2008-10-22 22:39:53 +0000
committeretienne <etienne@9215b0d5-fb2c-4bbd-8d3e-bd2e9090e864>2008-10-22 22:39:53 +0000
commit030ed86130fabc78695355021b5a0586df3e77b9 (patch)
treebd06e4842c67577829cd433d7621bd22dac12776 /main
downloadChimè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__.py0
-rw-r--r--main/actions.py15
-rw-r--r--main/admin.py35
-rw-r--r--main/forms.py77
-rw-r--r--main/models.py163
-rw-r--r--main/views.py106
-rw-r--r--main/widgets.py97
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)