summaryrefslogtreecommitdiff
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
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
-rw-r--r--__init__.py0
-rw-r--r--initial_data.json1
-rw-r--r--locale/fr/LC_MESSAGES/django.po146
-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
-rwxr-xr-xmanage.py11
-rw-r--r--settings.py94
-rw-r--r--static/edit_map.js117
-rw-r--r--static/forms.css11
-rw-r--r--static/main_map.js172
-rw-r--r--static/styles.css150
-rw-r--r--templates/base.html29
-rw-r--r--templates/detail.html7
-rw-r--r--templates/edit.html51
-rw-r--r--templates/main_map.html23
-rw-r--r--templates/submited.html9
-rw-r--r--urls.py26
22 files changed, 1340 insertions, 0 deletions
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/__init__.py
diff --git a/initial_data.json b/initial_data.json
new file mode 100644
index 0000000..e6261a2
--- /dev/null
+++ b/initial_data.json
@@ -0,0 +1 @@
+[{"pk": 13, "model": "auth.permission", "fields": {"codename": "add_logentry", "name": "Can add log entry", "content_type": 5}}, {"pk": 14, "model": "auth.permission", "fields": {"codename": "change_logentry", "name": "Can change log entry", "content_type": 5}}, {"pk": 15, "model": "auth.permission", "fields": {"codename": "delete_logentry", "name": "Can delete log entry", "content_type": 5}}, {"pk": 4, "model": "auth.permission", "fields": {"codename": "add_group", "name": "Can add group", "content_type": 2}}, {"pk": 10, "model": "auth.permission", "fields": {"codename": "add_message", "name": "Can add message", "content_type": 4}}, {"pk": 1, "model": "auth.permission", "fields": {"codename": "add_permission", "name": "Can add permission", "content_type": 1}}, {"pk": 7, "model": "auth.permission", "fields": {"codename": "add_user", "name": "Can add user", "content_type": 3}}, {"pk": 5, "model": "auth.permission", "fields": {"codename": "change_group", "name": "Can change group", "content_type": 2}}, {"pk": 11, "model": "auth.permission", "fields": {"codename": "change_message", "name": "Can change message", "content_type": 4}}, {"pk": 2, "model": "auth.permission", "fields": {"codename": "change_permission", "name": "Can change permission", "content_type": 1}}, {"pk": 8, "model": "auth.permission", "fields": {"codename": "change_user", "name": "Can change user", "content_type": 3}}, {"pk": 6, "model": "auth.permission", "fields": {"codename": "delete_group", "name": "Can delete group", "content_type": 2}}, {"pk": 12, "model": "auth.permission", "fields": {"codename": "delete_message", "name": "Can delete message", "content_type": 4}}, {"pk": 3, "model": "auth.permission", "fields": {"codename": "delete_permission", "name": "Can delete permission", "content_type": 1}}, {"pk": 9, "model": "auth.permission", "fields": {"codename": "delete_user", "name": "Can delete user", "content_type": 3}}, {"pk": 16, "model": "auth.permission", "fields": {"codename": "add_contenttype", "name": "Can add content type", "content_type": 6}}, {"pk": 17, "model": "auth.permission", "fields": {"codename": "change_contenttype", "name": "Can change content type", "content_type": 6}}, {"pk": 18, "model": "auth.permission", "fields": {"codename": "delete_contenttype", "name": "Can delete content type", "content_type": 6}}, {"pk": 25, "model": "auth.permission", "fields": {"codename": "add_category", "name": "Can add category", "content_type": 9}}, {"pk": 28, "model": "auth.permission", "fields": {"codename": "add_icon", "name": "Can add icon", "content_type": 10}}, {"pk": 34, "model": "auth.permission", "fields": {"codename": "add_marker", "name": "Can add marker", "content_type": 12}}, {"pk": 40, "model": "auth.permission", "fields": {"codename": "add_property", "name": "Can add property", "content_type": 14}}, {"pk": 37, "model": "auth.permission", "fields": {"codename": "add_propertymodel", "name": "Can add property model", "content_type": 13}}, {"pk": 31, "model": "auth.permission", "fields": {"codename": "add_subcategory", "name": "Can add sub category", "content_type": 11}}, {"pk": 26, "model": "auth.permission", "fields": {"codename": "change_category", "name": "Can change category", "content_type": 9}}, {"pk": 29, "model": "auth.permission", "fields": {"codename": "change_icon", "name": "Can change icon", "content_type": 10}}, {"pk": 35, "model": "auth.permission", "fields": {"codename": "change_marker", "name": "Can change marker", "content_type": 12}}, {"pk": 41, "model": "auth.permission", "fields": {"codename": "change_property", "name": "Can change property", "content_type": 14}}, {"pk": 38, "model": "auth.permission", "fields": {"codename": "change_propertymodel", "name": "Can change property model", "content_type": 13}}, {"pk": 32, "model": "auth.permission", "fields": {"codename": "change_subcategory", "name": "Can change sub category", "content_type": 11}}, {"pk": 27, "model": "auth.permission", "fields": {"codename": "delete_category", "name": "Can delete category", "content_type": 9}}, {"pk": 30, "model": "auth.permission", "fields": {"codename": "delete_icon", "name": "Can delete icon", "content_type": 10}}, {"pk": 36, "model": "auth.permission", "fields": {"codename": "delete_marker", "name": "Can delete marker", "content_type": 12}}, {"pk": 42, "model": "auth.permission", "fields": {"codename": "delete_property", "name": "Can delete property", "content_type": 14}}, {"pk": 39, "model": "auth.permission", "fields": {"codename": "delete_propertymodel", "name": "Can delete property model", "content_type": 13}}, {"pk": 33, "model": "auth.permission", "fields": {"codename": "delete_subcategory", "name": "Can delete sub category", "content_type": 11}}, {"pk": 19, "model": "auth.permission", "fields": {"codename": "add_session", "name": "Can add session", "content_type": 7}}, {"pk": 20, "model": "auth.permission", "fields": {"codename": "change_session", "name": "Can change session", "content_type": 7}}, {"pk": 21, "model": "auth.permission", "fields": {"codename": "delete_session", "name": "Can delete session", "content_type": 7}}, {"pk": 22, "model": "auth.permission", "fields": {"codename": "add_site", "name": "Can add site", "content_type": 8}}, {"pk": 23, "model": "auth.permission", "fields": {"codename": "change_site", "name": "Can change site", "content_type": 8}}, {"pk": 24, "model": "auth.permission", "fields": {"codename": "delete_site", "name": "Can delete site", "content_type": 8}}, {"pk": 2, "model": "auth.group", "fields": {"name": "moderator", "permissions": [34, 35, 36]}}, {"pk": 1, "model": "auth.group", "fields": {"name": "administrator", "permissions": [7, 8, 9, 25, 28, 34, 31, 26, 29, 35, 32, 27, 30, 36, 33]}}, {"pk": 9, "model": "contenttypes.contenttype", "fields": {"model": "category", "name": "category", "app_label": "main"}}, {"pk": 6, "model": "contenttypes.contenttype", "fields": {"model": "contenttype", "name": "content type", "app_label": "contenttypes"}}, {"pk": 2, "model": "contenttypes.contenttype", "fields": {"model": "group", "name": "group", "app_label": "auth"}}, {"pk": 10, "model": "contenttypes.contenttype", "fields": {"model": "icon", "name": "icon", "app_label": "main"}}, {"pk": 5, "model": "contenttypes.contenttype", "fields": {"model": "logentry", "name": "log entry", "app_label": "admin"}}, {"pk": 12, "model": "contenttypes.contenttype", "fields": {"model": "marker", "name": "marker", "app_label": "main"}}, {"pk": 4, "model": "contenttypes.contenttype", "fields": {"model": "message", "name": "message", "app_label": "auth"}}, {"pk": 1, "model": "contenttypes.contenttype", "fields": {"model": "permission", "name": "permission", "app_label": "auth"}}, {"pk": 14, "model": "contenttypes.contenttype", "fields": {"model": "property", "name": "property", "app_label": "main"}}, {"pk": 13, "model": "contenttypes.contenttype", "fields": {"model": "propertymodel", "name": "property model", "app_label": "main"}}, {"pk": 7, "model": "contenttypes.contenttype", "fields": {"model": "session", "name": "session", "app_label": "sessions"}}, {"pk": 8, "model": "contenttypes.contenttype", "fields": {"model": "site", "name": "site", "app_label": "sites"}}, {"pk": 11, "model": "contenttypes.contenttype", "fields": {"model": "subcategory", "name": "sub category", "app_label": "main"}}, {"pk": 3, "model": "contenttypes.contenttype", "fields": {"model": "user", "name": "user", "app_label": "auth"}}]
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 0000000..f9a779e
--- /dev/null
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,146 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-06 20:39+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: main/actions.py:15
+msgid "View"
+msgstr "Voir"
+
+#: main/actions.py:15
+msgid "Add"
+msgstr "Ajouter"
+
+#: main/models.py:16 main/models.py:28 main/models.py:39 main/models.py:66
+#: main/models.py:120
+msgid "Name"
+msgstr "Nom"
+
+#: main/models.py:17 main/models.py:40 main/models.py:71 main/models.py:122
+msgid "Available"
+msgstr "Disponible"
+
+#: main/models.py:18 main/models.py:42 main/models.py:121
+msgid "Order"
+msgstr "Ordre"
+
+#: main/models.py:23 main/models.py:38 templates/edit.html:24
+msgid "Category"
+msgstr "Catégorie"
+
+#: main/models.py:29 main/models.py:69 templates/edit.html:43
+msgid "Image"
+msgstr "Image"
+
+#: main/models.py:33 main/models.py:41
+msgid "Icon"
+msgstr "Icone"
+
+#: main/models.py:47 main/models.py:67
+msgid "Subcategory"
+msgstr "Sous-catégorie"
+
+#: main/models.py:68
+msgid "Localisation"
+msgstr "Localisation"
+
+#: main/models.py:70
+msgid "Submited"
+msgstr "Soumis"
+
+#: main/models.py:72
+msgid "Disabled"
+msgstr "Désactivé"
+
+#: main/models.py:76
+msgid "Status"
+msgstr "État"
+
+#: main/models.py:84 main/models.py:144
+msgid "Point of interest"
+msgstr "Point d'intérêt"
+
+#: main/models.py:123
+msgid "Text"
+msgstr "Texte"
+
+#: main/models.py:124
+msgid "Long text"
+msgstr "Texte long"
+
+#: main/models.py:125
+msgid "Password"
+msgstr "Mot de passe"
+
+#: main/models.py:129
+msgid "Type"
+msgstr "Type"
+
+#: main/models.py:134 main/models.py:146
+msgid "Property model"
+msgstr "Modèle de propriété"
+
+#: main/models.py:147
+msgid "Value"
+msgstr "Valeur"
+
+#: main/models.py:151
+msgid "Property"
+msgstr "Propriété"
+
+#: main/widgets.py:71
+msgid "Latitude"
+msgstr "Latitude"
+
+#: main/widgets.py:71
+msgid "Longitude"
+msgstr "Longitude"
+
+#: templates/edit.html:15 templates/submited.html:7
+msgid "Add a new site"
+msgstr "Ajouter un nouveau site"
+
+#: templates/edit.html:16
+msgid "indicates a mandatory field"
+msgstr "indique un champs obligatoire"
+
+#: templates/edit.html:19
+msgid "Site name"
+msgstr "Nom du site"
+
+#: templates/edit.html:38
+msgid "Point"
+msgstr "Point"
+
+#: templates/edit.html:39
+msgid "Select a location for this new site"
+msgstr "Choisissez une localisation pour ce nouveau site"
+
+#: templates/edit.html:54
+msgid "Propose"
+msgstr "Proposez"
+
+#: templates/main_map.html:19
+msgid "Detail"
+msgstr "Détail"
+
+#: templates/submited.html:8
+msgid ""
+"Your proposition has been submited. A moderator will treat your submission "
+"shortly. Thanks!"
+msgstr ""
+"Votre proposition a été soumise. Un modérateur va traiter votre proposition "
+"sous peu. Merci !"
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)
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..bcdd55e
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/settings.py b/settings.py
new file mode 100644
index 0000000..322167b
--- /dev/null
+++ b/settings.py
@@ -0,0 +1,94 @@
+# Django settings for chimere project.
+
+# GDAL path for GeoDjango
+GDAL_LIBRARY_PATH = '/usr/local/lib/libgdal.so'
+
+ROOT_PATH = '/home/etienne/Documents/Prog/chimere/'
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = 'postgresql_psycopg2' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
+DATABASE_NAME = 'ratatouille' # Or path to database file if using sqlite3.
+DATABASE_USER = 'ratatouille' # Not used with sqlite3.
+DATABASE_PASSWORD = 'wiki' # Not used with sqlite3.
+DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
+
+# Local time zone for this installation. Choices can be found here:
+# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
+# although not all variations may be possible on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'Europe/Paris'
+
+# Language code for this installation. All choices can be found here:
+# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
+# http://blogs.law.harvard.edu/tech/stories/storyReader$15
+LANGUAGE_CODE = 'fr-fr'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = ROOT_PATH + 'static/'
+
+# URL that handles the media served from MEDIA_ROOT.
+# Example: "http://media.lawrence.com"
+MEDIA_URL = '/chimere/static/'
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/chimere/media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'achanger_!ToChange!'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.middleware.doc.XViewMiddleware',
+)
+
+ROOT_URLCONF = 'chimere.urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+ ROOT_PATH + 'templates',
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.admin',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'chimere.main',
+)
+
+# Wikimap specific
+MAP_BOUNDS = (-20037508.34, -20037508.34, 20037508.34, 20037508.34)
+DEFAULT_CENTER = (-1.679444, 48.114722)
+EPSG_PROJECTION = 900913
+EPSG_DISPLAY_PROJECTION = 4326
diff --git a/static/edit_map.js b/static/edit_map.js
new file mode 100644
index 0000000..5a9c997
--- /dev/null
+++ b/static/edit_map.js
@@ -0,0 +1,117 @@
+/* map edit */
+
+/* availaible map layers */
+var layerMapnik = new OpenLayers.Layer.OSM.Mapnik('Mapnik');
+var layerMarkers = new OpenLayers.Layer.Markers('POIs');
+
+/* default size and offset for icon */
+var size = new OpenLayers.Size(21, 25);
+var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
+var icon = new OpenLayers.Icon('http://www.openlayers.org/dev/img/marker.png', size, offset);
+
+/* set a marker with a click on the map */
+var setMarker = function (event){
+ event = event || window.event;
+ lonlat = layerMarkers.getLonLatFromViewPortPx(event.xy);
+ putMarker(lonlat);
+ OpenLayers.Event.stop(event);
+}
+
+/* put the marker on the map and update latitude and longitude fields */
+var putMarker = function (lonlat){
+ if (marker) {
+ layerMarkers.removeMarker(marker);
+ }
+ var marker = new OpenLayers.Marker(lonlat.clone(), icon);
+ layerMarkers.addMarker(marker);
+ lonlat = lonlat.clone().transform(map.getProjectionObject(),
+ epsg_display_projection);
+ document.getElementById('id_point').value = 'POINT(' + lonlat.lon +
+ ' ' + lonlat.lat + ')';
+ document.getElementById('live_latitude').value = lonlat.lon;
+ document.getElementById('live_longitude').value = lonlat.lat;
+}
+
+/* main initialisation function */
+function init(){
+ map = new OpenLayers.Map ('map_edit', {
+ controls:[new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.PanZoomBar(),
+ new OpenLayers.Control.Attribution()],
+ maxExtent: maxExtent,
+ maxResolution: 156543.0399,
+ units: 'm',
+ projection: epsg_projection,
+ displayProjection: epsg_display_projection,
+ } );
+ layerMarkers.setOpacity(0.5);
+ map.addLayers([layerMapnik, layerMarkers]);
+ map.events.register('click', map, setMarker);
+ map.setCenter(centerLonLat, 12);
+
+}
+
+
+/* code for drag'n drop : delayed for further version of openlayers */
+
+/*
+function init_b(){
+ init();
+
+ marker.events.register('mouseup', marker, releaseMarker);
+ marker.events.register('mousedown', marker, catchMarker);
+ marker.events.register('mouseover', marker, markerOver);
+ marker.events.register('mouseout', marker, markerOut);
+
+ map.events.register('mousemove', map, onMouseMove);
+ map.events.register('moveend', map, onMoveEnd);
+}
+
+var marker_catched = false;
+
+function catchMarker(event){
+ marker_catched = true;
+ event = event || window.event;
+ OpenLayers.Event.stop(event);
+}
+
+function releaseMarker(event){
+ event = event || window.event;
+ if(marker_catched) {
+ marker_catched = false;
+ lonlat = layerMarkers.getLonLatFromViewPortPx(marker.icon.px);
+ marker.lonlat = lonlat;
+ document.getElementById('latitude').value = marker.lonlat.lat;
+ document.getElementById('longitude').value = marker.lonlat.lon;
+ }
+ OpenLayers.Event.stop(event);
+}
+
+function onMouseMove(event) {
+ event = event || window.event;
+ if (!marker_catched) {return;}
+ marker.icon.px = null;
+ marker.icon.moveTo(new OpenLayers.Pixel(event.xy.x + delta_x,
+ event.xy.y + delta_y + 20));
+ defLiveLatLon();
+ lonlat = layerMarkers.getLonLatFromViewPortPx(marker.icon.px).transform(epsg_projection, epsg_display_projection);
+ live_longitude.value = lonlat.lon;
+ live_latitude.value = lonlat.lat;
+ OpenLayers.Event.stop(event);
+}
+
+function onMoveEnd(event) {
+ event = event || window.event;
+ marker.icon.px = map.getPixelFromLonLat(marker.lonlat);
+ OpenLayers.Event.stop(event);
+}
+
+var markerOver = function (evt) {
+ document.body.style.cursor='pointer';
+ OpenLayers.Event.stop(evt);
+};
+var markerOut = function (evt) {
+ document.body.style.cursor='auto';
+ OpenLayers.Event.stop(evt);
+};
+*/
diff --git a/static/forms.css b/static/forms.css
new file mode 100644
index 0000000..e536e96
--- /dev/null
+++ b/static/forms.css
@@ -0,0 +1,11 @@
+#map_edit{
+border: 1px solid black;
+width:700px;
+height:400px;
+float:left;
+}
+
+#live_lonlat{
+margin:1em;
+float:left;
+} \ No newline at end of file
diff --git a/static/main_map.js b/static/main_map.js
new file mode 100644
index 0000000..9bb703f
--- /dev/null
+++ b/static/main_map.js
@@ -0,0 +1,172 @@
+/* main map */
+
+/* availaible map layers */
+var layerMapnik = new OpenLayers.Layer.OSM.Mapnik('Classic');
+var cyclemap = new OpenLayers.Layer.OSM.CycleMap("Cycle map", {
+ displayOutsideMaxExtent: true,
+ wrapDateLine: true});
+
+/* default size and offset for icon */
+var size = new OpenLayers.Size(21, 25);
+var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
+
+/* define global variable */
+var markers = new Array();
+var layerMarkers;
+
+var currentPopup;
+var currentFeature;
+var clicked = false;
+
+/* show a popup */
+function showPop(feature) {
+ if (currentPopup != null) {
+ currentPopup.hide();
+ }
+ if (feature.popup == null) {
+ feature.popup = feature.createPopup();
+ map.addPopup(feature.popup);
+ } else {
+ feature.popup.toggle();
+ }
+ currentPopup = feature.popup;
+}
+
+/* load markers with an AJAX request */
+function loadMarkers(){
+ /* get checked categories */
+ inputs = window.document.forms["frm_categories"];
+ var categories = '';
+ for (var i = 0; i < inputs.length; i++) {
+ input = inputs[i];
+ if (input.checked
+ && input.name.substring('category_'.length, 0) == 'category_'){
+ id = input.name.substring('category_'.length, input.name.length);
+ if(categories) categories += '_';
+ categories += id;
+ }
+ }
+ /* 0 stand for all categories */
+ if (!categories) categories = '0';
+ var uri = "/chimere/getMarkers/" + categories;
+ OpenLayers.loadURL(uri, '', this, setMarkers);
+}
+
+/* update the marker layers from an http response GeoJSON */
+function setMarkers(response){
+ if(layerMarkers) layerMarkers.destroy();
+ if (response.responseText.indexOf('no results') == -1) {
+ /* clean the marker layer */
+ if (currentPopup) {
+ currentPopup.hide();
+ hideDetail();
+ }
+ layerMarkers = new OpenLayers.Layer.Markers('POIs');
+ map.addLayer(layerMarkers);
+ layerMarkers.setOpacity(0.8);
+
+ var json = new OpenLayers.Format.JSON();
+ var markers_pt = json.read(response.responseText);
+ /* load every marker */
+ for (var i = 0; i < markers_pt.features.length; i++) {
+ putMarker(markers_pt.features[i]);
+ }
+ /*
+ var geojson = new OpenLayers.Format.GeoJSON();
+ var markers_pt = geojson.read(response.responseText);
+ for (var i = 0; i < markers_pt.length; i++) {
+ putMarker2(markers_pt[i]);
+ }*/
+ }
+}
+
+/* put a marker on the map */
+function putMarker(mark) {
+ /* initialise a new feature with appropriate attribute for setting a
+ marker */
+ lat = mark.geometry.coordinates[1];
+ lon = mark.geometry.coordinates[0];
+ iconclone = new OpenLayers.Icon(media_path + mark.properties.icon_path,
+ size, offset);
+ var feature = new OpenLayers.Feature(markers,
+ new OpenLayers.LonLat(lon, lat).transform(epsg_display_projection,
+ epsg_projection),
+ {icon:iconclone});
+ /*feature.closeBox = false;*/
+ feature.pk = mark.properties.pk;
+ feature.popupClass = OpenLayers.Class(OpenLayers.Popup.FramedCloud);
+ feature.data.popupContentHTML = "<div class='cloud'>" + mark.properties.name + "<br/>&nbsp;</div>";
+ feature.data.overflow = 'hidden';
+ var marker = feature.createMarker();
+
+ /* manage markers events */
+ var markerClick = function (evt) {
+ currentFeature = this;
+ if (clicked) {
+ if (currentPopup == this.popup) {
+ this.popup.hide();
+ clicked = false;
+ hideDetail();
+ } else {
+ currentPopup.hide();
+ showPop(this);
+ updateDetail(this.pk);
+ }
+ } else {
+ showPop(this);
+ clicked = true;
+ updateDetail(this.pk);
+ }
+ OpenLayers.Event.stop(evt);
+ };
+ var markerOver = function (evt) {
+ document.body.style.cursor='pointer';
+ OpenLayers.Event.stop(evt);
+ };
+ var markerOut = function (evt) {
+ document.body.style.cursor='auto';
+ OpenLayers.Event.stop(evt);
+ };
+ marker.events.register('mousedown', feature, markerClick);
+ marker.events.register('mouseover', feature, markerOver);
+ marker.events.register('mouseout', feature, markerOut);
+ layerMarkers.addMarker(marker);
+ return feature;
+}
+
+/* update current detail panel with an AJAX request */
+function updateDetail(pk){
+ var uri = "/chimere/getDetail/" + pk;
+ OpenLayers.loadURL(uri, '', this, setDetail);
+}
+
+/* update the detail panel from an http response */
+function setDetail(response){
+ if (response.responseText.indexOf('no results') == -1) {
+ document.getElementById('detail_content').innerHTML = response.responseText;
+ }
+}
+
+/* hide content of detail panel */
+function hideDetail(){
+ document.getElementById('detail_content').innerHTML = '';
+}
+
+/* main initialisation function */
+function init(){
+ /* set the main map */
+ map = new OpenLayers.Map ('map', {
+ controls:[new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.PanZoomBar(),
+ new OpenLayers.Control.ScaleLine()],
+ maxExtent: maxExtent,
+ maxResolution: 156543.0399,
+ units: 'm',
+ projection: new OpenLayers.Projection('EPSG:4326')
+ });
+ /*projection: new OpenLayers.Projection('EPSG:900913'),
+ displayProjection: new OpenLayers.Projection('EPSG:4326')*/
+ map.addLayers([layerMapnik, cyclemap]);
+ loadMarkers();
+ map.setCenter(centerLonLat, 12);
+}
diff --git a/static/styles.css b/static/styles.css
new file mode 100644
index 0000000..a70c702
--- /dev/null
+++ b/static/styles.css
@@ -0,0 +1,150 @@
+body{
+background-color:#EEF;
+font-family:arial;
+font-size:80%;
+}
+
+fieldset{
+background-color:#FFF;
+-moz-border-radius: 10px;
+-webkit-border-radius: 10px;
+border-radius: 10px;
+}
+
+legend{
+font-weight:bold;
+color:purple;
+}
+
+h2{
+font-size:16px;
+text-align:center;
+margin:0;
+margin-bottom:10px;
+padding:0;
+width:100%;
+background-color:white;
+border:solid 1px purple;
+-moz-border-radius: 4px;
+-webkit-border-radius: 4px;
+border-radius: 4px;
+}
+
+hr.spacer{
+clear:both;
+border:None;
+}
+
+.edit label{
+display:block;
+}
+
+ul#action{
+background-color:#EEF;
+list-style-type:none;
+margin:0;
+padding:3px;
+padding-left:0;
+border-bottom:1px solid black;
+}
+
+#action li{
+display:inline;
+padding:3px;
+margin-right:6px;
+border:1px solid black;
+-moz-border-radius: 4px 4px 0 0;
+-webkit-border-radius: 4px 4px 0 0;
+border-radius: 4px 4px 0 0;
+}
+
+#action li#selected{
+background-color:#FFF;
+border-top:solid 2px purple;
+}
+
+#action a{
+text-decoration:None;
+color:black;
+}
+
+#panel{
+padding:6px 10px;
+border: 1px solid black;
+height:200px;
+position:absolute;
+z-index:5;
+top:50px;
+bottom:20px;
+right:18px;
+width:250px;
+background-color:#FFF;
+opacity:0.8;
+-moz-border-radius:10px;
+-webkit-border-radius:10px;
+border-radius:10px;
+}
+
+#detail{
+padding:6px 10px;
+border: 1px solid black;
+position:absolute;
+z-index:5;
+top:274px;
+bottom:18px;
+right:18px;
+width:250px;
+background-color:#FFF;
+opacity:0.8;
+-moz-border-radius:10px;
+-webkit-border-radius:10px;
+border-radius:10px;
+}
+
+#detail_content{
+overflow:auto;
+height:90%;
+}
+
+#map{
+border: 1px solid black;
+position:absolute;
+top:40px;
+bottom:8px;
+left:8px;
+right:8px;
+}
+
+ul#categories{
+margin:0;
+padding:0;
+overflow:auto;
+height:140px;
+}
+
+ul#categories li{
+font-variant:small-caps;
+list-style:none;
+}
+
+ul#categories li li{
+font-variant:normal;
+}
+
+ul#categories ul{
+margin:0;
+padding:0;
+}
+
+ul.subcategories img{
+height:20px;
+}
+
+.errorlist{
+color:purple;
+font-weight:bold;
+}
+
+.fieldWrapper{
+padding:6px;
+} \ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
new file mode 100644
index 0000000..3f4e0ac
--- /dev/null
+++ b/templates/base.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>{% block title %}Chimère{% endblock %}</title>
+ <link rel="stylesheet" href="{{ media_path }}styles.css" />
+ <script type="text/javascript"><!--
+ var media_path = '{{ media_path }}';
+ // --></script>
+ {% block extra_head %}{{extra_head|safe}}{% endblock %}
+</head>
+
+<body>
+ <div id="topbar">
+ <ul id='action'>
+{% for action in actions %}
+ <li{% ifequal action.id action_selected %} id='selected'{% endifequal %}><a href='/{{ action.path }}'>{{ action.label }}</a></li>
+{% endfor %}
+ </ul>
+ </div>
+ <div id="sidebar">
+ {% block sidebar %}{% endblock %}
+ </div>
+ <div id="content">
+ {% block content %}{% endblock %}
+ </div>
+</body>
+</html>
+
diff --git a/templates/detail.html b/templates/detail.html
new file mode 100644
index 0000000..465d775
--- /dev/null
+++ b/templates/detail.html
@@ -0,0 +1,7 @@
+{% load i18n %}
+<h3>{{marker.name}}</h3>
+<div>{%trans marker.subcategory.name%}</div>
+{% if marker.picture %}<img src='{{media_path}}{{marker.picture}}' alt='{{marker.name}}'/>{%endif%}
+<div>{% for property in marker.getProperties %}
+<p id='{{property.propertymodel.getNamedId}}'>{{ property.value }}</p>
+{% endfor %}</div> \ No newline at end of file
diff --git a/templates/edit.html b/templates/edit.html
new file mode 100644
index 0000000..9d3ee8c
--- /dev/null
+++ b/templates/edit.html
@@ -0,0 +1,51 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% block sidebar %}
+{% endblock %}
+
+{% block content %}
+{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
+<fieldset class='edit'>
+<legend>{% trans "Add a new site" %}</legend>
+<p>* {% trans "indicates a mandatory field" %}</p>
+<form enctype="multipart/form-data" method='post' action='/chimere/edit/'>
+<div class="fieldWrapper">
+ <label for="id_name">{% trans "Site name"%} *</label>
+ {{ form.name.errors }}
+ {{ form.name }}
+</div>
+<div class="fieldWrapper">
+ <label for="id_subcategory">{% trans "Category" %} *</label>
+ {{ form.subcategory.errors }}
+ <select name='subcategory' id='subcategory'>
+ <option value="">---------</option>
+ {% for cat_subcat in sub_categories %}
+ <optgroup label="{{cat_subcat.0.name}}">
+ {% for sub_category in cat_subcat.1 %}
+ <option value='{{sub_category.id}}'{% ifequal sub_category.id current_category %} selected='selected'{% endifequal %}>
+ {% trans sub_category.name %}
+ </option>{% endfor %}
+ </optgroup>{% endfor %}
+ </select>
+</div>
+<div class="fieldWrapper">
+ <label for="id_point">{% trans "Point"%} *</label>
+ {%if form.point.errors %}<ul class="errorlist"><li>{% trans "Select a location for this new site" %}</li></ul>{%endif%}
+ {{form.point}}
+</div>
+<div class="fieldWrapper">
+ <label for="id_picture">{% trans "Image" %}</label>
+ {{ form.picture.errors }}
+ {{ form.picture }}
+</div>
+{%for field in form%}{%for property in properties%}{%ifequal field.name property%}
+<div class="fieldWrapper">
+ <label for="id_{{field.name}}">{% trans field.label %}</label>
+ {{ field.errors }}
+ {{ field }}
+</div>
+{%endifequal%}{%endfor%}{%endfor%}
+<p><input type='submit' value="{% trans 'Propose'%}"/></p>
+</form>
+</fieldset>
+{% endblock %}
diff --git a/templates/main_map.html b/templates/main_map.html
new file mode 100644
index 0000000..3f43131
--- /dev/null
+++ b/templates/main_map.html
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% block sidebar %}
+<div id='panel'>
+<form method='post' name='frm_categories' id='frm_categories'>
+<ul id='categories'>{% for category, lst_sub_categories in sub_categories %}
+ <li>{% trans category.name %}
+ <ul class='subcategories'>{% for sub_category in lst_sub_categories %}
+ <li><input type='checkbox' onclick='loadMarkers()' name='category_{{sub_category.id}}' id='{{sub_category.id}}'{% if sub_category.selected %} checked='checked'{% endif %}/> <label for='{{sub_category.id}}'><img alt='{{sub_category.name}}' src='{{media_path}}{{sub_category.icon.image}}'/> {% trans sub_category.name %}</label></li>{% endfor %}
+ </ul>
+ </li>{% endfor %}
+</ul>
+</form>
+</div>
+<div id='detail'>
+<h2>{% trans "Detail" %}</h2>
+<div id='detail_content'>
+</div>
+</div>{% endblock %}
+{% block content %}<div id='map'></div>
+<script type='text/javascript'><!--
+init();
+// --> </script>{% endblock %}
diff --git a/templates/submited.html b/templates/submited.html
new file mode 100644
index 0000000..824bd7f
--- /dev/null
+++ b/templates/submited.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% block content %}
+<fieldset class='edit'>
+<legend>{% trans "Add a new site" %}</legend>
+<p>{% trans "Your proposition has been submited. A moderator will treat your submission shortly. Thanks!" %}</p>
+</fieldset>
+{% endblock %}
+
diff --git a/urls.py b/urls.py
new file mode 100644
index 0000000..0f45476
--- /dev/null
+++ b/urls.py
@@ -0,0 +1,26 @@
+from django.conf.urls.defaults import *
+
+from django.contrib import admin
+admin.autodiscover()
+
+from settings import ROOT_PATH
+
+urlpatterns = patterns('',
+ # Example:
+ # (r'^chimere/', include('chimere.foo.urls')),
+ (r'^chimere/admin/(.*)', admin.site.root),
+ (r'^chimere/$', 'chimere.main.views.index'),
+ (r'^chimere/edit/$', 'chimere.main.views.edit'),
+ (r'^chimere/submited/$', 'chimere.main.views.submited'),
+ (r'^chimere/getDetail/(?P<marker_id>\d+)/$',
+ 'chimere.main.views.getDetail'),
+ (r'^chimere/getMarkers/(?P<category_ids>\w+)/$',
+ 'chimere.main.views.getMarkers'),
+ (r'^chimere/static/(?P<path>.*)$', 'django.views.static.serve',
+ {'document_root': 'static/'}),
+ (r'^chimere/static/(?P<path>.*)$', 'django.views.static.serve',
+ {'document_root': ROOT_PATH + 'static/'}),
+ (r'^chimere/media/(?P<path>.*)$', 'django.views.static.serve',
+ {'document_root': ROOT_PATH + 'media/'}),
+
+)