diff options
Diffstat (limited to 'chimere')
35 files changed, 3476 insertions, 0 deletions
diff --git a/chimere/__init__.py b/chimere/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/chimere/__init__.py diff --git a/chimere/initial_data.json b/chimere/initial_data.json new file mode 100644 index 0000000..b88b17b --- /dev/null +++ b/chimere/initial_data.json @@ -0,0 +1,53 @@ +[{"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": 43, "model": "auth.permission", "fields": {"codename": "add_area", "name": "Can add Area", "content_type": 15}}, + {"pk": 28, "model": "auth.permission", "fields": {"codename": "add_category", "name": "Can add Category", "content_type": 10}}, + {"pk": 31, "model": "auth.permission", "fields": {"codename": "add_icon", "name": "Can add Icon", "content_type": 11}}, + {"pk": 37, "model": "auth.permission", "fields": {"codename": "add_marker", "name": "Can add Point of interest", "content_type": 13}}, + {"pk": 25, "model": "auth.permission", "fields": {"codename": "add_news", "name": "Can add News", "content_type": 9}}, + {"pk": 49, "model": "auth.permission", "fields": {"codename": "add_property", "name": "Can add Property", "content_type": 17}}, + {"pk": 46, "model": "auth.permission", "fields": {"codename": "add_propertymodel", "name": "Can add Property model", "content_type": 16}}, + {"pk": 40, "model": "auth.permission", "fields": {"codename": "add_route", "name": "Can add Route", "content_type": 14}}, + {"pk": 34, "model": "auth.permission", "fields": {"codename": "add_subcategory", "name": "Can add Subcategory", "content_type": 12}}, + {"pk": 44, "model": "auth.permission", "fields": {"codename": "change_area", "name": "Can change Area", "content_type": 15}}, + {"pk": 29, "model": "auth.permission", "fields": {"codename": "change_category", "name": "Can change Category", "content_type": 10}}, + {"pk": 32, "model": "auth.permission", "fields": {"codename": "change_icon", "name": "Can change Icon", "content_type": 11}}, + {"pk": 38, "model": "auth.permission", "fields": {"codename": "change_marker", "name": "Can change Point of interest", "content_type": 13}}, + {"pk": 26, "model": "auth.permission", "fields": {"codename": "change_news", "name": "Can change News", "content_type": 9}}, + {"pk": 50, "model": "auth.permission", "fields": {"codename": "change_property", "name": "Can change Property", "content_type": 17}}, + {"pk": 47, "model": "auth.permission", "fields": {"codename": "change_propertymodel", "name": "Can change Property model", "content_type": 16}}, + {"pk": 41, "model": "auth.permission", "fields": {"codename": "change_route", "name": "Can change Route", "content_type": 14}}, + {"pk": 35, "model": "auth.permission", "fields": {"codename": "change_subcategory", "name": "Can change Subcategory", "content_type": 12}}, + {"pk": 45, "model": "auth.permission", "fields": {"codename": "delete_area", "name": "Can delete Area", "content_type": 15}}, + {"pk": 30, "model": "auth.permission", "fields": {"codename": "delete_category", "name": "Can delete Category", "content_type": 10}}, + {"pk": 33, "model": "auth.permission", "fields": {"codename": "delete_icon", "name": "Can delete Icon", "content_type": 11}}, + {"pk": 39, "model": "auth.permission", "fields": {"codename": "delete_marker", "name": "Can delete Point of interest", "content_type": 13}}, + {"pk": 27, "model": "auth.permission", "fields": {"codename": "delete_news", "name": "Can delete News", "content_type": 9}}, + {"pk": 51, "model": "auth.permission", "fields": {"codename": "delete_property", "name": "Can delete Property", "content_type": 17}}, + {"pk": 48, "model": "auth.permission", "fields": {"codename": "delete_propertymodel", "name": "Can delete Property model", "content_type": 16}}, + {"pk": 42, "model": "auth.permission", "fields": {"codename": "delete_route", "name": "Can delete Route", "content_type": 14}}, + {"pk": 36, "model": "auth.permission", "fields": {"codename": "delete_subcategory", "name": "Can delete Subcategory", "content_type": 12}}, + {"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": 1, "model": "auth.group", "fields": {"name": "Moderator", "permissions": [37, 40, 38, 41, 39, 42]}}, + {"pk": 2, "model": "auth.group", "fields": {"name": "Application administrator", "permissions": [43, 28, 31, 37, 25, 40, 34, 44, 29, 32, 38, 26, 41, 35, 45, 30, 33, 39, 27, 42, 36]}}] diff --git a/chimere/locale/fr/LC_MESSAGES/django.po b/chimere/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 0000000..a37d636 --- /dev/null +++ b/chimere/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,387 @@ +# 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. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-02-07 21:47+0100\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:32 +msgid "View" +msgstr "Voir" + +#: main/actions.py:33 +msgid "Contribute" +msgstr "Participer" + +#: main/actions.py:34 +msgid "Add a new point of interest" +msgstr "Ajout d'un point remarquable" + +#: main/actions.py:35 templates/edit_route.html:8 +msgid "Add a new route" +msgstr "Ajout d'un nouveau trajet" + +#: main/actions.py:37 +msgid "Contact us" +msgstr "Nous contacter" + +#: main/forms.py:53 +msgid "New submission for" +msgstr "Nouvelle proposition pour" + +#: main/forms.py:54 +#, python-format +msgid "The new item \"%s\" has been submited in the category: " +msgstr "Le nouvel élément « %s » a été proposé dans la catégorie : " + +#: main/forms.py:56 +msgid "To valid, precise or unvalid this item: " +msgstr "Pour valider, préciser ou rejeter cet élément : " + +#: main/forms.py:65 +msgid "Email (optional)" +msgstr "Courriel (optionnel) " + +#: main/forms.py:66 +msgid "Object" +msgstr "Objet" + +#: main/forms.py:226 main/models.py:282 +msgid "Area" +msgstr "Zone" + +#: main/models.py:35 main/models.py:48 main/models.py:69 main/models.py:82 +#: main/models.py:94 main/models.py:133 main/models.py:201 main/models.py:268 +#: main/models.py:293 +msgid "Name" +msgstr "Nom" + +#: main/models.py:36 main/models.py:70 main/models.py:95 main/models.py:139 +#: main/models.py:207 main/models.py:270 main/models.py:295 +msgid "Available" +msgstr "Disponible" + +#: main/models.py:37 +msgid "Date" +msgstr "Date" + +#: main/models.py:43 +msgid "News" +msgstr "Nouvelle" + +#: main/models.py:52 main/models.py:59 main/models.py:97 +msgid "Color theme" +msgstr "Thème de couleur" + +#: main/models.py:57 +msgid "Code" +msgstr "Code" + +#: main/models.py:58 main/models.py:71 main/models.py:99 main/models.py:269 +#: main/models.py:294 +msgid "Order" +msgstr "Ordre" + +#: main/models.py:64 +msgid "Color" +msgstr "Couleur" + +#: main/models.py:77 main/models.py:93 templates/edit.html:18 +#: templates/edit_route.html:17 +msgid "Category" +msgstr "Catégorie" + +#: main/models.py:83 main/models.py:136 main/models.py:204 +#: templates/edit.html:37 templates/edit_route.html:37 +msgid "Image" +msgstr "Image" + +#: main/models.py:88 main/models.py:96 +msgid "Icon" +msgstr "Icone" + +#: main/models.py:100 +msgid "Marker" +msgstr "Point d'intérêt" + +#: main/models.py:101 main/models.py:203 main/models.py:220 +#: templates/edit_route.html:31 +msgid "Route" +msgstr "Trajet" + +#: main/models.py:102 +msgid "Both" +msgstr "Mixte" + +#: main/models.py:103 +msgid "Item type" +msgstr "Type d'élément" + +#: main/models.py:108 main/models.py:134 main/models.py:202 +msgid "Subcategory" +msgstr "Sous-catégorie" + +#: main/models.py:135 +msgid "Localisation" +msgstr "Localisation" + +#: main/models.py:138 main/models.py:206 +msgid "Submited" +msgstr "Soumis" + +#: main/models.py:140 main/models.py:208 +msgid "Disabled" +msgstr "Désactivé" + +#: main/models.py:144 main/models.py:212 +msgid "Status" +msgstr "État" + +#: main/models.py:152 main/models.py:317 +msgid "Point of interest" +msgstr "Point d'intérêt" + +#: main/models.py:271 +msgid "Upper left corner" +msgstr "Coin en haut à gauche" + +#: main/models.py:273 +msgid "Lower right corner" +msgstr "Coin en bas à droite" + +#: main/models.py:296 +msgid "Text" +msgstr "Texte" + +#: main/models.py:297 +msgid "Long text" +msgstr "Texte long" + +#: main/models.py:298 +msgid "Password" +msgstr "Mot de passe" + +#: main/models.py:302 +msgid "Type" +msgstr "Type" + +#: main/models.py:307 main/models.py:319 +msgid "Property model" +msgstr "Modèle de propriété" + +#: main/models.py:320 +msgid "Value" +msgstr "Valeur" + +#: main/models.py:324 +msgid "Property" +msgstr "Propriété" + +#: main/views.py:193 +msgid "Comments/request on the map" +msgstr "Commentaires/requètes sur la carte" + +#: main/views.py:196 +msgid "" +"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." +msgstr "" +"Merci pour votre contribution. Elle va être prise en compte. Si vous " +"avez laissé votre courriel vous serez peut-être contacté bientôt pour plus de " +"détails." + +#: main/views.py:200 +msgid "Temporary error. Renew your message later." +msgstr "Erreur temporaire. Réenvoyez votre message plus tard." + +#: main/widgets.py:109 +msgid "Latitude" +msgstr "Latitude" + +#: main/widgets.py:109 +msgid "Longitude" +msgstr "Longitude" + +#: main/widgets.py:133 +msgid "Invalid point" +msgstr "Point invalide" + +#: main/widgets.py:159 +msgid "Creation mode" +msgstr "Mode création" + +#: main/widgets.py:160 +msgid "" +"To start drawing the route click on the toggle button : \"Start drawing\"." +msgstr "" +"Pour commencer le dessin cliquez sur le bouton : « Commencer le tracé » " + +#: main/widgets.py:161 +msgid "Then click on the map to begin the drawing." +msgstr "Puis cliquez sur la carte pour commencer le dessin." + +#: main/widgets.py:162 +msgid "You can add points by clicking again." +msgstr "Vous pouvez ajouter des points en cliquant de nouveau." + +#: main/widgets.py:163 +msgid "" +"To finish the drawing double click. When the drawing is finished you can " +"edit it." +msgstr "" +"Pour finir le tracé double-cliquez. Quand le tracé est fini vous pouvez " +"toujours l'éditer." + +#: main/widgets.py:165 +msgid "" +"While creating to undo a drawing click again on the toggle button \"Stop " +"drawing\"." +msgstr "" +"En mode création vous pouvez annuler un tracé en appuyant sur le bouton « " +"Arrêter le tracé »" + +#: main/widgets.py:170 +msgid "Modification mode" +msgstr "Mode modification" + +#: main/widgets.py:171 +msgid "To move a point click on it and drag it to the desired position." +msgstr "" +"Pour bouger un point, cliquez dessus, maintenez le click pour le déposer à " +"la position désirée" + +#: main/widgets.py:172 +msgid "" +"To delete a point move the mouse cursor over it and press the \"d\" key." +msgstr "" +"Pour supprimer un point, mettez le curseur de la souris sur celui-ci et " +"appuyez sur le touche « d »" + +#: main/widgets.py:173 +msgid "" +"To add a point click in the middle of a segment and drag the new point to " +"the desired position" +msgstr "" +"Pour ajouter un nouveau point, cliquez au milieu d'un des segments, " +"maintenez le bouton appuyé et déplacez le nouveau point à la position " +"désirée." + +#: main/widgets.py:182 +msgid "Start drawing" +msgstr "Commencer le tracé" + +#: main/widgets.py:182 +msgid "Stop drawing" +msgstr "Arrêter le tracé" + +#: templates/base.html:37 +msgid "This site uses Chimère" +msgstr "Ce site utilise Chimère" + +#: templates/category_detail.html:6 templates/welcome.html:15 +msgid "Close" +msgstr "Fermer" + +#: templates/contactus.html:11 +msgid "" +"If you have some requests or remarks about this site you can leave them here." +msgstr "Si vous avez des requètes, des remarques à propos de ce site vous " +"pouvez nous laisser un commentaire ici." + +#: templates/contactus.html:14 +msgid "Submit" +msgstr "Proposer" + +#: templates/edit.html:9 +msgid "Add a new site" +msgstr "Ajouter un nouveau site" + +#: templates/edit.html:10 templates/edit_route.html:9 +msgid "indicates a mandatory field" +msgstr "indique un champ obligatoire" + +#: templates/edit.html:13 templates/edit_route.html:12 +msgid "Site name" +msgstr "Nom du site" + +#: templates/edit.html:32 +msgid "Point" +msgstr "Point" + +#: templates/edit.html:33 templates/edit_route.html:32 +msgid "Select a location for this new site" +msgstr "Choisissez une localisation pour ce nouveau site" + +#: templates/edit.html:48 templates/edit_route.html:48 +msgid "Propose" +msgstr "Proposez" + +#: templates/main_map.html:6 +msgid "Topics" +msgstr "Thèmes" + +#: templates/main_map.html:16 templates/main_map.html.py:21 +#: templates/main_map.html:33 +msgid "Zoom to" +msgstr "Zoomer sur" + +#: templates/main_map.html:22 +msgid "Tell me more..." +msgstr "En savoir plus..." + +#: templates/main_map.html:25 +msgid "Display markers and routes waiting for validation" +msgstr "" +"Afficher les points remarquables et les trajets en attente de validation" + +#: templates/main_map.html:31 +msgid "Shortcuts" +msgstr "Raccourcis" + +#: templates/main_map.html:42 +msgid "Welcome message" +msgstr "Message d'accueil" + +#: templates/main_map.html:47 +msgid "Permalink" +msgstr "Lien permanent" + +#: templates/main_map.html:57 +msgid "Map" +msgstr "Carte" + +#: templates/submited.html:7 +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 !" + +#: templates/welcome.html:3 +msgid "Welcome to Chimère" +msgstr "Bienvenue dans Chimère" + +#: templates/welcome.html:5 +msgid "" +"This is the default message. You can overload it by modifying the file " +"welcome.html in the template directory of Chimère. Below this message all " +"news message will be displayed. You can add them in administration pages." +msgstr "" +"Ceci est le message par défaut. Vous pouvez le surcharger en modifiant le " +"fichier welcome.html dans le dossier de patrons de Chimère. En dessous de ce " +"message toutes les nouvelles vont être affichées. Vous pouvez les ajouter " +"dans les pages d'administration." + +#~ msgid "Description" +#~ msgstr "Description" diff --git a/chimere/locale/fr/LC_MESSAGES/djangojs.po b/chimere/locale/fr/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000..84823e0 --- /dev/null +++ b/chimere/locale/fr/LC_MESSAGES/djangojs.po @@ -0,0 +1,21 @@ +# 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-11-23 15:35+0100\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" + +#: static/main_map.js:104 +msgid "Show details" +msgstr "Voir le détail" 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 diff --git a/chimere/manage.py b/chimere/manage.py new file mode 100755 index 0000000..bcdd55e --- /dev/null +++ b/chimere/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/chimere/settings.py b/chimere/settings.py new file mode 100644 index 0000000..36c6858 --- /dev/null +++ b/chimere/settings.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Django settings for chimere project. +PROJECT_NAME = u'Chimère' + +ROOT_PATH = '/var/local/django/chimere/' + +SERVER_URL = "http://www.peacefrogs.net/" +EXTRA_URL = 'chimere/' +BASE_URL = SERVER_URL + EXTRA_URL +EMAIL_HOST = 'localhost' + +TINYMCE_URL = SERVER_URL + 'tinymce/' + +# chimere specific +DEFAULT_CENTER = (-1.679444, 48.114722) +EPSG_PROJECTION = 900913 +EPSG_DISPLAY_PROJECTION = 4326 +# if you want to restrict the map to a defined bounding box set it here +RESTRICTED_EXTENT = None + +# default id category to check on the map +DEFAULT_CATEGORIES = [1] + +# JS definition of the main map cf. OpenLayers documentation for more details +#MAP_LAYER = '''new OpenLayers.Layer.OSM.CycleMap("Cycle map", { +#displayOutsideMaxExtent: true, wrapDateLine: true})''' # OSM cyclemap +MAP_LAYER = "new OpenLayers.Layer.OSM.Mapnik('Mapnik')" # OSM mapnik map + +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 = '/' + EXTRA_URL + '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 = '/' + EXTRA_URL + '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', +) diff --git a/chimere/static/base.js b/chimere/static/base.js new file mode 100644 index 0000000..cabdf2a --- /dev/null +++ b/chimere/static/base.js @@ -0,0 +1,63 @@ +/* base function shared by some pages */ +/* Copyright (C) 2009 É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. +*/ + +/* show a block panel */ +function show(id){ + document.getElementById(id).style.display = 'block'; +} + +/* hide a panel */ +function hide(id){ + document.getElementById(id).style.display = 'None'; +} + +function saveExtent() { + /* save the current extent in a cookie */ + if(!map) return; + document.cookie = "MAP_EXTENT=" + map.getExtent().toArray().join('_') + + ';path="/"'; +} + +function getExtent() { + /* get the current extent from a cookie */ + var cookies = document.cookie.split(';'); + var map_extent; + for (i in cookies){ + var items = cookies[i].split('='); + key = items[0].split(' ').join(''); + if (key == 'MAP_EXTENT'){ + map_extent = items[1].split('_'); + } + } + return map_extent; +} + +function zoomToCurrentExtent(map){ + /* zoom to current extent */ + var current_extent = getExtent(); + if (OpenLayers && current_extent && current_extent.length == 4){ + extent = new OpenLayers.Bounds(current_extent[0], current_extent[1], + current_extent[2], current_extent[3]); + map.zoomToExtent(extent, true); + return true; + } + else{ + return; + } +}
\ No newline at end of file diff --git a/chimere/static/edit_area.js b/chimere/static/edit_area.js new file mode 100644 index 0000000..4daccf3 --- /dev/null +++ b/chimere/static/edit_area.js @@ -0,0 +1,51 @@ +/* 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. +*/ + +/* area edit */ + +var map; + +/* update form fields on zoom action */ +function updateForm(){ + var bounds = map.getExtent(); + document.getElementById('upper_left_lat').value = bounds.top; + document.getElementById('upper_left_lon').value = bounds.left; + document.getElementById('lower_right_lat').value = bounds.bottom; + document.getElementById('lower_right_lon').value = bounds.right; +} + +/* main initialisation function */ +function init(){ + map = new OpenLayers.Map ('map_edit', { + controls:[new OpenLayers.Control.Navigation(), + new OpenLayers.Control.PanPanel(), + new OpenLayers.Control.ZoomPanel(), + new OpenLayers.Control.Attribution()], + maxResolution: 156543.0399, + units: 'm', + projection: epsg_projection, + displayProjection: epsg_display_projection + } ); + map.addLayers([map_layer]); + map.events.register('zoomend', map, updateForm); + map.events.register('moveend', map, updateForm); + /* zoom to the appropriate extent */ + if (!zoomToCurrentExtent(map)){ + map.setCenter(centerLonLat, 12); + } +} diff --git a/chimere/static/edit_map.js b/chimere/static/edit_map.js new file mode 100644 index 0000000..8f37f6b --- /dev/null +++ b/chimere/static/edit_map.js @@ -0,0 +1,146 @@ +/* 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. +*/ + +/* map edit */ + +var map; + +/* availaible map layers */ +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; + var lonlat = layerMarkers.getLonLatFromViewPortPx(event.xy); + putMarker(lonlat, false); + OpenLayers.Event.stop(event); +} + +/* put the marker on the map and update latitude and longitude fields */ +var putMarker = function (lonlat, zoom){ + 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; + /*zoom to the point*/ + if (zoom){ + var bounds = layerMarkers.getDataExtent(); + if (bounds) map.zoomToExtent(bounds); + } + return; +} + +/* main initialisation function */ +function init(){ + var options = { + controls:[new OpenLayers.Control.Navigation(), + new OpenLayers.Control.PanPanel(), + new OpenLayers.Control.ZoomPanel(), + new OpenLayers.Control.Attribution()], + maxResolution: 156543.0399, + units: 'm', + projection: epsg_projection, + displayProjection: epsg_display_projection + }; + if (restricted_extent){options['restrictedExtent'] = restricted_extent;} + map = new OpenLayers.Map ('map_edit', options); + layerMarkers.setOpacity(0.5); + map.addLayers([map_layer, layerMarkers]); + map.events.register('click', map, setMarker); + /* zoom to the appropriate extent */ + if (!zoomToCurrentExtent(map)){ + 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/chimere/static/edit_route_map.js b/chimere/static/edit_route_map.js new file mode 100644 index 0000000..dcf7c84 --- /dev/null +++ b/chimere/static/edit_route_map.js @@ -0,0 +1,121 @@ +/* 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. +*/ + +/* map edit */ + +var map; +var currentControl; + +/* availaible map layers */ +var vectors = new OpenLayers.Layer.Vector("Vector Layer"); + +vectors.events.on({ + "featuremodified": updateForm, + "featureadded": featureCreated +}); + +var currentFeature; +function featureCreated(event) { + /* toggle to edition mode */ + pathCreate.deactivate(); + currentControl = pathModify; + var help_route_create = document.getElementById('help-route-create'); + if (help_route_create){ + help_route_create.style.display = 'None'; + } + document.getElementById('help-route-modify').style.display = 'block'; + + pathModify.activate(); + updateForm(event); + pathModify.selectControl.select(event.feature); + +} + +function initFeature(json_geometry){ + var json = new OpenLayers.Format.JSON(); + var polyline = json.read(json_geometry); + var point_array = new Array(); + for (i=0; i<polyline.coordinates.length; i++){ + var point = new OpenLayers.Geometry.Point(polyline.coordinates[i][0], + polyline.coordinates[i][1]); + point_array.push(point); + } + var linestring = new OpenLayers.Geometry.LineString(point_array); + currentFeature = new OpenLayers.Feature.Vector(); + currentFeature.geometry = linestring; + vectors.addFeatures([currentFeature]); + currentControl = pathModify; + /*zoom to the route*/ + var bounds = vectors.getDataExtent(); + if (bounds) map.zoomToExtent(bounds); +} + +function updateForm(event) { + /* update the form */ + currentFeature = event.feature; + document.getElementById('id_route').value = currentFeature.geometry; +} + + +/* path control */ +var pathCreate = new OpenLayers.Control.DrawFeature(vectors, + OpenLayers.Handler.Path); +var currentControl = pathCreate; + +var pathModify = new OpenLayers.Control.ModifyFeature(vectors, + {clickout:false, toggle:false}); + +/* main initialisation function */ +function init(){ + var options = { + controls:[new OpenLayers.Control.Navigation(), + new OpenLayers.Control.PanPanel(), + new OpenLayers.Control.ZoomPanel(), + new OpenLayers.Control.Attribution(), + pathCreate, + pathModify], + maxResolution: 156543.0399, + units: 'm', + projection: epsg_projection, + displayProjection: epsg_display_projection, + }; + if (restricted_extent){options['restrictedExtent'] = restricted_extent;} + map = new OpenLayers.Map('map_edit', options); + + vectors.setOpacity(0.5); + map.addLayers([map_layer, vectors]); + /* zoom to the appropriate extent */ + if (!zoomToCurrentExtent(map)){ + map.setCenter(centerLonLat, 12); + } +} + +function toggleDrawOn() { + currentControl.activate(); + document.getElementById('draw-toggle-off').style.display = 'None'; + document.getElementById('draw-toggle-on').style.display = 'block'; + if (currentFeature){ + pathModify.selectControl.select(currentFeature); + } +} + +function toggleDrawOff() { + currentControl.deactivate(); + document.getElementById('draw-toggle-on').style.display = 'None'; + document.getElementById('draw-toggle-off').style.display = 'block'; +} diff --git a/chimere/static/forms.css b/chimere/static/forms.css new file mode 100644 index 0000000..c8e4939 --- /dev/null +++ b/chimere/static/forms.css @@ -0,0 +1,49 @@ +#map_edit{ +border: 1px solid black; +width:700px; +height:400px; +float:left; +} + +#live_lonlat{ +margin:1em; +float:left; +} + +.toggle-button{ +float:left; +padding:4px; +-moz-border-radius: 4px; +-webkit-border-radius: 4px; +border-radius: 4px; +border:1px solid grey; +} + +#draw-toggle-off{ +color:black; +} + +#draw-toggle-on{ +background-color:lightgrey; +display:None; +} + +.help-route{ +width:700px; +background-color:#EEF;; +margin: 10px 0px; +float:left; +padding:0 4px; +-moz-border-radius: 4px; +-webkit-border-radius: 4px; +border-radius: 4px; +border:1px solid grey; +} + +#help-route-create{ +} + +#help-route-modify{ +display:None +} + diff --git a/chimere/static/icons/minus.png b/chimere/static/icons/minus.png Binary files differnew file mode 100644 index 0000000..a95822f --- /dev/null +++ b/chimere/static/icons/minus.png diff --git a/chimere/static/icons/plus.png b/chimere/static/icons/plus.png Binary files differnew file mode 100644 index 0000000..404a24a --- /dev/null +++ b/chimere/static/icons/plus.png diff --git a/chimere/static/icons/zoom.png b/chimere/static/icons/zoom.png Binary files differnew file mode 100644 index 0000000..3a53680 --- /dev/null +++ b/chimere/static/icons/zoom.png diff --git a/chimere/static/main_map.js b/chimere/static/main_map.js new file mode 100644 index 0000000..1849713 --- /dev/null +++ b/chimere/static/main_map.js @@ -0,0 +1,413 @@ +/* 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. +*/ + +/* main map */ + +var current_cat; + +/* open a category section */ +function toggleCategory(id){ + old = document.getElementById('maincategory_' + current_cat) + if(old){ + old.style.display = 'None'; + old_img = document.getElementById('maincategory_img_' + current_cat); + old_img.src = "/" + extra_url + "static/icons/plus.png"; + } + if (id != current_cat){ + current_cat = id; + document.getElementById('maincategory_' + current_cat).style.display = 'block'; + img = document.getElementById('maincategory_img_' + current_cat); + img.src = "/" + extra_url + "static/icons/minus.png"; + } else { + current_cat = 0; + } +} + +/* check all the categories if clicked, unckeck if unclick */ +function checkAll(item, ids){ + check = false; + if(item.checked == true){ + check = true; + } + for (i in ids){ + document.getElementById('category_'+ids[i]).checked = check; + } +} + +var map; +var permalink; + +/* 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 layerVectors; + +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; + /* hide on click on the cloud */ + currentPopup.groupDiv.onclick = hidePopUp; +} + +/* check checked categories */ +var checked_categories; +var display_submited = false; + +function updateCheckedCategories(){ + /* get checked categories */ + inputs = window.document.forms["frm_categories"]; + checked_categories = ''; + display_submited = false; + for (var i = 0; i < inputs.length; i++) { + input = inputs[i]; + // 'category_'.length : 9 + if (input.checked + && input.name.substring(9, 0) == 'category_'){ + id = input.name.substring(9, input.name.length); + if(checked_categories) checked_categories += '_'; + checked_categories += id; + } + if (input.checked && input.name == 'display_submited'){ + display_submited = true; + } + } + permalink.updateLink(); +} + +/* load marker and route layer from a JSON feature string */ +function loadLayersFromJSON(layer_markers, layer_vectors, geo_objects){ + for (var i = 0; i < geo_objects.features.length; i++) { + var feature = geo_objects.features[i]; + if (feature.geometry.type == 'Point'){ + putMarker(layer_markers, feature); + } else if (feature.geometry.type == 'LineString') { + putRoute(layer_vectors, feature); + } + } +} + +/* zoom to an area */ +function zoomToArea(top, left, bottom, right){ + var bounds = new OpenLayers.Bounds(left, bottom, right, top); + map.zoomToExtent(bounds, true); +} + +/* zoom to a desired category */ +function zoomToCategory(categorie_ids){ + updateCheckedCategories(); + /* 0 stand for all categories */ + var uri = "/" + extra_url + "getGeoObjects/" + categorie_ids; + if (display_submited) uri += "/A_S"; + OpenLayers.loadURL(uri, '', this, zoomToCategoryExtent); +} + +/* zoom to a selected category from an http response GeoJSON */ +function zoomToCategoryExtent(response){ + if (response.responseText.indexOf('no results') != -1) return; + var fakeLayerVectors = new OpenLayers.Layer.Vector("Fake vector layer"); + var fakeLayerMarkers = new OpenLayers.Layer.Markers('Fake POIs layer'); + var json = new OpenLayers.Format.JSON(); + var geo_objects = json.read(response.responseText); + /* load every geo object */ + loadLayersFromJSON(fakeLayerMarkers, fakeLayerVectors, geo_objects); + var bounds = fakeLayerMarkers.getDataExtent(); + if (bounds){ + bounds.extend(fakeLayerVectors.getDataExtent()); + } else { + bounds = fakeLayerVectors.getDataExtent(); + } + if(bounds){ + map.zoomToExtent(bounds); + } + fakeLayerMarkers.destroy(); + fakeLayerVectors.destroy(); +} + +/* load geo objects with an AJAX request */ +function loadGeoObjects(){ + updateCheckedCategories(); + /* 0 stand for all categories */ + if (!checked_categories) checked_categories = '0'; + var uri = "/" + extra_url + "getGeoObjects/" + checked_categories; + if (display_submited) uri += "/A_S"; + OpenLayers.loadURL(uri, '', this, setGeoObjects); +} + +/* update the marker and vector layers from an http response GeoJSON */ +function setGeoObjects(response){ + if(layerMarkers) layerMarkers.destroy(); + if(layerVectors) layerVectors.destroy(); + if (response.responseText.indexOf('no results') == -1) { + /* clean the marker layer */ + if (currentPopup) { + currentPopup.hide(); + hide('detail'); + } + layerVectors = new OpenLayers.Layer.Vector("Vector Layer"); + map.addLayer(layerVectors); + layerVectors.setOpacity(0.8); + layerMarkers = new OpenLayers.Layer.Markers('POIs'); + map.addLayer(layerMarkers); + layerMarkers.setOpacity(0.8); + + var json = new OpenLayers.Format.JSON(); + var geo_objects = json.read(response.responseText); + /* load every geo object */ + loadLayersFromJSON(layerMarkers, layerVectors, geo_objects); + /* + 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 route on the map */ +function putRoute(layer, route) { + var polyline = route.geometry; + var point_array = new Array(); + for (i=0; i<polyline.coordinates.length; i++){ + var point = new OpenLayers.Geometry.Point(polyline.coordinates[i][0], + polyline.coordinates[i][1]); + point_array.push(point); + } + var linestring = new OpenLayers.Geometry.LineString(point_array); + currentFeature = new OpenLayers.Feature.Vector(); + + var style = OpenLayers.Util.extend({}, + OpenLayers.Feature.Vector.style['default']); + style.strokeColor = route.properties.color; + style.strokeWidth = 3; + currentFeature.style = style; + currentFeature.geometry = linestring; + layer.addFeatures([currentFeature]); +} + + +/* put a marker on the map */ +function putMarker(layer, mark) { + /* initialise a new marker with appropriate attribute for setting a + marker */ + lat = mark.geometry.coordinates[1]; + lon = mark.geometry.coordinates[0]; + var size = new OpenLayers.Size(mark.properties.icon_width, + mark.properties.icon_height); + 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'>"; + feature.data.popupContentHTML += mark.properties.name; + feature.data.popupContentHTML += "</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; + hide('detail'); + } 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('click', feature, markerClick); + marker.events.register('mouseover', feature, markerOver); + marker.events.register('mouseout', feature, markerOut); + layer.addMarker(marker); + return feature; +} + +var hidePopUp = function (evt) { + if (clicked) { + currentPopup.hide(); + clicked = false; + hide('detail'); + } +} + +/* update current detail panel with an AJAX request */ +function updateDetail(pk){ + var uri = "/" + extra_url + "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').innerHTML = response.responseText; + show('detail'); + } +} + +/* show the detail of a category */ +function displayCategoryDetail(category_id) { + var uri = "/" + extra_url + "getDescriptionDetail/" + category_id; + OpenLayers.loadURL(uri, '', this, setCategoryDetail); +} + +/* update the category detail panel from an http response */ +function setCategoryDetail(response){ + if (response.responseText.indexOf('no results') == -1) { + document.getElementById('category_detail').innerHTML = + response.responseText; + show('category_detail'); + } +} + +/* new permalink createParams method - update when facilities are given to +personalize the permalink */ +function createParams(center, zoom, layers) { + center = center || this.map.getCenter(); + var params = OpenLayers.Util.getParameters(this.base); + // If there's still no center, map is not initialized yet. + // Break out of this function, and simply return the params from the + // base link. + if (center) { + //zoom + params.zoom = zoom || this.map.getZoom(); + //lon,lat + var lat = center.lat; + var lon = center.lon; + if (this.displayProjection) { + var mapPosition = OpenLayers.Projection.transform( + { x: lon, y: lat }, + this.map.getProjectionObject(), + this.displayProjection ); + lon = mapPosition.x; + lat = mapPosition.y; + } + params.lat = Math.round(lat*100000)/100000; + params.lon = Math.round(lon*100000)/100000; + //layers + layers = layers || this.map.layers; + params.layers = ''; + for (var i=0, len=layers.length; i<len; i++) { + var layer = layers[i]; + + if (layer.isBaseLayer) { + params.layers += (layer == this.map.baseLayer) ? "B" : "0"; + } else { + params.layers += (layer.getVisibility()) ? "T" : "F"; + } + } + /* only piece of code added */ + params.checked_categories = checked_categories; + params.display_submited = display_submited; + } + return params; +} + +/* main initialisation function */ +function init(){ + /* set the main map */ + var options = { + controls:[new OpenLayers.Control.Navigation(), + new OpenLayers.Control.PanPanel(), + new OpenLayers.Control.ZoomPanel(), + new OpenLayers.Control.ScaleLine()], + maxResolution: 156543.0399, + units: 'm', + projection: new OpenLayers.Projection('EPSG:4326'), + theme:null, + }; + if (restricted_extent){options['restrictedExtent'] = restricted_extent;} + map = new OpenLayers.Map('map', options); + permalink = new OpenLayers.Control.Permalink("permalink"); + permalink.createParams = createParams; + map.addControl(permalink); + // update with the translated permalink label + if(permalink_label && permalink.div && permalink.div.childNodes.length > 0){ + permalink.div.childNodes[0].textContent = permalink_label; + } + map.addLayers([map_layer]); + + map.events.register('click', map, hidePopUp); + + /* if from a permalink */ + if (p_zoom) { + var p_centerLonLat = new OpenLayers.LonLat(p_lon, p_lat); + map.setCenter(p_centerLonLat, p_zoom); + if (p_display_submited) { + document.getElementById('display_submited_check').checked = true; + } + if (p_checked_categories){ + /* ckeck selected categories and uncheck others */ + inputs = window.document.forms["frm_categories"]; + for (var i = 0; i < inputs.length; i++) { + input = inputs[i]; + if (input.name.substring(9, 0) == 'category_'){ + id = input.name.substring(9, input.name.length); + input.checked = false; + for (var cc=0; cc < p_checked_categories.length; cc++){ + if (p_checked_categories[cc] == id){ + input.checked = true; + } + } + } + } + } + } + /* if not zoom to the extent in cookies */ + else if (!zoomToCurrentExtent(map)){ + /* if no extent in cookies zoom to default */ + map.setCenter(centerLonLat, 13); + } + loadGeoObjects(); +} diff --git a/chimere/static/styles.css b/chimere/static/styles.css new file mode 100644 index 0000000..5e7e2cd --- /dev/null +++ b/chimere/static/styles.css @@ -0,0 +1,456 @@ +body{ +background-color:#b488ff; +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:#b400ff; +} + +a{ +color:#b400ff; +} + +h2{ +font-size:16px; +text-align:center; +margin:0; +margin-bottom:10px; +padding:0; +width:100%; +color:white; +background-color:#b400ff; +-moz-border-radius: 4px; +-webkit-border-radius: 4px; +border-radius: 4px; +} + +#areas h2, #panel h2{ +-moz-border-radius: 4px 4px 0 0; +-webkit-border-radius: 4px 4px 0 0; +border-radius: 4px 4px 0 0; +} + +h3{ +color:#b400ff; +} + +h4{ +color:#5e1e68; +font-weight:normal; +font-style:italic; +} + +hr.spacer{ +clear:both; +border:None; +visibility: hidden; +} + +fieldset{ +margin-top:8px; +} + +.edit label{ +display:block; +} + +ul#action{ +position:absolute; +z-index:5; +list-style-type:none; +top:20px; +left:80px; +margin:0; +padding:3px; +padding-left:0; +} + +#action li{ +font-size:15px; +display:inline; +padding:1px 5px; +margin-right:6px; +border:1px solid #888; +-moz-border-radius: 4px; +-webkit-border-radius: 4px; +border-radius: 4px; +background-color:#FFF; +} + +#action li.selected{ +background-color:#b400ff; +border-color:#b400ff; +color:white; +} + +#action a{ +text-decoration:None; +color:black; +} + +#action li ul{ +margin:8px 6px; +position:absolute; +width:600px; +} + +#action li.selected a{ +color:white; +} + +#action li.selected li a{ +color:black; +} + +#action li.selected li.selected a{ +color:white; +} + +#content{ +margin:4px; +margin-top:14px; +padding:20px; +padding-top:46px; +background-color:white; +-moz-border-radius: 10px; +-webkit-border-radius: 10px; +border-radius: 10px; +border:1px solid #888; +} + +#footer{ +text-align:center; +} + +#map-footer{ +position:absolute; +z-index:5; +background-color:white; +bottom:5px; +right:5px; +border:1px solid #888; +padding:2px; +} + +#panel{ +padding:0; +border:1px solid #888; +height:200px; +position:absolute; +z-index:5; +top:50px; +bottom:20px; +right:18px; +width:300px; +background-color:#FFF; +opacity:0.8; +-moz-border-radius:10px; +-webkit-border-radius:10px; +border-radius:10px; +} + +#areas{ +padding:0; +border:1px solid #888; +height:115px; +position:absolute; +z-index:5; +bottom:105px; +left:18px; +width:200px; +background-color:#FFF; +opacity:0.8; +-moz-border-radius:10px; +-webkit-border-radius:10px; +border-radius:10px; +overflow:auto; +} + +#areas ul{ +margin:0; +padding:0 10px; +} + +#areas li{ +list-style:none; +} + +#popup_link{ +text-align:center; +} + +#welcome{ +padding:6px 10px; +border:1px solid #888; +position:absolute; +z-index:5; +top:50px; +bottom:102px; +left:80px; +margin-right:360px; +background-color:#FFF; +opacity:0.9; +-moz-border-radius:10px; +-webkit-border-radius:10px; +border-radius:10px; +} + +#detail{ +display:None; +padding:6px 10px; +border:1px solid #888; +position:absolute; +z-index:5; +top:274px; +bottom:38px; +right:18px; +width:300px; +background-color:#FFF; +-moz-border-radius:10px; +-webkit-border-radius:10px; +border-radius:10px; +} + +#detail_content{ +overflow:auto; +height:90%; +} + +#detail_content img{ +width:280px; +} + +#welcome h2{ +padding:10px 0; +} + +.detail_footer{ +text-align:center; +position:absolute; +top:15px; +right:18px; +} + +.detail_footer a{ +color:#b400ff; +padding:2px; +background-color:#FFF; +border:1px solid; +display:block; +text-decoration:None; +-moz-border-radius:4px; +-webkit-border-radius:4px; +border-radius:4px; +} + +#category_detail{ +display:None; +padding:6px 10px; +border:1px solid #888; +position:absolute; +z-index:5; +top:120px; +bottom:180px; +left:100px; +right:50px; +margin-right:360px; +background-color:#FFF; +opacity:0.9; +-moz-border-radius:10px; +-webkit-border-radius:10px; +border-radius:10px; +} + +#category_detail h2{ +padding:10px 0; +} + + +#category_desc_content{ +overflow:auto; +height:88%; +} + +#map{ +position:absolute; +border:1px solid #888; +margin:0px; +padding:0px; +height:98%; +margin:0; +padding:0; +top:8px; +bottom:8px; +left:8px; +right:8px; +z-index:0; +} + +.news{ +} + +.news h3{ +padding:0px; +margin:0; +} + +.info{ +border-top:1px dashed; +padding:10px; +margin:0; +} + +ul#categories{ +margin:0; +padding:0 10px; +overflow:auto; +height:160px; +width:270px; +} + +ul#categories li{ +font-variant:small-caps; +list-style:none; +} + +ul#categories li li{ +font-variant:normal; +margin-left:20px; +} + +ul#categories li li a{ +line-height:25px; +margin-left:0; +font-weight:bold; +} + +ul#categories ul{ +margin:0; +padding:0; +} + +ul.subcategories label img{ +height:20px; +} + +ul#categories li#display_submited{ +font-variant:normal; +color:#b400ff; +} + +.zoom_image{ +cursor:pointer; +} + +.control_image{ +cursor:pointer; +vertical-align:text-bottom; +} + +.errorlist{ +color:#b400ff; +font-weight:bold; +} + +.fieldWrapper{ +padding:6px; +} + +div.warning{ +margin-top:18px; +padding:0 10px; +border:1px solid #888; +-moz-border-radius: 10px; +-webkit-border-radius: 10px; +border-radius: 10px; +background-color:#ffdbdb; +} + +#logos{ +text-align:center; +z-index:5; +position: absolute; +bottom:46px; +left:18px; +} + +#logos ul{ +margin:0; +margin-right:20px; +padding:4px; +border:1px solid #888; +-moz-border-radius: 10px; +-webkit-border-radius: 10px; +border-radius: 10px; +background-color:white; +height:40px; +float:left; +} + +#logos li{ +display:inline; +} + +#logos img{ +height:40px; +text-decoration:None; +border-width:0; +} + +#logos li a{ +text-decoration:None; +border-width:0; +} + +#welcome_button { +display: block; +position: absolute; +bottom:40px; +left:29px; +width:180px; +font-size:small; +background-color:#b400ff; +text-align:center; +z-index:4; +} + +#welcome_button a{ +color:white; +font-size:14px; +text-align:center; +text-decoration:none; +} + +/* openlayer customisation */ +.olControlPermalink { +display: block; +position: absolute; +bottom:12px; +left:20px; +width:180px; +font-size:small; +background-color:#b400ff; +text-align:center; +} + +.olControlPermalink a{ +color:white; +font-size:14px; +text-align:center; +text-decoration:none; +} + +.olControlScaleLine { +bottom:12px; +left:220px; +} diff --git a/chimere/static/textareas.js b/chimere/static/textareas.js new file mode 100644 index 0000000..fec83b8 --- /dev/null +++ b/chimere/static/textareas.js @@ -0,0 +1,27 @@ +/* base function shared by some pages */ +/* Copyright (C) 2009 É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. +*/ + +tinyMCE.init({ + mode : "textareas", + theme : "advanced", + relative_urls : false, + theme_advanced_buttons1 : "bold,italic,underline,strikethrough,separator,bullist,numlist,separator,hr,separator,link", + theme_advanced_buttons2 : "", + theme_advanced_buttons3 : "" +}); diff --git a/chimere/templates/base.html b/chimere/templates/base.html new file mode 100644 index 0000000..d8127cc --- /dev/null +++ b/chimere/templates/base.html @@ -0,0 +1,41 @@ +{% load i18n %} +<!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> + <script type="text/javascript"><!-- + var media_path = '{{ media_path }}'; + // --></script> + {% block extra_head %}{{extra_head|safe}}{% endblock %} + <link rel="stylesheet" href="{{ media_path }}styles.css" /> + {% block head %}{% endblock %} +</head> + +<body> + {% block top %}{% endblock %} + <div id="topbar"> + <ul id='action'> +{% for action, subactions in actions %} + <li{% ifequal action.id action_selected.0 %} class='selected'{% endifequal %}> + <a href='/{{ action.path }}' onclick='saveExtent();'>{{ action.label }}</a> + {% ifequal action.id action_selected.0 %}{% if subactions %}<ul>{% for subaction in subactions %} + <li{% ifequal subaction.id action_selected.1 %} class='selected'{% endifequal %}> + <a href='/{{ subaction.path }}' onclick='saveExtent();'>{{ subaction.label }}</a> + </li> + {% endfor %}</ul>{% endif %}{% endifequal %} + </li> +{% endfor %} + </ul> + </div> + {% block sidebar %}{% endblock %} + {% block message_map %}{% endblock %} + {% block message_edit %}{% endblock %} + {% block content %}{% endblock %} + {% block bottom %}{% endblock %} + <div id='footer'>{% block footer %} +{% trans "This site uses Chimère"%} - © 2008-2010 <a href='http://blog.peacefrogs.net/nim/chimere/'>Chimère project</a> + {% endblock %}</div> +</body> +</html> + diff --git a/chimere/templates/base_user.html b/chimere/templates/base_user.html new file mode 100644 index 0000000..73f22a5 --- /dev/null +++ b/chimere/templates/base_user.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% load i18n %} +{# to customize your base Chimère template add codes between the following blocks #} +{# title of the map #}{% block title %}{% endblock %} +{# head of the document #}{% block head %}{% endblock %} +{# top of the page before the tabs #}{% block top %}{% endblock %} +{# message block displayed on the map #}{% block message_map %}{% endblock %} +{# message block displayed on the edit pages #}{% block message_edit %}{% endblock %} +{# top of the page after the tabs #}{% block sidebar %}{% endblock %} +{# main part of the page #}{% block content %}{% endblock %} +{# bottom of the page before the footer #}{% block bottom %}{% endblock %} +{# inside the footer - please leave bloc.super it shows the Chimère copyright #}{% block footer %}{{ block.super }}{% endblock %} diff --git a/chimere/templates/category_detail.html b/chimere/templates/category_detail.html new file mode 100644 index 0000000..0211989 --- /dev/null +++ b/chimere/templates/category_detail.html @@ -0,0 +1,6 @@ +{% load i18n %} +<h2>{{ category.name }}</h2> +<div id='category_desc_content'> +{{ category.description|safe }} +</div> +<div class='detail_footer'><a href='javascript:hide("category_detail");'>{% trans "Close" %}</a></div> diff --git a/chimere/templates/contactus.html b/chimere/templates/contactus.html new file mode 100644 index 0000000..7464c72 --- /dev/null +++ b/chimere/templates/contactus.html @@ -0,0 +1,20 @@ +{% extends "base_user.html" %} +{% load i18n %} +{% block message_map %}{% endblock %} +{% block message_edit%}{% endblock %} +{% block content %}{{ block.super }} +<div id='content'> +{% if message %} +<div class='warning'><p>{{message}}</p></div> +{% else %} +<fieldset class='edit'> +<p>{% trans "If you have some requests or remarks about this site you can leave them here." %}</p> +<form action="" method="post"> +{{contact_form.as_p}} +<input type="submit" value="{% trans "Submit" %}" /> +</form> +</fieldset> +{% endif %} +</div> +{% endblock %} + diff --git a/chimere/templates/detail.html b/chimere/templates/detail.html new file mode 100644 index 0000000..351ab4e --- /dev/null +++ b/chimere/templates/detail.html @@ -0,0 +1,8 @@ +{% load i18n %} +<h2>{{ marker.name }}</h2> +<div id='detail_content'> +{% 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|safe }}</p> +{% endfor %}</div> +</div> diff --git a/chimere/templates/edit.html b/chimere/templates/edit.html new file mode 100644 index 0000000..1378a63 --- /dev/null +++ b/chimere/templates/edit.html @@ -0,0 +1,52 @@ +{% extends "base_user.html" %} +{% load i18n %} +{% block message_map %}{% endblock %} +{% block message_edit%}<div id='content'>{{block.super}}{% endblock %} + +{% block content %}{{ block.super }} +{% 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='/{{extra_url}}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' onclick='saveExtent();' value="{% trans 'Propose'%}"/></p> +</form> +</fieldset> +</div> +{% endblock %} diff --git a/chimere/templates/edit_route.html b/chimere/templates/edit_route.html new file mode 100644 index 0000000..421a600 --- /dev/null +++ b/chimere/templates/edit_route.html @@ -0,0 +1,52 @@ +{% extends "base_user.html" %} +{% load i18n %} +{% block message_map %}{% endblock %} +{% block message_edit%}<div id='content'>{{block.super}}{% endblock %} +{% block content %}{{ block.super }} +{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} +<fieldset class='edit'> +<legend>{% trans "Add a new route" %}</legend> +<p>* {% trans "indicates a mandatory field" %}</p> +<form enctype="multipart/form-data" method='post' action='/{{extra_url}}edit_route/'> +<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_route">{% trans "Route"%} *</label> + {%if form.point.errors %}<ul class="errorlist"><li>{% trans "Select a location for this new site" %}</li></ul>{%endif%} + {{form.route}} +</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' onclick='saveExtent();' value="{% trans 'Propose'%}"/></p> +</form> +</fieldset> +</div> +{% endblock %} diff --git a/chimere/templates/main_map.html b/chimere/templates/main_map.html new file mode 100644 index 0000000..dbd81ad --- /dev/null +++ b/chimere/templates/main_map.html @@ -0,0 +1,58 @@ +{% extends "base_user.html" %} +{% load i18n %} +{% block message_edit %}{% endblock %} +{% block sidebar %}{{ block.super }} +<div id='panel'> +<h2>{% trans "Topics"%}</h2> +<form method='post' name='frm_categories' id='frm_categories'> +<ul id='categories'>{% for category, lst_sub_categories in sub_categories %} +<script language='javascript'><!-- +var lst_{{category.id}}=new Array(); +{% for sub_category in lst_sub_categories %} +lst_{{category.id}}.push("{{sub_category.id}}");{% endfor %} +--></script> + <li>{% if category.selected %}<script language='javascript'>var current_cat={{category.id}};{% endif %}</script><img class='control_image' id='maincategory_img_{{category.id}}' alt='control' src='{{media_path}}icons/{% if category.selected %}minus.png{%else%}plus.png{%endif%}' onclick='toggleCategory({{category.id}});'/> +{% trans category.name %} +<img class='zoom_image' alt='{% trans "Zoom to" %} {{category.name}}' src='{{media_path}}icons/zoom.png' onclick='zoomToCategory(lst_{{category.id}}.join("_"))'/> + <ul class='subcategories' id='maincategory_{{category.id}}'{% if not category.selected %} style='display:None'{% endif %}>{% for sub_category in lst_sub_categories %} + <li><input type='checkbox' onclick='loadGeoObjects()' name='category_{{sub_category.id}}' id='category_{{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> + <img class='zoom_image' alt='{% trans "Zoom to" %} {{sub_category.name}}' src='{{media_path}}icons/zoom.png' onclick='zoomToCategory({{sub_category.id}})'/></li>{% endfor %} + {%if category.description%}<li><a href='#' onclick='displayCategoryDetail({{category.id}});'>{%trans "Tell me more..."%}</a></li>{%endif%} + </ul> + </li>{% endfor %} + <li id='display_submited'><input type='checkbox' onclick='loadGeoObjects()' name='display_submited' id='display_submited_check'/> {% trans "Display markers and routes waiting for validation"%}</li> +</ul> + +</form> +</div> +{%if areas%}<div id='areas'> +<h2>{% trans "Shortcuts"%}</h2> +<ul>{% for area in areas%} +<li><img class='zoom_image' alt='{% trans "Zoom to" %} {{area.name}}' src='{{media_path}}icons/zoom.png' onclick='zoomToArea({{area.upper_left_corner.x}}, {{area.upper_left_corner.y}}, {{area.lower_right_corner.x}}, {{area.lower_right_corner.y}})'/> {{area.name}}</li>{%endfor%} +</ul> +</div>{%endif%} + +<div id='detail'> +</div> +<div id='category_detail'> +</div> + +<div id='welcome_button'><a href='javascript:show("welcome");'>{% trans "Welcome message"%}</a></div> +{{welcome}}{% endblock %} +{% block content %}{{ block.super }}<div id='map'></div> +<script type='text/javascript'><!-- +var map_layer = {{map_layer|safe}}; +var permalink_label = '{%trans "Permalink"%}'; +var extra_url = "{{extra_url}}"; +var p_zoom; +{%if p_zoom %}p_zoom={{p_zoom}}; +var p_lat={{p_lat}}; +var p_lon={{p_lon}}; +var p_display_submited={{p_display_submited}}; +var p_checked_categories = [{{p_checked_categories}}];{%endif%} +init(); +// --> </script>{% endblock %} +{% block footer %}<div id='map-footer'>{{ block.super }} - {% trans "Map"%} © <a href='http://openstreetmap.org/'>OpenStreetMap</a></div> +{% endblock %} diff --git a/chimere/templates/submited.html b/chimere/templates/submited.html new file mode 100644 index 0000000..200717e --- /dev/null +++ b/chimere/templates/submited.html @@ -0,0 +1,11 @@ +{% extends "base_user.html" %} +{% load i18n %} +{% block message_map %}{% endblock %} +{% block message_edit%}<div id='content'>{{block.super}}{% endblock %} +{% block content %}{{ block.super }} +<fieldset class='edit'> +<p>{% trans "Your proposition has been submited. A moderator will treat your submission shortly. Thanks!" %}</p> +</fieldset> +</div> +{% endblock %} + diff --git a/chimere/templates/welcome.html b/chimere/templates/welcome.html new file mode 100644 index 0000000..463f880 --- /dev/null +++ b/chimere/templates/welcome.html @@ -0,0 +1,16 @@ +{% load i18n %} +<div id='welcome' {% if not display %}style='display:None'{%endif%}> +<h2>{% trans "Welcome to Chimère"%}</h2> +<div id='detail_content'> + <p>{% trans "This is the default message. You can overload it by modifying the file welcome.html in the template directory of Chimère. Below this message all news message will be displayed. You can add them in administration pages."%}</p> +{% if news_lst %}<div class='news'> +{% for news in news_lst %} + <div class='info'> + <h3>{{news.title}} - {% trans news.date %}</h3> + <p>{{news.content|safe}}</p> + </div> +{%endfor%} +</div>{%endif%} +</div> +<div class='detail_footer'><a href='javascript:hide("welcome");'>{% trans "Close" %}</a></div> +</div> diff --git a/chimere/urls.py b/chimere/urls.py new file mode 100644 index 0000000..0cf6862 --- /dev/null +++ b/chimere/urls.py @@ -0,0 +1,32 @@ +from django.conf.urls.defaults import * + +from django.contrib import admin +admin.autodiscover() + +from settings import ROOT_PATH, EXTRA_URL + +js_info_dict = { + 'packages': 'chimere', +} + +urlpatterns = patterns('', + (r'^' + EXTRA_URL + r'admin/(.*)', admin.site.root), + (r'^' + EXTRA_URL + r'$', 'chimere.main.views.index'), + (r'^' + EXTRA_URL + r'contact/$', 'chimere.main.views.contactus'), + (r'^' + EXTRA_URL + r'edit/$', 'chimere.main.views.edit'), + (r'^' + EXTRA_URL + r'edit_route/$', 'chimere.main.views.editRoute'), + (r'^' + EXTRA_URL + r'submited/(?P<action>\w+)/$$', 'chimere.main.views.submited'), + (r'^' + EXTRA_URL + r'getDetail/(?P<marker_id>\d+)/$', + 'chimere.main.views.getDetail'), + (r'^' + EXTRA_URL + r'getDescriptionDetail/(?P<category_id>\d+)/$', + 'chimere.main.views.getDescriptionDetail'), + (r'^' + EXTRA_URL + 'getGeoObjects/(?P<category_ids>\w+)/$', + 'chimere.main.views.getGeoObjects'), + (r'^' + EXTRA_URL + 'getGeoObjects/(?P<category_ids>\w+)/(?P<status>\w+)$', + 'chimere.main.views.getGeoObjects'), + (r'^' + EXTRA_URL + 'static/(?P<path>.*)$', 'django.views.static.serve', + {'document_root': ROOT_PATH + 'static/'}), + (r'^' + EXTRA_URL + 'media/(?P<path>.*)$', 'django.views.static.serve', + {'document_root': ROOT_PATH + 'media/'}), + (r'^' + EXTRA_URL + 'jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict), +) |