summaryrefslogtreecommitdiff
path: root/chimere/main
diff options
context:
space:
mode:
Diffstat (limited to 'chimere/main')
-rw-r--r--chimere/main/__init__.py0
-rw-r--r--chimere/main/actions.py38
-rw-r--r--chimere/main/admin.py91
-rw-r--r--chimere/main/forms.py253
-rw-r--r--chimere/main/models.py325
-rw-r--r--chimere/main/views.py260
-rw-r--r--chimere/main/widgets.py292
7 files changed, 1259 insertions, 0 deletions
diff --git a/chimere/main/__init__.py b/chimere/main/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chimere/main/__init__.py
diff --git a/chimere/main/actions.py b/chimere/main/actions.py
new file mode 100644
index 0000000..74b622a
--- /dev/null
+++ b/chimere/main/actions.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008 É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.
+
+"""
+Actions available in the main interface
+"""
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.auth import models
+
+from chimere.settings import EXTRA_URL
+
+class Action:
+ def __init__(self, id, path, label):
+ self.id, self.path, self.label = id, EXTRA_URL + path, label
+
+actions = ((Action('view', '', _('View')), []),
+ (Action('contribute', 'edit', _('Contribute')),
+ (Action('edit', 'edit', _('Add a new point of interest')),
+ Action('edit_route', 'edit_route', _('Add a new route')))
+ ),
+ (Action('contact', 'contact', _('Contact us')), []),
+ )
diff --git a/chimere/main/admin.py b/chimere/main/admin.py
new file mode 100644
index 0000000..195e7c4
--- /dev/null
+++ b/chimere/main/admin.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008 É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.
+
+"""
+Settings for administration pages
+"""
+
+from chimere.main.models import Category, Icon, SubCategory, Marker, \
+ PropertyModel, News, Route, Area, ColorTheme, Color
+from chimere.main.forms import MarkerAdminForm, RouteAdminForm, AreaAdminForm,\
+ NewsAdminForm, CategoryAdminForm
+from chimere.main.widgets import TextareaWidget
+
+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
+
+class RouteAdmin(admin.ModelAdmin):
+ """
+ Specialized the Route field.
+ """
+ search_fields = ("name",)
+ list_display = ('name', 'subcategory', 'status')
+ list_filter = ('status', 'subcategory')
+ form = RouteAdminForm
+
+class AreaAdmin(admin.ModelAdmin):
+ """
+ Specialized the area field.
+ """
+ form = AreaAdminForm
+ exclude = ['upper_left_corner', 'lower_right_corner']
+
+class SubCategoryAdmin(admin.ModelAdmin):
+ """
+ Specialized the subcategory admin
+ """
+ list_display = ('name', 'category', 'available')
+ list_filter = ('category',)
+
+class NewsAdmin(admin.ModelAdmin):
+ """
+ Use the TinyMCE widget for the news content
+ """
+ form = NewsAdminForm
+
+class CategoryAdmin(admin.ModelAdmin):
+ """
+ Use the TinyMCE widget for categories
+ """
+ form = CategoryAdminForm
+
+class ColorInline(admin.TabularInline):
+ model = Color
+
+class ColorThemeAdmin(admin.ModelAdmin):
+ inlines = [ColorInline,]
+
+# register of differents database fields
+admin.site.register(News, NewsAdmin)
+admin.site.register(Category, CategoryAdmin)
+admin.site.register(Icon)
+admin.site.register(SubCategory, SubCategoryAdmin)
+admin.site.register(Marker, MarkerAdmin)
+admin.site.register(Route, RouteAdmin)
+admin.site.register(PropertyModel)
+admin.site.register(Area, AreaAdmin)
+admin.site.register(ColorTheme, ColorThemeAdmin)
diff --git a/chimere/main/forms.py b/chimere/main/forms.py
new file mode 100644
index 0000000..be18b2f
--- /dev/null
+++ b/chimere/main/forms.py
@@ -0,0 +1,253 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008 É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.
+
+"""
+Forms
+"""
+from django import forms
+from django.contrib.gis.db import models
+from django.utils.translation import ugettext as _
+from django.contrib.auth.models import User
+from django.core.mail import EmailMessage, BadHeaderError
+
+from chimere import settings
+
+from chimere.main.models import Marker, Route, PropertyModel, Property, Area,\
+ News, Category
+from chimere.main.widgets import AreaField, PointField, TextareaWidget
+
+def notifyStaff(subject, body, sender=None):
+ if settings.PROJECT_NAME:
+ subject = u'[%s] %s' % (settings.PROJECT_NAME, subject)
+ user_list = [u.email for u in
+ User.objects.filter(is_staff=True).exclude(email="").order_by('id')]
+ headers = {}
+ if sender:
+ headers['Reply-To'] = sender
+ email = EmailMessage(subject, body, user_list[0], user_list,
+ headers=headers)
+ try:
+ email.send()
+ except BadHeaderError:
+ return False
+ return True
+
+def notifySubmission(geo_object):
+ category = unicode(geo_object.subcategory)
+ subject = u'%s %s' % (_(u"New submission for"), category)
+ message = _(u'The new item "%s" has been submited in the category: ') % \
+ geo_object.name + category
+ message += "\n\n" + _(u"To valid, precise or unvalid this item: ")
+ message += settings.BASE_URL + 'admin'
+ message += u"\n\n--\nChimère"
+ return notifyStaff(subject, message)
+
+class ContactForm(forms.Form):
+ """
+ Main form for categories
+ """
+ email = forms.EmailField(label=_("Email (optional)"), required=False)
+ content = forms.CharField(label=_("Object"), widget=forms.Textarea)
+
+class NewsAdminForm(forms.ModelForm):
+ """
+ Main form for news
+ """
+ content = forms.CharField(widget=TextareaWidget)
+ class Meta:
+ model = News
+
+class CategoryAdminForm(forms.ModelForm):
+ """
+ Main form for categories
+ """
+ description = forms.CharField(widget=TextareaWidget, required=False)
+ class Meta:
+ model = Category
+
+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=%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',)
+
+class RouteAdminForm(forms.ModelForm):
+ """
+ Main form for route
+ """
+ """
+ # declare properties
+ for property in PropertyModel.objects.filter(available=True):
+ exec('property_%d_%d = forms.CharField(label="%s", widget=%s, \
+required=False)' % (property.order, property.id, property.name,
+ PropertyModel.TYPE_WIDGET[property.type]))"""
+ class Meta:
+ model = Route
+
+ 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(RouteAdminForm, self).__init__(*args, **keys)
+
+ def save(self, *args, **keys):
+ """
+ Custom save method in order to manage associeted properties
+ """
+ new_marker = super(RouteAdminForm, 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 RouteForm(RouteAdminForm):
+ """
+ Form for the edit page
+ """
+ class Meta:
+ model = Route
+ exclude = ('status',)
+
+class AreaAdminForm(forms.ModelForm):
+ """
+ Admin page to create an area
+ """
+ area = AreaField(label=_("Area"), fields=(PointField(), PointField()))
+ class Meta:
+ model = Area
+
+ def __init__(self, *args, **keys):
+ """
+ Custom initialization method in order to manage area
+ """
+ if 'instance' in keys and keys['instance']:
+ instance = keys['instance']
+ dct = {'area':(instance.upper_left_corner,
+ instance.lower_right_corner)}
+ if 'initial' in keys:
+ keys['initial'].update(dct)
+ else:
+ keys['initial'] = dct
+ super(AreaAdminForm, self).__init__(*args, **keys)
+
+ def save(self, *args, **keys):
+ """
+ Custom save method in order to manage area
+ """
+ new_area = super(AreaAdminForm, self).save(*args, **keys)
+ area = self.cleaned_data['area']
+ new_area.upper_left_corner = 'POINT(%s %s)' % (area[0][0], area[0][1])
+ new_area.lower_right_corner = 'POINT(%s %s)' % (area[1][0],
+ area[1][1])
+ return new_area
diff --git a/chimere/main/models.py b/chimere/main/models.py
new file mode 100644
index 0000000..14f3481
--- /dev/null
+++ b/chimere/main/models.py
@@ -0,0 +1,325 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008 É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.
+
+"""
+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, RouteField
+
+
+class News(models.Model):
+ """News of the site
+ """
+ title = models.CharField(_("Name"), max_length=150)
+ available = models.BooleanField(_("Available"))
+ date = models.DateField(_("Date"), auto_now_add=True)
+ content = models.TextField()
+ def __unicode__(self):
+ ordering = ["-date"]
+ return self.title
+ class Meta:
+ verbose_name = _("News")
+
+class ColorTheme(models.Model):
+ """Color theme
+ """
+ name = models.CharField(_("Name"), max_length=150)
+ def __unicode__(self):
+ return self.name
+ class Meta:
+ verbose_name = _("Color theme")
+
+class Color(models.Model):
+ """Color
+ """
+ code = models.CharField(_("Code"), max_length=6)
+ order = models.IntegerField(_("Order"))
+ color_theme = models.ForeignKey(ColorTheme, verbose_name=_("Color theme"))
+ def __unicode__(self):
+ return self.code
+ class Meta:
+ ordering = ["order"]
+ verbose_name = _("Color")
+
+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"))
+ description = models.TextField(blank=True, null=True)
+ 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',
+ height_field='height', width_field='width')
+ 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"))
+ color_theme = models.ForeignKey(ColorTheme, verbose_name=_("Color theme"),
+ blank=True, null=True)
+ order = models.IntegerField(_("Order"))
+ TYPE = (('M', _('Marker')),
+ ('R', _('Route')),
+ ('B', _('Both')),)
+ item_type = models.CharField(_("Item type"), max_length=1, choices=TYPE)
+ def __unicode__(self):
+ return u"%s / %s" % (self.category.name, self.name)
+ class Meta:
+ ordering = ["category", "order"]
+ verbose_name = _("Subcategory")
+
+ def getAvailable(item_types=None):
+ '''Get list of tuples with first the category and second the associated
+ subcategories
+ '''
+ sub_categories = {}
+ subcategories = None
+ if not item_types:
+ subcategories = SubCategory.objects.filter(category__available=True,
+ available=True)
+ else:
+ subcategories = SubCategory.objects.filter(category__available=True,
+ item_type__in=item_types)
+ for sub_category in subcategories:
+ 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,
+ height_field='height', width_field='width')
+ 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":%(geometry)s, \
+"properties":{"pk": %(id)d, "name": "%(name)s", \
+"icon_path":"%(icon_path)s", "icon_width":%(icon_width)d, \
+"icon_height":%(icon_height)d}}""" % {'id':self.id, 'name':self.name,
+'icon_path':self.subcategory.icon.image, 'geometry':self.point.geojson,
+'icon_width':self.subcategory.icon.image.width,
+'icon_height':self.subcategory.icon.image.height,}
+
+class Route(models.Model):
+ '''Route on the map
+ '''
+ name = models.CharField(_("Name"), max_length=150)
+ subcategory = models.ForeignKey(SubCategory, verbose_name=_("Subcategory"))
+ route = RouteField(_("Route"))
+ picture = models.ImageField(_("Image"), upload_to='upload', blank=True,
+ height_field='height', width_field='width')
+ 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 = _("Route")
+
+ 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, color="#000"):
+ '''Return a GeoJSON string
+ '''
+ if '#' not in color:
+ color = '#' + color
+ return """{"type":"Feature", "geometry":%(geometry)s, \
+"properties":{"pk": %(id)d, "name": "%(name)s", \
+"color":"%(color)s"}}""" % {'id':self.id, 'name':self.name,
+'color':color, 'geometry':self.route.geojson,}
+
+class Area(models.Model):
+ """Rectangular area of the map
+ """
+ name = models.CharField(_("Name"), max_length=150)
+ order = models.IntegerField(_("Order"))
+ available = models.BooleanField(_("Available"))
+ upper_left_corner = models.PointField(_("Upper left corner"),
+ default='POINT(0 0)')
+ lower_right_corner = models.PointField(_("Lower right corner"),
+ default='POINT(0 0)')
+ objects = models.GeoManager()
+
+ def __unicode__(self):
+ return self.name
+
+ class Meta:
+ ordering = ('order', 'name')
+ verbose_name = _("Area")
+
+ def getAvailable():
+ '''Get available areas
+ '''
+ return Area.objects.filter(available=True)
+ getAvailable = staticmethod(getAvailable)
+
+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':'forms.TextInput',
+ 'L':'TextareaWidget',
+ 'P':'forms.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/chimere/main/views.py b/chimere/main/views.py
new file mode 100644
index 0000000..9de8f4b
--- /dev/null
+++ b/chimere/main/views.py
@@ -0,0 +1,260 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008-2010 É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.
+
+"""
+Views of the project
+"""
+
+import datetime
+
+from django.utils.translation import ugettext as _
+from django.shortcuts import render_to_response
+from django.template import loader
+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 Category, SubCategory, PropertyModel, Marker, \
+ Route, News, Area, Color
+
+from chimere.main.widgets import getMapJS, PointChooserWidget, \
+ RouteChooserWidget, URL_OSM_JS, URL_OSM_CSS
+from chimere.main.forms import MarkerForm, RouteForm, ContactForm, \
+ notifySubmission, notifyStaff
+
+def index(request):
+ """
+ Main page
+ """
+ subcategories = SubCategory.getAvailable()
+ for cat, sub_cats in subcategories:
+ for sub_category in sub_cats:
+ if sub_category.id in settings.DEFAULT_CATEGORIES:
+ sub_category.selected = True
+ cat.selected= True
+ extra = ""
+ tab = " "*4
+ for url in URL_OSM_CSS:
+ extra += tab + '<link rel="stylesheet" href="%s" />' % url
+ for url in URL_OSM_JS + ["%sbase.js" % settings.MEDIA_URL,
+ "%smain_map.js" % settings.MEDIA_URL,]:
+ extra += tab + '<script src="%s"></script>\n' % url
+ extra += tab + '<script src="/' + settings.EXTRA_URL + 'jsi18n/"></script>\n'
+ # show the welcome page
+ today = datetime.date.today().strftime('%y-%m-%d')
+ display_welcome = None
+ if not 'last_visit' in request.session or \
+ request.session['last_visit'] != today:
+ request.session['last_visit'] = today
+ display_welcome = True
+ response_dct = {'actions':actions, 'action_selected':('view',),
+ 'error_message':'',
+ 'sub_categories':subcategories,
+ 'extra_head':extra + getMapJS(),
+ 'media_path':settings.MEDIA_URL,
+ 'extra_url':settings.EXTRA_URL,
+ 'welcome':welcome(request, display_welcome),
+ 'areas':Area.getAvailable(),
+ 'map_layer':settings.MAP_LAYER
+ }
+ # manage permalink
+ if request.GET:
+ for key in ('zoom', 'lon', 'lat', 'display_submited'):
+ if key in request.GET and request.GET[key]:
+ response_dct['p_'+key] = request.GET[key]
+ else:
+ response_dct['p_'+key] = '""'
+ if 'checked_categories' in request.GET \
+ and request.GET['checked_categories']:
+ cats = request.GET['checked_categories'].split('_')
+ response_dct['p_checked_categories'] = ",".join(cats)
+ else:
+ response_dct['p_checked_categories'] = '';
+ 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()
+ notifySubmission(marker)
+ return HttpResponseRedirect('/' + settings.EXTRA_URL +'submited/edit')
+ 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':('contribute', 'edit'),
+ 'error_message':'',
+ 'media_path':settings.MEDIA_URL,
+ 'extra_url':settings.EXTRA_URL,
+ 'map_layer':settings.MAP_LAYER,
+ 'form':form,
+ 'extra_head':form.media,
+ 'sub_categories':SubCategory.getAvailable(['M', 'B']),
+ '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 editRoute(request):
+ """
+ Route edition page
+ """
+ # If the form has been submited
+ if request.method == 'POST':
+ form = RouteForm(request.POST, request.FILES)
+ # All validation rules pass
+ if form.is_valid():
+ route = form.save()
+ # set the submited status
+ route.status = 'S'
+ route.save()
+ notifySubmission(route)
+ return HttpResponseRedirect('/' + settings.EXTRA_URL + \
+ 'submited/edit_route')
+ else:
+ # An unbound form
+ form = RouteForm()
+ # get the « manualy » declared_fields. Ie: properties
+ declared_fields = form.declared_fields.keys()
+ response_dct = {'actions':actions,
+ 'action_selected':('contribute', 'edit_route'),
+ 'error_message':'',
+ 'media_path':settings.MEDIA_URL,
+ 'map_layer':settings.MAP_LAYER,
+ 'form':form,
+ 'extra_head':form.media,
+ 'extra_url':settings.EXTRA_URL,
+ 'sub_categories':SubCategory.getAvailable(['R', 'B']),
+ 'route_widget':RouteChooserWidget().render('route', 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_route.html', response_dct)
+
+def welcome(request, display=None):
+ """
+ Welcome string
+ """
+ response_dct = {'display':display}
+ response_dct['news_lst'] = News.objects.filter(available=True)
+ return loader.render_to_string('welcome.html', response_dct)
+
+def submited(request, action):
+ """
+ Successful submission page
+ """
+ response_dct = {'actions':actions, 'action_selected':action,
+ 'media_path':settings.MEDIA_URL,}
+ return render_to_response('submited.html', response_dct)
+
+def contactus(request):
+ """
+ Contact page
+ """
+ form = None
+ msg = ''
+ # If the form has been submited
+ if request.method == 'POST':
+ form = ContactForm(request.POST)
+ # All validation rules pass
+ if form.is_valid():
+ response = notifyStaff(_(u"Comments/request on the map"),
+ form.cleaned_data['content'], form.cleaned_data['email'])
+ if response:
+ msg = _(u"Thank you for your contribution. It will be taken \
+into account. If you have left your email you may be contacted soon for more \
+details.")
+ else:
+ msg = _(u"Temporary error. Renew your message later.")
+ else:
+ form = ContactForm()
+ response_dct = {'actions':actions, 'action_selected':('contact',),
+ 'media_path':settings.MEDIA_URL,'contact_form':form, 'message':msg}
+ return render_to_response('contactus.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 getDescriptionDetail(request, category_id):
+ '''
+ Get the description for a category
+ '''
+ try:
+ category = Category.objects.filter(id=int(category_id))[0]
+ except (ValueError, IndexError):
+ return HttpResponse('no results')
+ response_dct= {'media_path':settings.MEDIA_URL, 'category':category}
+ return render_to_response('category_detail.html', response_dct)
+
+def getGeoObjects(request, category_ids, status='A'):
+ '''
+ Get the JSON for a route
+ '''
+ status = status.split('_')
+ try:
+ query = Route.objects.filter(status__in=status,
+ subcategory__in=category_ids.split('_'))
+ except:
+ return HttpResponse('no results')
+ query.order_by('subcategory')
+ routes = list(query)
+ jsons = []
+ current_cat, colors, idx = None, None, 0
+ for route in routes:
+ if not current_cat or current_cat != route.subcategory:
+ idx = 0
+ current_cat = route.subcategory
+ colors = list(Color.objects.filter(color_theme=\
+ route.subcategory.color_theme))
+ jsons.append(route.getGeoJSON(color=colors[idx % len(colors)].code))
+ idx += 1
+ try:
+ query = Marker.objects.filter(status__in=status,
+ subcategory__in=category_ids.split('_'))
+ except:
+ return HttpResponse('no results')
+ jsons += [geo_object.getGeoJSON() for geo_object in list(query)]
+ if not jsons:
+ return HttpResponse('no results')
+ data = '{"type": "FeatureCollection", "features":[%s]}' % ",".join(jsons)
+ return HttpResponse(data)
diff --git a/chimere/main/widgets.py b/chimere/main/widgets.py
new file mode 100644
index 0000000..6b5e544
--- /dev/null
+++ b/chimere/main/widgets.py
@@ -0,0 +1,292 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008 É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.utils.safestring import mark_safe
+from django.utils.translation import ugettext as _
+from django.contrib.gis.geos import fromstr
+
+from chimere import settings
+from django.contrib.gis.db import models
+
+URL_OSM_CSS = ["http://www.openlayers.org/api/theme/default/style.css"]
+URL_OSM_JS = ["http://www.openlayers.org/api/OpenLayers.js",
+ "http://www.openstreetmap.org/openlayers/OpenStreetMap.js"]
+
+def getMapJS():
+ '''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 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):
+ '''
+ 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(), 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):
+ 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):
+ '''
+ Render a map and latitude, longitude information field
+ '''
+ tpl = getMapJS()
+ 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>""" % (_("Creation mode"),
+_("To start drawing the route click on the toggle button : \"Start drawing\"."),
+_("Then click on the map to begin the drawing."),
+_("You can add points by clicking again."),
+_("To finish the drawing double click. When the drawing is finished you can \
+edit it."),
+_("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>""" % (_("Modification mode"),
+_("To move a point click on it and drag it to the desired position."),
+_("To delete a point move the mouse cursor over it and press the \"d\" key."),
+_("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:
+ tpl += u"""<div id='draw-toggle-off' class='toggle-button' \
+onclick='toggleDrawOn();'>%s</div>
+<div id='draw-toggle-on' class='toggle-button' \
+onclick='toggleDrawOff();'>%s</div>
+<hr class='spacer'/>""" % (_("Start drawing"), _("Stop drawing"))
+ tpl += """
+<div id='map_edit'></div>"""
+ if not value:
+ 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"/>
+""" % (style, help_modify, name, name, value)
+ tpl += """<script type='text/javascript'><!--
+init();"""
+ if value:
+ 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);
+map.zoomToExtent(extent, true);""" % (upper_left_lat, upper_left_lon, lower_right_lat,
+ lower_right_lon)
+ 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_lat', 'upper_left_lon',),
+ ('lower_right_lat', 'lower_right_lon')):
+ 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