diff options
Diffstat (limited to 'chimere/main')
-rw-r--r-- | chimere/main/__init__.py | 0 | ||||
-rw-r--r-- | chimere/main/actions.py | 38 | ||||
-rw-r--r-- | chimere/main/admin.py | 91 | ||||
-rw-r--r-- | chimere/main/forms.py | 253 | ||||
-rw-r--r-- | chimere/main/models.py | 325 | ||||
-rw-r--r-- | chimere/main/views.py | 260 | ||||
-rw-r--r-- | chimere/main/widgets.py | 292 |
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 |