summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chimere/admin.py2
-rw-r--r--chimere/forms.py12
-rw-r--r--chimere/locale/fr/LC_MESSAGES/django.po627
-rw-r--r--chimere/migrations/0009_auto__add_field_marker_keywords__add_field_route_keywords.py298
-rw-r--r--chimere/models.py60
-rw-r--r--chimere/search_indexes.py59
-rw-r--r--chimere/settings.sample.py9
-rw-r--r--chimere/static/chimere/css/styles.css96
-rw-r--r--chimere/static/chimere/js/base.js9
-rw-r--r--chimere/static/chimere/js/jquery.chimere.js88
-rw-r--r--chimere/static/chimere/js/search-autocomplete.js108
-rw-r--r--chimere/static/chimere/js/search.js32
-rw-r--r--chimere/templates/chimere/base.html1
-rw-r--r--chimere/templates/chimere/blocks/areas.html2
-rw-r--r--chimere/templates/chimere/blocks/footer.html2
-rw-r--r--chimere/templates/chimere/detail.html5
-rw-r--r--chimere/templates/chimere/edit.html6
-rw-r--r--chimere/templates/chimere/main_map.html18
-rw-r--r--chimere/templates/chimere/main_map_simple.html2
-rw-r--r--chimere/templates/search/indexes/chimere/marker_text.txt4
-rw-r--r--chimere/templates/search/indexes/chimere/route_text.txt2
-rw-r--r--chimere/templates/search/search.html51
-rw-r--r--chimere/templates/search/search_js.html26
-rw-r--r--chimere/templatetags/chimere_tags.py7
-rw-r--r--chimere/templatetags/unescape.py15
-rw-r--r--chimere/urls.py20
-rw-r--r--chimere/views.py53
-rw-r--r--conf/solr/schema-fr.xml166
-rw-r--r--conf/solr/schema.xml168
-rw-r--r--haystack-requirements.txt4
-rw-r--r--requirements.txt2
-rw-r--r--requirements_searchengine.txt12
32 files changed, 1630 insertions, 336 deletions
diff --git a/chimere/admin.py b/chimere/admin.py
index b8414dc..a93f96d 100644
--- a/chimere/admin.py
+++ b/chimere/admin.py
@@ -192,7 +192,7 @@ class MarkerAdmin(admin.ModelAdmin):
form = MarkerAdminForm
fieldsets = ((None, {
'fields': ['point', 'name', 'status', 'categories',
- 'description', 'start_date', 'end_date']
+ 'description', 'keywords', 'start_date', 'end_date']
}),
(_(u"Submitter"), {
'classes':('collapse',),
diff --git a/chimere/forms.py b/chimere/forms.py
index a57fe87..c8d7d45 100644
--- a/chimere/forms.py
+++ b/chimere/forms.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (C) 2008-2012 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+# Copyright (C) 2008-2014 É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
@@ -20,6 +20,7 @@
"""
Forms
"""
+
from django import forms
from django.conf import settings
from django.contrib.gis.db import models
@@ -31,6 +32,9 @@ from django.contrib.auth.models import User, Permission, ContentType
from django.contrib.admin.widgets import AdminDateWidget
from django.core.mail import EmailMessage, BadHeaderError
+if hasattr(settings, 'CHIMERE_SEARCH_ENGINE') and settings.CHIMERE_SEARCH_ENGINE:
+ from haystack.forms import SearchForm as HaystackSearchForm
+
from chimere.models import Marker, Route, PropertyModel, Property, Area,\
News, Category, SubCategory, RouteFile, MultimediaFile, MultimediaType, \
PictureFile, Importer, PropertyModelChoice, IFRAME_LINKS, \
@@ -304,6 +308,7 @@ class MarkerForm(MarkerBaseForm):
ref_pk = forms.IntegerField(label=u" ", widget=forms.HiddenInput(),
required=False)
description = forms.CharField(widget=TextareaWidget, required=False)
+ keywords = forms.CharField(max_length=200, required=False)
class Meta:
model = Marker
exclude = ('status',)
@@ -631,3 +636,8 @@ class RoutingForm(forms.Form):
for speed, lbl in settings.CHIMERE_ROUTING_SPEEDS[transport]:
self.fields['speed'].widget.choices.append(
("%s_%d" % (transport, speed), _(lbl)))
+
+SearchForm = None
+if hasattr(settings, 'CHIMERE_SEARCH_ENGINE') and settings.CHIMERE_SEARCH_ENGINE:
+ class SearchForm(HaystackSearchForm):
+ pass
diff --git a/chimere/locale/fr/LC_MESSAGES/django.po b/chimere/locale/fr/LC_MESSAGES/django.po
index f0b15b1..4634b0a 100644
--- a/chimere/locale/fr/LC_MESSAGES/django.po
+++ b/chimere/locale/fr/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.2\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-03-14 18:43+0100\n"
+"POT-Creation-Date: 2015-02-26 01:30+0100\n"
"PO-Revision-Date: 2010-03-20 20:00+0100\n"
"Last-Translator: Étienne Loks <etienne.loks@peacefrogs.net>\n"
"MIME-Version: 1.0\n"
@@ -75,57 +75,57 @@ msgstr "Élément modifié traité."
msgid "Managed modified items"
msgstr "Gérer les éléments modifiés"
-#: admin.py:197 admin.py:274
+#: admin.py:197 admin.py:275
msgid "Submitter"
msgstr "Demandeur"
-#: admin.py:202 admin.py:279 admin.py:331
+#: admin.py:202 admin.py:280 admin.py:332
msgid "Import"
msgstr "Import"
-#: admin.py:207 admin.py:284
+#: admin.py:207 admin.py:285
msgid "Associated items"
msgstr "Éléments associés"
-#: admin.py:337
+#: admin.py:338
msgid "Cancel import"
msgstr "Annuler l'import"
-#: admin.py:343
+#: admin.py:344
msgid "Cancel export"
msgstr "Annuler l'export"
-#: admin.py:347
+#: admin.py:348
msgid "Can manage only one OSM export at a time."
msgstr "Ne peux gérer qu'un seul export OSM à la fois."
-#: admin.py:352
+#: admin.py:353
msgid ""
"You must treat all item with the status \"imported\" before exporting to OSM."
msgstr ""
"Vous devez traiter tous les éléments avec le status « importé » avant "
"d'exporter vers OSM."
-#: admin.py:356
+#: admin.py:357
msgid "Only OSM importer are managed for export."
msgstr "Seul les imports de type OSM peuvent être gérés pour les exports."
-#: admin.py:363
+#: admin.py:364
msgid "No point of interest are concerned by this export."
msgstr "Aucun point d'intérêt n'est concerné par cet export."
-#: admin.py:375
+#: admin.py:376
msgid "Export launched."
msgstr "Export lancé."
-#: admin.py:379
+#: admin.py:380
#, python-format
msgid ""
"%s point(s) of interest concerned by this export before bounding box filter."
msgstr ""
"%s point(s) d'intérêt concerné par cet export (avant le filtre sur la zone)"
-#: admin.py:384
+#: admin.py:385
msgid "Export to osm"
msgstr "Exporter vers osm"
@@ -141,50 +141,50 @@ msgstr "Nouveaux points d'intérêt de "
msgid "Last points of interest by area"
msgstr "Nouveaux points d'intérêt par zone"
-#: forms.py:83
+#: forms.py:87
msgid "New submission for"
msgstr "Nouvelle proposition pour"
-#: forms.py:84
+#: forms.py:88
#, 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 : "
-#: forms.py:86
+#: forms.py:90
msgid "To valid, precise or unvalid this item: "
msgstr "Pour valider, préciser ou rejeter cet élément : "
-#: forms.py:96
+#: forms.py:100
msgid "Email (optional)"
msgstr "Courriel (optionnel) "
-#: forms.py:97
+#: forms.py:101
msgid "Object"
msgstr "Objet"
-#: forms.py:117
+#: forms.py:121
msgid "OSM user"
msgstr "Utilisateur OSM"
-#: forms.py:118 models.py:1500
+#: forms.py:122 models.py:1543
msgid "Password"
msgstr "Mot de passe"
-#: forms.py:122
+#: forms.py:126
msgid "API"
msgstr "API"
-#: forms.py:125
+#: forms.py:129
#, python-format
msgid "Test API - %s"
msgstr "API de test - %s"
-#: forms.py:127
+#: forms.py:131
#, python-format
msgid "Main API - %s"
msgstr "API principale - %s"
-#: forms.py:158 forms.py:162
+#: forms.py:162 forms.py:166
msgid ""
"For OSM import you must be provide a filter. Select an area and node/way "
"filter."
@@ -192,530 +192,554 @@ msgstr ""
"Pour les imports OSM vous devez fournir un filtre. Sélectionnez une zone et "
"un filtre sur les nœuds/routes."
-#: forms.py:166
+#: forms.py:170
msgid "Shapefiles must be provided in a zipped archive."
msgstr ""
"Les fichiers Shapefiles doivent être fournis regroupés dans une archive zip."
-#: forms.py:170
+#: forms.py:175
msgid "You have to set \"source\" or \"source file\" but not both."
msgstr ""
"Vous devez spécifier le champ « Source » ou « Fichier source » mais pas les "
"deux."
-#: forms.py:175
+#: forms.py:180
msgid "You have to set \"source\" or \"source file\"."
msgstr "Vous devez spécifier le champ « Source » ou « Fichier source »."
-#: forms.py:236
+#: forms.py:241
msgid "End date has been set with no start date"
msgstr "Une date de fin a été donnée sans date de début"
-#: forms.py:240
+#: forms.py:245
msgid "End date can't be before start date"
msgstr "La date de fin ne peut pas être antérieure à la date de début"
-#: forms.py:250
+#: forms.py:255
msgid "This field is mandatory for the selected categories"
msgstr "Ce champ est obligatoire pour les catégories sélectionnées"
-#: forms.py:501
+#: forms.py:507
msgid "File"
msgstr "Fichier"
-#: forms.py:507
+#: forms.py:513
msgid "Bad file format: this must be a GPX or KML file"
msgstr "Mauvais format de fichier : KML et GPX sont supportés"
-#: forms.py:512 models.py:53 models.py:101 models.py:163 models.py:184
-#: models.py:197 models.py:212 models.py:375 models.py:720 models.py:776
-#: models.py:835 models.py:953 models.py:1304 models.py:1316 models.py:1490
-#: utils.py:485 templates/admin/chimere/managed_modified.html:23
+#: forms.py:518 models.py:54 models.py:102 models.py:164 models.py:185
+#: models.py:198 models.py:213 models.py:414 models.py:764 models.py:820
+#: models.py:879 models.py:997 models.py:1347 models.py:1359 models.py:1533
+#: utils.py:490 templates/admin/chimere/managed_modified.html:23
#: templates/chimere/edit.html:40 templates/chimere/edit_route.html:37
#: templates/chimere/blocks/alternate_multimedia.html:39
msgid "Name"
msgstr "Nom"
-#: forms.py:521 models.py:1353
+#: forms.py:527 models.py:1396
msgid "Area"
msgstr "Zone"
-#: forms.py:561
+#: forms.py:567
msgid "No area selected."
msgstr "Pas de zone sélectionnée."
-#: forms.py:568
+#: forms.py:574
#, python-format
msgid "The area \"%s\" has the same order, you need to choose another one."
msgstr ""
"La zone « %s » a le même numéro d'ordre, vous devez un choisir un autre."
-#: forms.py:613
+#: forms.py:625
msgid "Start"
msgstr "Départ"
-#: forms.py:614
+#: forms.py:626
msgid "Finish"
msgstr "Arrivée"
-#: forms.py:615
+#: forms.py:627
msgid "Speed"
msgstr "Vitesse"
-#: models.py:54
+#: models.py:55
msgid "Mnemonic"
msgstr "Mnémonique"
-#: models.py:56 models.py:102 models.py:185 models.py:213 models.py:368
-#: models.py:724 models.py:1322 models.py:1492 models.py:1533
+#: models.py:57 models.py:103 models.py:186 models.py:214 models.py:407
+#: models.py:768 models.py:1365 models.py:1535 models.py:1578
msgid "Available"
msgstr "Disponible"
-#: models.py:57 models.py:173 models.py:186 models.py:230 models.py:778
-#: models.py:850 models.py:1321 models.py:1479 models.py:1491
+#: models.py:58 models.py:174 models.py:187 models.py:231 models.py:822
+#: models.py:894 models.py:1364 models.py:1522 models.py:1534
msgid "Order"
msgstr "Ordre"
-#: models.py:58
+#: models.py:59
msgid "Template path"
msgstr "Chemin du patron"
-#: models.py:65 models.py:66
+#: models.py:66 models.py:67
msgid "Page"
msgstr "Page"
-#: models.py:103 models.py:469
+#: models.py:104 models.py:510
msgid "Is front page"
msgstr "Est en page principale"
-#: models.py:105 models.py:1501
+#: models.py:106 models.py:1544
msgid "Date"
msgstr "Date"
-#: models.py:107 models.py:777
+#: models.py:108 models.py:821
msgid "Url"
msgstr "Url"
-#: models.py:108
+#: models.py:109
msgid "Associated areas"
msgstr "Zones associées"
-#: models.py:114 models.py:115 templates/chimere/blocks/news.html:3
+#: models.py:115 models.py:116 templates/chimere/blocks/news.html:3
#: templates/chimere/blocks/news.html:5
msgid "News"
msgstr "Nouvelle"
-#: models.py:124
+#: models.py:125
msgid "Parameters"
msgstr "Paramètres"
-#: models.py:128
+#: models.py:129
msgid "TinyUrl"
msgstr "Mini-url"
-#: models.py:167 models.py:174 models.py:225
+#: models.py:168 models.py:175 models.py:226
msgid "Color theme"
msgstr "Thème de couleur"
-#: models.py:172
+#: models.py:173
msgid "Code"
msgstr "Code"
-#: models.py:179
+#: models.py:180
msgid "Color"
msgstr "Couleur"
-#: models.py:192 models.py:210
+#: models.py:193 models.py:211 models.py:400
msgid "Category"
msgstr "Catégorie"
-#: models.py:198 models.py:716 models.py:836 models.py:1019
+#: models.py:199 models.py:760 models.py:880 models.py:1063
#: templates/chimere/blocks/alternate_multimedia.html:43
msgid "Image"
msgstr "Image"
-#: models.py:200 models.py:838 models.py:1021
+#: models.py:201 models.py:882 models.py:1065
msgid "Height"
msgstr "Hauteur"
-#: models.py:201 models.py:839 models.py:1022
+#: models.py:202 models.py:883 models.py:1066
msgid "Width"
msgstr "Largeur"
-#: models.py:205 models.py:222
+#: models.py:206 models.py:223
msgid "Icon"
msgstr "Icône"
-#: models.py:214
+#: models.py:215
msgid "Available for submission"
msgstr "Disponible pour soumission"
-#: models.py:216
+#: models.py:217
msgid "Marker"
msgstr "Point d'intérêt"
-#: models.py:217 models.py:1015 models.py:1032
+#: models.py:218 models.py:1059 models.py:1076
#: templates/chimere/edit_route.html:28
msgid "Route"
msgstr "Trajet"
-#: models.py:218
+#: models.py:219
msgid "Both"
msgstr "Mixte"
-#: models.py:219
+#: models.py:220
msgid "Item type"
msgstr "Type d'élément"
-#: models.py:220
+#: models.py:221
msgid "Is dated"
msgstr "Est daté"
-#: models.py:223
+#: models.py:224
msgid "Hover icon"
msgstr "Icône en survol"
-#: models.py:227
+#: models.py:228
msgid "Displayed in the layer menu"
msgstr "Apparaît dans le menu des couches ?"
-#: models.py:229
+#: models.py:230
msgid "Routing warn"
msgstr "Avertissement sur les itinéraires"
-#: models.py:235
+#: models.py:236
msgid "Sub-category"
msgstr "Sous-catégorie"
-#: models.py:236
+#: models.py:237
msgid "Sub-categories"
msgstr "Sous-catégories"
-#: models.py:320
+#: models.py:326
msgid "Importer type"
msgstr "Type d'import"
-#: models.py:322
+#: models.py:328
msgid "Filter"
msgstr "Filtre"
-#: models.py:324 templates/chimere/blocks/alternate_multimedia.html:49
+#: models.py:330 templates/chimere/blocks/alternate_multimedia.html:49
msgid "Web address"
msgstr "Adresse web"
-#: models.py:326
+#: models.py:332
+msgid "Don't forget the trailing slash"
+msgstr "N'oubliez pas la barre oblique (« / ») finale"
+
+#: models.py:333
msgid "Source file"
msgstr "Fichier source"
-#: models.py:328
+#: models.py:335
+msgid "Alt source file"
+msgstr "Fichier source alternatif"
+
+#: models.py:337
msgid "Name by default"
msgstr "Nom par défaut"
-#: models.py:330
+#: models.py:339
msgid "SRID"
msgstr "SRID"
-#: models.py:331
+#: models.py:340
msgid "Zipped file"
msgstr "Fichier zippé"
-#: models.py:332
+#: models.py:341
msgid "Overwrite existing data"
msgstr "Écraser les données existantes"
-#: models.py:334
+#: models.py:343
msgid "Get description from source"
msgstr "Obtenir une description depuis la source"
-#: models.py:336
+#: models.py:345
msgid "Default description"
msgstr "Description par défaut"
-#: models.py:338 models.py:396
+#: models.py:347 models.py:437
msgid "Origin"
msgstr "Origine"
-#: models.py:340 models.py:398
+#: models.py:349 models.py:439
msgid "License"
msgstr "Licence"
-#: models.py:343
+#: models.py:352
msgid "Associated subcategories"
msgstr "Sous-catégories associées"
-#: models.py:344 utils.py:489
+#: models.py:353 utils.py:494
msgid "State"
msgstr "État"
-#: models.py:346
+#: models.py:354
msgid "Automatically associate a marker to a way"
msgstr "Associer automatiquement un marqueur à une route"
-#: models.py:350
+#: models.py:356
+msgid "Automatically updated"
+msgstr "Mis à jour automatiquement"
+
+#: models.py:358
+msgid "Default localisation"
+msgstr "Localisation par défaut"
+
+#: models.py:364 models.py:398
msgid "Importer"
msgstr "Import"
-#: models.py:367
+#: models.py:401 models.py:427
+msgid "Import key"
+msgstr "Clé d'import"
+
+#: models.py:404
+msgid "Importer - Key categories"
+msgstr "Importeur - clés / catégories"
+
+#: models.py:406
msgid "Submited"
msgstr "Soumis"
-#: models.py:369
+#: models.py:408
msgid "Modified"
msgstr "Modifié"
-#: models.py:370
+#: models.py:409
msgid "Disabled"
msgstr "Désactivé"
-#: models.py:371
+#: models.py:410
msgid "Imported"
msgstr "Importé"
-#: models.py:377
+#: models.py:416
msgid "Submitter session key"
msgstr "Clé de session du demandeur"
-#: models.py:379
+#: models.py:418
msgid "Submitter name or nickname"
msgstr "Nom ou pseudo du demandeur"
-#: models.py:381
+#: models.py:420
msgid "Submitter email"
msgstr "Courriel du demandeur"
-#: models.py:383
+#: models.py:422
msgid "Submitter comment"
msgstr "Commentaire du demandeur"
-#: models.py:385 models.py:1195
+#: models.py:424 models.py:1239
msgid "Status"
msgstr "État"
-#: models.py:386
-msgid "Import key"
-msgstr "Clé d'import"
+#: models.py:425 templates/chimere/edit.html:56
+msgid "Keywords"
+msgstr "Mots clés"
-#: models.py:388
+#: models.py:429
msgid "Import version"
msgstr "Version de l'import"
-#: models.py:390
+#: models.py:431
msgid "Source"
msgstr "Source"
-#: models.py:392
+#: models.py:433
msgid "Modified since last import"
msgstr "Modifié depuis le dernier import"
-#: models.py:394
+#: models.py:435
msgid "Not to be exported to OSM"
msgstr "À ne pas exporter vers OSM"
-#: models.py:400 templates/chimere/edit.html:57
+#: models.py:441 templates/chimere/edit.html:63
#: templates/chimere/edit_route.html:53
msgid "Start date"
msgstr "Date de début"
-#: models.py:401
+#: models.py:442
msgid "Not mandatory. Set it for dated item such as event. Format YYYY-MM-DD"
msgstr ""
"Optionnel. Précisez ce champ pour les éléments datés comme un événement. "
"Format du champ : AAAA-MM-JJ"
-#: models.py:403 templates/chimere/edit.html:63
+#: models.py:444 templates/chimere/edit.html:69
#: templates/chimere/edit_route.html:59
msgid "End date"
msgstr "Date de fin"
-#: models.py:404
+#: models.py:445
msgid ""
"Not mandatory. Set it only if you have a multi-day event. Format YYYY-MM-DD"
msgstr ""
"Optionnel. Précisez ce champ seulement pour des événements durant plusieurs "
"jours. Format du champ : AAAA-MM-JJ"
-#: models.py:461
+#: models.py:502
msgid "Reference marker"
msgstr "Point d'intérêt de référence"
-#: models.py:462 utils.py:491
+#: models.py:503 utils.py:496
msgid "Localisation"
msgstr "Localisation"
-#: models.py:464
+#: models.py:505
msgid "Available Date"
msgstr "Date de mise en disponibilité"
-#: models.py:468 utils.py:490 templates/admin/chimere/managed_modified.html:31
+#: models.py:509 utils.py:495 templates/admin/chimere/managed_modified.html:31
#: templates/chimere/edit.html:50 templates/chimere/edit_route.html:47
msgid "Description"
msgstr "Description"
-#: models.py:539 models.py:1543
+#: models.py:580 models.py:1588
msgid "Point of interest"
msgstr "Point d'intérêt"
-#: models.py:714
+#: models.py:758
msgid "Audio"
msgstr "Audio"
-#: models.py:715
+#: models.py:759
msgid "Video"
msgstr "Vidéo"
-#: models.py:717
+#: models.py:761
msgid "Other"
msgstr "Autre"
-#: models.py:718
+#: models.py:762
msgid "Media type"
msgstr "Type de media"
-#: models.py:721
+#: models.py:765
msgid "Mime type"
msgstr "Type mime"
-#: models.py:723
+#: models.py:767
msgid "Inside an iframe"
msgstr "À l'intérieur d'un iframe"
-#: models.py:727
+#: models.py:771
msgid "Multimedia type"
msgstr "Type de multimedia"
-#: models.py:728
+#: models.py:772
msgid "Multimedia types"
msgstr "Types de multimedia"
-#: models.py:737
+#: models.py:781
msgid "Automatic recognition"
msgstr "Reconnaissance automatique"
-#: models.py:763
+#: models.py:807
msgid "Extension name"
msgstr "Nom de l'extension"
-#: models.py:765
+#: models.py:809
msgid "Associated multimedia type"
msgstr "Type de multimedia associé"
-#: models.py:769
+#: models.py:813
msgid "Multimedia extension"
msgstr "Extension multimedia"
-#: models.py:770
+#: models.py:814
msgid "Multimedia extensions"
msgstr "Extensions multimedia"
-#: models.py:780 models.py:840
+#: models.py:824 models.py:884
msgid "Display inside the description?"
msgstr "Apparaît dans la description ?"
-#: models.py:785
+#: models.py:829
msgid "Multimedia file"
msgstr "Fichier multimedia"
-#: models.py:786
+#: models.py:830
msgid "Multimedia files"
msgstr "Fichiers multimedias"
-#: models.py:842
+#: models.py:886
msgid "Thumbnail"
msgstr "Miniature"
-#: models.py:846
+#: models.py:890
msgid "Thumbnail height"
msgstr "Hauteur de la miniature"
-#: models.py:848
+#: models.py:892
msgid "Thumbnail width"
msgstr "Largeur de la miniature"
-#: models.py:857
+#: models.py:901
msgid "Picture file"
msgstr "Fichier d'image"
-#: models.py:858
+#: models.py:902
msgid "Picture files"
msgstr "Fichiers d'image"
-#: models.py:954
+#: models.py:998
msgid "Raw file (gpx or kml)"
msgstr "Fichier brut (gpx ou kml)"
-#: models.py:956
+#: models.py:1000
msgid "Simplified file"
msgstr "Fichier simplifié"
-#: models.py:958
+#: models.py:1002
msgid "KML"
msgstr "KML"
-#: models.py:958
+#: models.py:1002
msgid "GPX"
msgstr "GPX"
-#: models.py:963
+#: models.py:1007
msgid "Route file"
msgstr "Fichier de trajet"
-#: models.py:964
+#: models.py:1008
msgid "Route files"
msgstr "Fichiers de trajet"
-#: models.py:1014
+#: models.py:1058
msgid "Reference route"
msgstr "Trajet de référence"
-#: models.py:1018
+#: models.py:1062
msgid "Associated file"
msgstr "Fichier associé"
-#: models.py:1023
+#: models.py:1067
msgid "Has an associated marker"
msgstr "Dispose d'un marqueur associé"
-#: models.py:1305
+#: models.py:1348
msgid "Layer code"
msgstr "Code pour la couche"
-#: models.py:1311
+#: models.py:1354
msgid "Layer"
msgstr "Couche"
-#: models.py:1317
+#: models.py:1360
msgid "Area urn"
msgstr "Urn de la zone"
-#: models.py:1319 templates/chimere/blocks/welcome.html:3
+#: models.py:1362 templates/chimere/blocks/welcome.html:3
msgid "Welcome message"
msgstr "Message d'accueil"
-#: models.py:1323
+#: models.py:1366
msgid "Upper left corner"
msgstr "Coin en haut à gauche"
-#: models.py:1325
+#: models.py:1368
msgid "Lower right corner"
msgstr "Coin en bas à droite"
-#: models.py:1327
+#: models.py:1370
msgid "Default area"
msgstr "Zone par défaut"
-#: models.py:1328
+#: models.py:1371
msgid "Only one area is set by default"
msgstr "Seule une zone est définie par défaut"
-#: models.py:1332
+#: models.py:1375
msgid "Sub-categories checked by default"
msgstr "Sous-catégories cochées par défaut"
-#: models.py:1334
+#: models.py:1377
msgid "Sub-categories dynamicaly displayed"
msgstr "Sous-categories affichées dynamiquement"
-#: models.py:1335
+#: models.py:1378
msgid ""
"If checked, categories are only displayed in the menu if they are available "
"on the current extent."
@@ -723,75 +747,107 @@ msgstr ""
"Si coché, les catégories sont disponibles sur le menu seulement si elles "
"apparaissent sur la zone affichée."
-#: models.py:1339 models.py:1495
+#: models.py:1382 models.py:1538
msgid "Restricted to theses sub-categories"
msgstr "Restreindre à ces sous-categories"
-#: models.py:1340
+#: models.py:1383
msgid "If no sub-category is set all sub-categories are available"
msgstr ""
"Si aucune sous-catégorie n'est définie toutes les sous-catégories sont "
"disponibles"
-#: models.py:1342
+#: models.py:1385
msgid "Link to an external CSS"
msgstr "Lien vers une feuille de style externe"
-#: models.py:1344
+#: models.py:1387
msgid "Restrict to the area extent"
msgstr "Restreindre à l'étendue de la zone"
-#: models.py:1480 widgets.py:88
+#: models.py:1523 widgets.py:89
msgid "Default layer"
msgstr "Couche par défaut"
-#: models.py:1484 models.py:1485
+#: models.py:1527 models.py:1528
msgid "Layers"
msgstr "Couches"
-#: models.py:1493
+#: models.py:1536
msgid "Mandatory"
msgstr "Obligatoire"
-#: models.py:1496
+#: models.py:1539
msgid ""
"If no sub-category is set all the property applies to all sub-categories"
msgstr ""
"Si aucune sous-catégorie n'est précisée, cette propriété est disponible pour "
"toutes les sous-catégories"
-#: models.py:1498
+#: models.py:1541
msgid "Text"
msgstr "Texte"
-#: models.py:1499
+#: models.py:1542
msgid "Long text"
msgstr "Texte long"
-#: models.py:1502
+#: models.py:1545
msgid "Choices"
msgstr "Choix"
-#: models.py:1510
+#: models.py:1546
+msgid "Boolean"
+msgstr "Booléen"
+
+#: models.py:1555
msgid "Type"
msgstr "Type"
-#: models.py:1515 models.py:1531 models.py:1545
+#: models.py:1560 models.py:1576 models.py:1590
msgid "Property model"
msgstr "Modèle de propriété"
-#: models.py:1532 models.py:1546
+#: models.py:1577 models.py:1591
msgid "Value"
msgstr "Valeur"
-#: models.py:1538
+#: models.py:1583
msgid "Model property choice"
msgstr "Choix pour les modèles de propriété"
-#: models.py:1557
+#: models.py:1602
msgid "Property"
msgstr "Propriété"
+#: settings.sample.py:92
+msgid "Foot"
+msgstr "À pieds"
+
+#: settings.sample.py:93
+msgid "Bicycle"
+msgstr "À vélo"
+
+#: settings.sample.py:94
+msgid "Motorcar"
+msgstr "En voiture"
+
+#: settings.sample.py:97
+msgid "You are walking slowly"
+msgstr "Vous marchez lentement"
+
+#: settings.sample.py:98
+msgid "You are walking pretty quickly"
+msgstr "Vous marchez plutôt rapidement"
+
+#: settings.sample.py:99
+msgid "You are riding pretty slowly"
+msgstr "Vous conduisez plutôt lentement"
+
+#: settings.sample.py:100
+msgid "You are riding pretty quickly"
+msgstr "Vous conduisez plutôt rapidement"
+
#: tasks.py:63
msgid "Import pending"
msgstr "Import en attente"
@@ -842,70 +898,71 @@ msgstr "Export échoué"
msgid "Export canceled"
msgstr "Export annulé"
-#: utils.py:148 utils.py:197
+#: utils.py:153 utils.py:202
msgid "Bad zip file"
msgstr "Mauvais fichier zip"
-#: utils.py:200
+#: utils.py:205
msgid "Missing file(s) inside the zip file"
msgstr "Fichier(s) manquant(s) dans l'archive zip"
-#: utils.py:241
+#: utils.py:246
msgid "Bad XML file"
msgstr "Mauvais fichier XML"
-#: utils.py:328
+#: utils.py:333
msgid "Error while reading the data source."
msgstr "Erreur lors de la lecture de la source."
-#: utils.py:346
+#: utils.py:351
#, python-format
msgid "SRID cannot be guessed. The default SRID (%s) has been used."
msgstr "Le SRID n'a pu être trouvé. Le SRID par défaut (%s) a été utilisé."
-#: utils.py:367
+#: utils.py:372
#, python-format
-msgid "Type of geographic item (%s) of this shapefile is not managed by Chimère."
+msgid ""
+"Type of geographic item (%s) of this shapefile is not managed by Chimère."
msgstr ""
-"Les types des éléments géographiques (%s) de ce fichier Shapefile ne sont pas "
- "gérés par Chimère."
+"Les types des éléments géographiques (%s) de ce fichier Shapefile ne sont "
+"pas gérés par Chimère."
-#: utils.py:387
+#: utils.py:392
msgid "Bad Shapefile"
msgstr "Mauvais fichier Shapefile"
-#: utils.py:429
+#: utils.py:434
msgid "Could not create file!"
msgstr "Ne peut pas créer le fichier !"
-#: utils.py:440
+#: utils.py:445
msgid "Failed to create field"
msgstr "Ne peut pas créer un champ"
-#: utils.py:486 templates/admin/chimere/managed_modified.html:25
+#: utils.py:491 templates/admin/chimere/managed_modified.html:25
#: templates/chimere/edit.html:45 templates/chimere/edit_route.html:42
-#: templates/chimere/main_map.html:13
+#: templates/chimere/main_map.html:16
#: templates/chimere/main_map_simple.html:10
msgid "Categories"
msgstr "Catégories"
-#: utils.py:519
+#: utils.py:524
msgid "Invalid CSV format"
msgstr "Fichier CSV non valide"
-#: utils.py:597
+#: utils.py:603
msgid "RSS feed is not well formed"
msgstr "Flux RSS non valide"
-#: utils.py:673
+#: utils.py:679
msgid "Nothing to import"
msgstr "Rien à importer"
-#: utils.py:757
+#: utils.py:763
msgid "New items imported - validate them before exporting"
msgstr "Nouveaux éléments importés - valider ceux-ci avant d'exporter"
-#: utils.py:759
+#: utils.py:765
msgid ""
"There are items from a former import not yet validated - validate them "
"before exporting"
@@ -913,19 +970,19 @@ msgstr ""
"Il y a des éléments d'un import précédent pas encore validé - Validez les "
"avant d'exporter"
-#: utils.py:771
+#: utils.py:777
msgid "Bad params - programming error"
msgstr "Mauvais paramètres - erreur de programmation"
-#: utils.py:781
+#: utils.py:787
msgid "Bad param"
msgstr "Mauvais paramètre"
-#: utils.py:796
+#: utils.py:802
msgid "No non ambigious tag is defined in the XAPI request"
msgstr "Pas de tag non ambigü définis dans la requête XAPI"
-#: utils.py:798
+#: utils.py:804
msgid ""
"No bounding box is defined in the XAPI request.If you are sure to manage the "
"entire planet set the bounding box to -180,-90,180,90"
@@ -934,19 +991,40 @@ msgstr ""
"vouloir lancer la requête sur la planète entière fixez la « bounding box » "
"à -180,-90,180,90"
-#: views.py:290
+#: utils.py:933
+msgid "Source page is unreachable."
+msgstr "La page source est inatteignable"
+
+#: utils.py:949
+msgid "The source file is not a valid XSLT file."
+msgstr "Le fichier source n'est pas un fichier XSLT valide"
+
+#: utils.py:961
+msgid "The alt source file is not a valid XSLT file."
+msgstr "Le fichier source alternatif n'est pas un fichier XSLT valide"
+
+#: utils.py:1005
+#, python-format
+msgid ""
+"Names \"%s\" doesn't match existing categories. Modify the import to match "
+"theses names with categories."
+msgstr ""
+"Les noms \"%s\" ne correspondent pas à des catégories existantes. Modifiez "
+"l'import pour faire correspondre ces noms avec des catégories."
+
+#: views.py:301
msgid "There are missing field(s) and/or errors in the submited form."
msgstr "Il y a des champs manquants ou des erreurs dans ce formulaire."
-#: views.py:375
+#: views.py:386
msgid "Bad file. Please check it with an external software."
msgstr "Fichier incohérent. Merci de le vérifier avec un logiciel externe."
-#: views.py:487
+#: views.py:501
msgid "Comments/request on the map"
msgstr "Commentaires/requètes sur la carte"
-#: views.py:490
+#: views.py:504
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."
@@ -955,56 +1033,56 @@ msgstr ""
"laissé votre courriel vous serez peut-être contacté bientôt pour plus de "
"détails."
-#: views.py:494
+#: views.py:508
msgid "Temporary error. Renew your message later."
msgstr "Erreur temporaire. Réenvoyez votre message plus tard."
-#: views.py:663
+#: views.py:687
msgid "No category available in this area."
msgstr "Pas de catégorie disponible sur cette zone."
-#: views.py:763
+#: views.py:800
msgid "Bad geometry"
msgstr "Géométrie incorrecte"
-#: views.py:848
+#: views.py:885
msgid "Incorrect choice in the list"
msgstr "Choix incorrect dans la liste"
-#: widgets.py:242
+#: widgets.py:243
msgid "Street, City, Country"
msgstr "Rue, Commune, Pays"
-#: widgets.py:311
+#: widgets.py:281
msgid "Latitude"
msgstr "Latitude"
-#: widgets.py:311
+#: widgets.py:283
msgid "Longitude"
msgstr "Longitude"
-#: widgets.py:335
+#: widgets.py:326
msgid "Invalid point"
msgstr "Point invalide"
-#: widgets.py:391
+#: widgets.py:382
msgid "Creation mode"
msgstr "Mode création"
-#: widgets.py:392
+#: widgets.py:383
msgid "To start drawing the route click on the toggle button: \"Draw\"."
msgstr ""
"Pour commencer le dessin cliquez sur le bouton&nbsp;: «&nbsp;Tracer&nbsp;»."
-#: widgets.py:394
+#: widgets.py:385
msgid "Then click on the map to begin the drawing."
msgstr "Puis cliquez sur la carte pour commencer le dessin."
-#: widgets.py:395
+#: widgets.py:386
msgid "You can add points by clicking again."
msgstr "Vous pouvez ajouter des points en cliquant de nouveau."
-#: widgets.py:396
+#: widgets.py:387
msgid ""
"To finish the drawing double click. When the drawing is finished you can "
"edit it."
@@ -1012,7 +1090,7 @@ msgstr ""
"Pour finir le tracé double-cliquez. Quand le tracé est fini vous pouvez "
"toujours l'éditer."
-#: widgets.py:398
+#: widgets.py:389
msgid ""
"While creating to undo a drawing click again on the toggle button \"Stop "
"drawing\"."
@@ -1020,17 +1098,17 @@ msgstr ""
"En mode création vous pouvez annuler un tracé en appuyant sur le bouton "
"«&nbsp;Arrêter le tracé&nbsp;»."
-#: widgets.py:403
+#: widgets.py:394
msgid "Modification mode"
msgstr "Mode modification"
-#: widgets.py:404
+#: widgets.py:395
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."
-#: widgets.py:405
+#: widgets.py:396
msgid ""
"To delete a point move the mouse cursor over it and press the \"d\" or \"Del"
"\" key."
@@ -1038,7 +1116,7 @@ msgstr ""
"Pour supprimer un point, mettez le curseur de la souris sur celui-ci et "
"appuyez sur le touche «&nbsp;d&nbsp;» ou «&nbsp;Suppr&nbsp;»."
-#: widgets.py:407
+#: widgets.py:398
msgid ""
"To add a point click in the middle of a segment and drag the new point to "
"the desired position"
@@ -1047,51 +1125,51 @@ msgstr ""
"maintenez le bouton appuyé et déplacez le nouveau point à la position "
"désirée."
-#: widgets.py:414
+#: widgets.py:405
msgid "Give a name and set category before uploading a file."
msgstr ""
"Renseignez le nom et choisissez au moins une catégorie avant de déposer un "
"fichier."
-#: widgets.py:417
+#: widgets.py:408
msgid "Upload a route file (GPX or KML)"
msgstr "Déposer un trajet (fichier GPX ou KML)"
-#: widgets.py:418
+#: widgets.py:409
msgid "or"
msgstr "ou"
-#: widgets.py:423
+#: widgets.py:414
msgid "Start \"hand\" drawing"
msgstr "Commencer le tracé manuellement"
-#: widgets.py:446
+#: widgets.py:437
msgid "Move on the map"
msgstr "Se déplacer"
-#: widgets.py:446
+#: widgets.py:437
msgid "Draw"
msgstr "Tracer"
-#: widgets.py:536
+#: widgets.py:527
msgid "Hold CTRL, click and drag to select area on the map"
msgstr ""
"Maintenir la touche Control, cliquez puis glissez pour sélectionner une zone "
"sur la carte"
-#: widgets.py:593
+#: widgets.py:584
msgid "Type:"
msgstr "Type :"
-#: widgets.py:593
+#: widgets.py:584
msgid "Node"
msgstr "Nœud"
-#: widgets.py:594
+#: widgets.py:585
msgid "Way"
msgstr "Route"
-#: widgets.py:605
+#: widgets.py:596
msgid ""
"Enter an OSM \"tag=value\" string such as \"amenity=pub\". A list of common "
"tag is available <a href='https://wiki.openstreetmap.org/wiki/Map_Features' "
@@ -1101,39 +1179,39 @@ msgstr ""
"liste des clés est disponible <a href='https://wiki.openstreetmap.org/wiki/"
"FR:Map_Features' target='_blank'>ici</a>."
-#: widgets.py:612
+#: widgets.py:603
msgid "Tag:"
msgstr "Clé/valeur :"
-#: widgets.py:616
+#: widgets.py:607
msgid "You have to select an area."
msgstr "Vous devez sélectionner une zone."
-#: widgets.py:618
+#: widgets.py:609
msgid "You have to select a type."
msgstr "Vous devez sélectionner un type."
-#: widgets.py:620
+#: widgets.py:611
msgid "You have to insert a filter tag."
msgstr "Vous devez saisir une clé=valeur."
-#: widgets.py:622
+#: widgets.py:613
msgid "If you change the above form don't forget to refresh before submit!"
msgstr ""
"Si vous modifiez le formulaire ci-dessus n'oubliez pas de rafraîchir avant "
"de valider !"
-#: widgets.py:625
+#: widgets.py:616
msgid "You can put a Folder name of the KML file to filter on it."
msgstr ""
"Vous pouvez saisir le nom d'un « Folder » du fichier KML pour filter sur "
"celui-ci."
-#: widgets.py:633
+#: widgets.py:624
msgid "Refresh"
msgstr "Rafraîchir"
-#: widgets.py:699
+#: widgets.py:690
msgid "Select..."
msgstr "Sélectionner..."
@@ -1228,7 +1306,7 @@ msgstr ""
"Après ajout/modification de modèle de propriété vous aurez à recharger le "
"serveur web."
-#: templates/chimere/base.html:12
+#: templates/chimere/base.html:15
msgid "You must enable JavaScript in your browser to display Chimère."
msgstr ""
"Vous devez activer le JavaScript dans votre navigateur pour afficher Chimère."
@@ -1244,31 +1322,31 @@ msgstr ""
msgid "Submit"
msgstr "Proposer"
-#: templates/chimere/detail.html:16
+#: templates/chimere/detail.html:17
msgid "Date:"
msgstr "Date :"
-#: templates/chimere/detail.html:25
+#: templates/chimere/detail.html:26
msgid "Source:"
msgstr "Source :"
-#: templates/chimere/detail.html:26
+#: templates/chimere/detail.html:27
msgid "License:"
msgstr "Licence :"
-#: templates/chimere/detail.html:28
+#: templates/chimere/detail.html:29
msgid "Show multimedia gallery"
msgstr "Montrer la galerie multimedia"
-#: templates/chimere/detail.html:32
+#: templates/chimere/detail.html:34
msgid "Submit an amendment"
msgstr "Proposer une modification"
-#: templates/chimere/detail.html:35 templates/chimere/detail.html.py:36
+#: templates/chimere/detail.html:37 templates/chimere/detail.html.py:38
msgid "Propose amendment"
msgstr "Proposer une modification"
-#: templates/chimere/detail.html:35
+#: templates/chimere/detail.html:37
msgid "I would like to propose an amendment for this item:"
msgstr "Je souhaiterais proposer une modification pour cet élément :"
@@ -1304,11 +1382,11 @@ msgstr "Choisissez une localisation pour ce nouveau site"
msgid "indicates a mandatory field"
msgstr "indique un champ obligatoire"
-#: templates/chimere/edit.html:114
+#: templates/chimere/edit.html:120
msgid "Personal information"
msgstr "Informations personnelles"
-#: templates/chimere/edit.html:116
+#: templates/chimere/edit.html:122
msgid ""
"This fields are not mandatory. If you provided them they not will be made "
"public and they will only used to join you for this project."
@@ -1317,23 +1395,23 @@ msgstr ""
"pas publiés et ne seront utilisés seulement pour vous joindre dans le cadre "
"de ce projet."
-#: templates/chimere/edit.html:119
+#: templates/chimere/edit.html:125
msgid "Your name or nickname"
msgstr "Votre nom ou pseudo"
-#: templates/chimere/edit.html:124
+#: templates/chimere/edit.html:130
msgid "Your email"
msgstr "Votre courriel"
-#: templates/chimere/edit.html:129
+#: templates/chimere/edit.html:135
msgid "Comments about your submission"
msgstr "Commentaires au sujet de votre proposition"
-#: templates/chimere/edit.html:135
+#: templates/chimere/edit.html:141
msgid "Upload in progress. Please wait..."
msgstr "Dépôt en cours. Veuillez patienter..."
-#: templates/chimere/edit.html:153 templates/chimere/edit_route.html:78
+#: templates/chimere/edit.html:159 templates/chimere/edit_route.html:78
msgid "Propose"
msgstr "Proposez"
@@ -1345,7 +1423,7 @@ msgstr "Modifier un trajet"
msgid "Add a route"
msgstr "Ajout d'un nouveau trajet"
-#: templates/chimere/main_map.html:35
+#: templates/chimere/main_map.html:38
msgid "Simple map"
msgstr "Carte simple"
@@ -1410,8 +1488,8 @@ msgid "You must provide a web address."
msgstr "Vous devez fournir une adresse web."
#: templates/chimere/blocks/areas.html:4
-msgid "Areas:"
-msgstr "Zones :"
+msgid "Maps"
+msgstr "Cartes"
#: templates/chimere/blocks/areas_alternative.html:4
msgid "Shortcuts"
@@ -1440,19 +1518,19 @@ msgstr "Ce site utilise Chimère"
msgid "Map"
msgstr "Carte"
-#: templates/chimere/blocks/map.html:9
+#: templates/chimere/blocks/map.html:15
msgid "Loading of the map in progress"
msgstr "Chargement de la carte en cours"
-#: templates/chimere/blocks/map.html:13
+#: templates/chimere/blocks/map.html:19
msgid "Display options"
msgstr "Options d'affichage"
-#: templates/chimere/blocks/map.html:15
+#: templates/chimere/blocks/map.html:21
msgid "Map type"
msgstr "Type de carte"
-#: templates/chimere/blocks/map.html:24
+#: templates/chimere/blocks/map.html:32
msgid "Permalink"
msgstr "Lien permanent"
@@ -1507,7 +1585,7 @@ msgstr "Itinéraire"
msgid "Add a step"
msgstr "Ajouter une étape"
-#: templates/chimere/blocks/routing.html:17
+#: templates/chimere/blocks/routing.html:17 templates/search/search.html:33
msgid "Search"
msgstr "Rechercher"
@@ -1607,11 +1685,38 @@ msgstr "Description :"
msgid ":"
msgstr " :"
-#: templatetags/chimere_tags.py:80
+#: templates/search/search.html:3
+msgid "Do you mean: "
+msgstr "Voulez-vous dire :"
+
+#: templates/search/search.html:4
+msgid "?"
+msgstr " ?"
+
+#: templates/search/search.html:15
+msgid "No results found."
+msgstr "Pas de résultats trouvés."
+
+#: templates/search/search.html:23
+msgid "Previous"
+msgstr "Précédent"
+
+#: templates/search/search.html:24
+msgid "More results..."
+msgstr "Plus de résultats..."
+
+#: templates/search/search.html:38
+msgid "No exact match."
+msgstr "Pas de correspondance exacte."
+
+#: templatetags/chimere_tags.py:93
#, python-format
msgid "Welcome to the %s"
msgstr "Bienvenue sur %s"
+#~ msgid "Areas:"
+#~ msgstr "Zones :"
+
#~ msgid "Advanced options"
#~ msgstr "Options avancées"
diff --git a/chimere/migrations/0009_auto__add_field_marker_keywords__add_field_route_keywords.py b/chimere/migrations/0009_auto__add_field_marker_keywords__add_field_route_keywords.py
new file mode 100644
index 0000000..daeb1be
--- /dev/null
+++ b/chimere/migrations/0009_auto__add_field_marker_keywords__add_field_route_keywords.py
@@ -0,0 +1,298 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'Marker.keywords'
+ db.add_column('chimere_marker', 'keywords',
+ self.gf('django.db.models.fields.TextField')(max_length=200, null=True, blank=True),
+ keep_default=False)
+
+ # Adding field 'Route.keywords'
+ db.add_column('chimere_route', 'keywords',
+ self.gf('django.db.models.fields.TextField')(max_length=200, null=True, blank=True),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'Marker.keywords'
+ db.delete_column('chimere_marker', 'keywords')
+
+ # Deleting field 'Route.keywords'
+ db.delete_column('chimere_route', 'keywords')
+
+
+ models = {
+ 'chimere.aggregatedroute': {
+ 'Meta': {'object_name': 'AggregatedRoute', 'db_table': "'chimere_aggregated_routes'", 'managed': 'False'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'route': ('django.contrib.gis.db.models.fields.MultiLineStringField', [], {}),
+ 'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'subcategory': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.SubCategory']"})
+ },
+ 'chimere.area': {
+ 'Meta': {'ordering': "('order', 'name')", 'object_name': 'Area'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'default': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ 'default_subcategories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'dynamic_categories': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ 'external_css': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layers': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'areas'", 'blank': 'True', 'through': "orm['chimere.AreaLayers']", 'to': "orm['chimere.Layer']"}),
+ 'lower_right_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
+ 'restrict_to_extent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'subcategories': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'areas'", 'blank': 'True', 'db_table': "'chimere_subcategory_areas'", 'to': "orm['chimere.SubCategory']"}),
+ 'upper_left_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}),
+ 'urn': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'blank': 'True'}),
+ 'welcome_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.arealayers': {
+ 'Meta': {'ordering': "('order',)", 'object_name': 'AreaLayers'},
+ 'area': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Area']"}),
+ 'default': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Layer']"}),
+ 'order': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'chimere.category': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'Category'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'chimere.color': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'Color'},
+ 'code': ('django.db.models.fields.CharField', [], {'max_length': '6'}),
+ 'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.ColorTheme']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'chimere.colortheme': {
+ 'Meta': {'object_name': 'ColorTheme'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'chimere.icon': {
+ 'Meta': {'object_name': 'Icon'},
+ 'height': ('django.db.models.fields.IntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'width': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'chimere.importer': {
+ 'Meta': {'object_name': 'Importer'},
+ 'associate_marker_to_way': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'automatic_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'categories': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'to': "orm['chimere.SubCategory']", 'null': 'True', 'blank': 'True'}),
+ 'default_description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'default_localisation': ('chimere.widgets.PointField', [], {'null': 'True', 'blank': 'True'}),
+ 'default_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'filtr': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'get_description': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'importer_type': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
+ 'license': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'origin': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'source': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'source_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'source_file_alt': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'srid': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'state': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'zipped': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'chimere.importerkeycategories': {
+ 'Meta': {'object_name': 'ImporterKeyCategories'},
+ 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.SubCategory']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'importer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'key_categories'", 'to': "orm['chimere.Importer']"}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'chimere.layer': {
+ 'Meta': {'object_name': 'Layer'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer_code': ('django.db.models.fields.TextField', [], {'max_length': '300'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'chimere.marker': {
+ 'Meta': {'ordering': "('status', 'name')", 'object_name': 'Marker'},
+ 'available_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'import_key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'import_source': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'import_version': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'is_front_page': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ 'keywords': ('django.db.models.fields.TextField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'license': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'modified_since_import': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'not_for_osm': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'origin': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'point': ('chimere.widgets.PointField', [], {}),
+ 'ref_item': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'submited_marker'", 'null': 'True', 'to': "orm['chimere.Marker']"}),
+ 'route': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'associated_marker'", 'null': 'True', 'to': "orm['chimere.Route']"}),
+ 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'submiter_comment': ('django.db.models.fields.TextField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'submiter_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'submiter_name': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'submiter_session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.multimediaextension': {
+ 'Meta': {'object_name': 'MultimediaExtension'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'multimedia_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'extensions'", 'to': "orm['chimere.MultimediaType']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '6'})
+ },
+ 'chimere.multimediafile': {
+ 'Meta': {'object_name': 'MultimediaFile'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'marker': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'multimedia_files'", 'to': "orm['chimere.Marker']"}),
+ 'miniature': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'multimedia_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.MultimediaType']", 'null': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
+ },
+ 'chimere.multimediatype': {
+ 'Meta': {'object_name': 'MultimediaType'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'iframe': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'chimere.news': {
+ 'Meta': {'object_name': 'News'},
+ 'areas': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'to': "orm['chimere.Area']", 'null': 'True', 'blank': 'True'}),
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'content': ('django.db.models.fields.TextField', [], {}),
+ 'date': ('django.db.models.fields.DateField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_front_page': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.page': {
+ 'Meta': {'object_name': 'Page'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'content': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mnemonic': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '10', 'null': 'True', 'blank': 'True'}),
+ 'template_path': ('django.db.models.fields.CharField', [], {'max_length': '150', 'null': 'True', 'blank': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'chimere.picturefile': {
+ 'Meta': {'object_name': 'PictureFile'},
+ 'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'marker': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pictures'", 'to': "orm['chimere.Marker']"}),
+ 'miniature': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+ 'picture': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
+ 'thumbnailfile': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'thumbnailfile_height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'thumbnailfile_width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.property': {
+ 'Meta': {'object_name': 'Property'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'marker': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Marker']"}),
+ 'propertymodel': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.PropertyModel']"}),
+ 'value': ('django.db.models.fields.TextField', [], {})
+ },
+ 'chimere.propertymodel': {
+ 'Meta': {'ordering': "('order',)", 'object_name': 'PropertyModel'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mandatory': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {}),
+ 'subcategories': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'properties'", 'blank': 'True', 'to': "orm['chimere.SubCategory']"}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '1'})
+ },
+ 'chimere.propertymodelchoice': {
+ 'Meta': {'object_name': 'PropertyModelChoice'},
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'propertymodel': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'choices'", 'to': "orm['chimere.PropertyModel']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'chimere.route': {
+ 'Meta': {'ordering': "('status', 'name')", 'object_name': 'Route'},
+ 'associated_file': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.RouteFile']", 'null': 'True', 'blank': 'True'}),
+ 'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}),
+ 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'has_associated_marker': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'import_key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'import_source': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'import_version': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'keywords': ('django.db.models.fields.TextField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'license': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'modified_since_import': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'not_for_osm': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'origin': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'picture': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'ref_item': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'submited_route'", 'null': 'True', 'to': "orm['chimere.Route']"}),
+ 'route': ('chimere.widgets.RouteField', [], {}),
+ 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'submiter_comment': ('django.db.models.fields.TextField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'submiter_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'submiter_name': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'submiter_session_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.routefile': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'RouteFile'},
+ 'file_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'raw_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'simplified_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'})
+ },
+ 'chimere.subcategory': {
+ 'Meta': {'ordering': "['category', 'order']", 'object_name': 'SubCategory'},
+ 'as_layer': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'category': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'subcategories'", 'to': "orm['chimere.Category']"}),
+ 'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.ColorTheme']", 'null': 'True', 'blank': 'True'}),
+ 'dated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'hover_icon': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'subcat_hovered'", 'null': 'True', 'to': "orm['chimere.Icon']"}),
+ 'icon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Icon']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'item_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '1000'}),
+ 'routing_warn': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'submission': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'chimere.tinyurl': {
+ 'Meta': {'object_name': 'TinyUrl'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'parameters': ('django.db.models.fields.CharField', [], {'max_length': '500'})
+ }
+ }
+
+ complete_apps = ['chimere'] \ No newline at end of file
diff --git a/chimere/models.py b/chimere/models.py
index 5727098..77fdaf2 100644
--- a/chimere/models.py
+++ b/chimere/models.py
@@ -20,7 +20,7 @@
"""
Models description
"""
-import os, datetime, pyexiv2, re, string
+import os, datetime, pyexiv2, re, string, copy
import simplejson as json
from lxml import etree
from PIL import Image
@@ -422,6 +422,8 @@ class GeographicItem(models.Model):
submiter_comment = models.TextField(_(u"Submitter comment"), max_length=200,
blank=True, null=True)
status = models.CharField(_(u"Status"), max_length=1, choices=STATUS)
+ keywords = models.TextField(_(u"Keywords"), max_length=200,
+ blank=True, null=True)
import_key = models.CharField(_(u"Import key"), max_length=200,
blank=True, null=True)
import_version = models.IntegerField(_(u"Import version"),
@@ -658,31 +660,34 @@ class Marker(GeographicItem):
'''Return a GeoJSON string
'''
jsons = []
+ json_tpl = {"type":"Feature", "properties":{}}
for cat in self.categories.all():
if categories_id and cat.id not in categories_id:
continue
- items = {'id':self.id, 'name':json.dumps(self.name),
- 'geometry':self.point.geojson,
- 'icon_path':cat.icon.image,
+ items = copy.deepcopy(json_tpl)
+ items['geometry'] = json.loads(self.point.geojson)
+ items['properties'].update({
+ 'pk':self.id,
+ 'name':self.name,
+ 'icon_path':unicode(cat.icon.image),
'icon_hover_path':cat.hover_icon.image \
if cat.hover_icon else '',
- 'icon_width':cat.icon.image.width,
- 'icon_height':cat.icon.image.height,
- 'category_name':json.dumps(cat.name)}
- jsons.append(u'{"type":"Feature", "geometry":%(geometry)s, '\
- u'"properties":{"pk": %(id)d, "name": %(name)s, '\
- u'"icon_path":"%(icon_path)s", '\
- u'"icon_hover_path":"%(icon_hover_path)s", '\
- u'"icon_width":%(icon_width)d, '\
- u'"icon_height":%(icon_height)d, '\
- u'"category_name":%(category_name)s}}' % items)
- return ",".join(jsons)
+ 'category_name':cat.name})
+ try:
+ items['properties'].update({'icon_width':cat.icon.image.width,
+ 'icon_height':cat.icon.image.height,})
+ except IOError:
+ pass
+
+ jsons.append(items)
+
+ return json.dumps(jsons)
@property
def default_category(self):
# Should we select only available ones ?
# Should we catch if not exists ?
- cats = self.categories
+ cats = self.categories.filter(available=True, category__available=True)
if cats.count():
return cats.all()[0]
@@ -1142,11 +1147,11 @@ class Route(GeographicItem):
'''
if '#' not in color:
color = '#' + color
- attributes = {'id':self.id, 'name':json.dumps(self.name),
- 'color':color, 'geometry':self.route.geojson,}
- return u'{"type":"Feature", "geometry":%(geometry)s, '\
- u'"properties":{"pk": %(id)d, "name": %(name)s, '\
- u'"color":"%(color)s"}}' % attributes
+ attributes = {"type":"Feature",
+ "geometry":json.loads(self.route.geojson),
+ "properties":{"pk":self.id, "name":self.name,
+ "color":color}}
+ return json.dumps(attributes)
def getTinyUrl(self):
parameters = 'current_feature=%d&checked_categories=%s' % (self.id,
@@ -1241,11 +1246,10 @@ class AggregatedRoute(models.Model):
'''
if '#' not in color:
color = '#' + color
- attributes = {'id':self.id, 'name':json.dumps(u'Aggregated route'),
- 'color':color, 'geometry':self.route.geojson,}
- return u'{"type":"Feature", "geometry":%(geometry)s, '\
- u'"properties":{"pk": %(id)d, "name": %(name)s, '\
- u'"color":"%(color)s"}}' % attributes
+ attributes = {'color':color, 'geometry':json.loads(self.route.geojson),
+ 'type':"Feature", "properties":{"pk": self.id,
+ "name": u'Aggregated route',}}
+ return json.dumps(attributes)
class SimplePoint:
"""
@@ -1539,12 +1543,14 @@ class PropertyModel(models.Model):
('P', _('Password')),
('D', _("Date")),
('C', _("Choices")),
+ ('B', _("Boolean")),
)
TYPE_WIDGET = {'T':forms.TextInput,
'L':TextareaWidget,
'P':forms.PasswordInput,
'D':DatePickerWidget,
- 'C':forms.Select
+ 'C':forms.Select,
+ 'B':forms.CheckboxInput,
}
type = models.CharField(_(u"Type"), max_length=1, choices=TYPE)
def __unicode__(self):
diff --git a/chimere/search_indexes.py b/chimere/search_indexes.py
new file mode 100644
index 0000000..edf9bd1
--- /dev/null
+++ b/chimere/search_indexes.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2014-2015 É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.
+
+import datetime
+from haystack import indexes
+from django.db.models import Q
+
+from chimere import models
+from chimere.views import checkDate
+
+class GeographicItemIndex(indexes.SearchIndex):
+ text = indexes.CharField(document=True, use_template=True)
+ categories = indexes.MultiValueField()
+ # for autocomplete
+ content_auto = indexes.EdgeNgramField(model_attr='name')
+
+ def index_queryset(self, using=None):
+ q = checkDate(Q(status='A', categories__available=True,
+ categories__category__available=True))
+ return self.get_model().objects.filter(q).distinct('pk').order_by('pk')
+
+ def prepare_categories(self, obj):
+ cats = []
+ for cat in obj.categories.all():
+ cats.append(cat.name)
+ return cats
+
+class MarkerIndex(GeographicItemIndex, indexes.Indexable):
+ location = indexes.LocationField(model_attr='point')
+ def get_model(self):
+ return models.Marker
+
+"""
+class RouteIndex(GeographicItemIndex, indexes.Indexable):
+ location = indexes.LocationField()
+ def get_model(self):
+ return models.Route
+
+ def prepare_location(self, obj):
+ centroid = obj.route.centroid
+ return "%s,%s" % (centroid.y, centroid.x)
+"""
diff --git a/chimere/settings.sample.py b/chimere/settings.sample.py
index d0ffc42..1ddef5f 100644
--- a/chimere/settings.sample.py
+++ b/chimere/settings.sample.py
@@ -118,6 +118,15 @@ NOMINATIM_URL = 'http://nominatim.openstreetmap.org/search'
CHIMERE_THUMBS_SCALE_HEIGHT=250
CHIMERE_THUMBS_SCALE_WIDTH=None
+# search engine
+CHIMERE_SEARCH_ENGINE = False
+HAYSTACK_CONNECTIONS = {
+ 'default': {
+ 'ENGINE': 'haystack.backends.solr_backend.SolrEngine',
+ 'URL': 'http://127.0.0.1:8080/solr'
+ },
+}
+
CHIMERE_CSV_ENCODING = 'ISO-8859-1'
# generic contact email
diff --git a/chimere/static/chimere/css/styles.css b/chimere/static/chimere/css/styles.css
index ab68d68..fd39b79 100644
--- a/chimere/static/chimere/css/styles.css
+++ b/chimere/static/chimere/css/styles.css
@@ -21,6 +21,7 @@ h2, h3, th, .action li, .action li a,
color:#fff;
}
+#search-listing ul li a,
.action li.ui-state-active a,
#content .olControlLayerSwitcher,
.action li li.ui-state-active a{
@@ -62,7 +63,7 @@ fieldset, .action li, #content,
background-color:#FFF;
}
-div.warning, .errorlist{
+div.warning, .errorlist, .errorlist legend{
background-color:#ffca64;
}
@@ -74,23 +75,6 @@ div.warning, .errorlist{
border:1px solid #54c200;
}
-#layer_selection h4,
-#layer_selection #layer_list,
-#areas, #detail, #main-map,
-div.warning,
-#content,
-.action li.selected,
-#content .olControlLayerSwitcher .layersDiv,
-#panel, #map-footer, #chimere_itinerary_panel,
-#utils-div{
- border:1px solid #327e04;
-}
-
-
-.errorlist{
- border:1px solid #ff3f3f;
-}
-
/* rounded */
/* entête */
@@ -193,6 +177,13 @@ fieldset{
display:block;
}
+a:link[disabled],
+a[disabled] {
+ pointer-events: none;
+ cursor: default;
+ color: #ccc;
+}
+
#page_title{
position:absolute;
top:6px;
@@ -275,6 +266,11 @@ ul#action-2 {
padding:0.3em;
}
+#areas-div label{
+ color: #777;
+ font-variant:small-caps;
+}
+
#areas{
position:absolute;
z-index:5;
@@ -317,6 +313,16 @@ ul#action-2 {
margin:4px 8px;
}
+#close-detail{
+ position: absolute;
+ right: 10px;
+ font-weight:bold;
+}
+
+#close-detail:hover{
+ cursor:pointer;
+}
+
#news_content{
overflow:auto;
}
@@ -340,8 +346,10 @@ ul#action-2 {
ul.share{
list-style-type:none;
margin:0;
- padding:0;
- display:inline;
+ padding:0 1em;
+ display:block;
+ text-align:right;
+ font-style:italic;
}
ul.share li{
@@ -772,6 +780,46 @@ table.inline-table td input[type=file]{
margin-right: auto;
}
+#haystack-search .action-label{
+ display:none;
+}
+
+#search-box{
+ position:absolute;
+ z-index:200;
+ left:70px;
+ top:50px;
+ padding:0.3em;
+ padding-right:1.4em;
+ width:auto;
+ background-color:white;
+}
+
+#search-listing{
+ overflow:auto;
+}
+
+#search-listing ul{
+ list-style-type:none;
+ margin:0;
+ padding:4px;
+}
+
+#search-listing ul li{
+ padding:4px;
+}
+
+#search-listing a{
+ padding: 0.2em 0.5em;
+ border-radius:5px;
+}
+
+#search-listing a:hover{
+ text-decoration:none;
+ background-color:rgb(175, 231, 175);
+}
+
+
.alert-box .ui-dialog-titlebar {
display:none;
}
@@ -937,6 +985,12 @@ ul#multimedia_list_content li.multimedia{
min-height:50px;
}
+.errorlist legend{
+ margin:0;
+ padding:0 10px;
+ border:none;
+}
+
p.legend{
padding:0;
margin:0;
@@ -1051,7 +1105,7 @@ div.pp_default .pp_expand{
}
.olControlSimplePanZoom {
- top: 10px;
+ top: 20px;
right: 10px;
}
diff --git a/chimere/static/chimere/js/base.js b/chimere/static/chimere/js/base.js
index fe8d954..d7a9695 100644
--- a/chimere/static/chimere/js/base.js
+++ b/chimere/static/chimere/js/base.js
@@ -1,5 +1,5 @@
/* base function shared by some pages */
-/* Copyright (C) 2009-2013 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+/* Copyright (C) 2009-2014 É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
@@ -60,7 +60,6 @@ function open_window(url){
return false;
}
-
function saveExtent() {
/* save the current extent in a cookie */
if(!map) return;
@@ -141,3 +140,9 @@ function share_link_update(){
return false;
});
}
+
+$("a").on("click", function(event){
+ if ($(this).is("[disabled]")) {
+ event.preventDefault();
+ }
+});
diff --git a/chimere/static/chimere/js/jquery.chimere.js b/chimere/static/chimere/js/jquery.chimere.js
index c9f02af..d85490f 100644
--- a/chimere/static/chimere/js/jquery.chimere.js
+++ b/chimere/static/chimere/js/jquery.chimere.js
@@ -1,4 +1,4 @@
-/* Copyright (C) 2008-2012 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+/* Copyright (C) 2008-2015 É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
@@ -167,8 +167,7 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
new OpenLayers.Control.Zoom()];
} else {
settings.controls = [new OpenLayers.Control.Navigation(),
- new OpenLayers.Control.PanPanel(),
- new OpenLayers.Control.ZoomPanel(),
+ new OpenLayers.Control.SimplePanZoom(),
new OpenLayers.Control.ScaleLine()];
}
}
@@ -441,8 +440,36 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
methods.routingAddStep();
}
}
+
+ // verify that the initial display_feature is displayed
+ if (settings.display_feature){
+ var is_displayed = false;
+ for(j=0; j<settings.layerMarkers.markers.length;j++){
+ var c_marker = settings.layerMarkers.markers[j];
+ if(c_marker.pk == settings.display_feature){
+ is_displayed = true;
+ }
+ }
+ if (!is_displayed){
+ methods.loadMarker(settings.display_feature);
+ }
+ }
+ methods.update_permalink_activation();
+
methods.preload_images();
}, // end of init
+ update_permalink_activation:function(){
+ if (settings.checked_categories.length ||
+ settings.current_feature ||
+ settings.routing_speed ||
+ settings.routing_transport ||
+ settings.routing_start ||
+ settings.routing_end){
+ $("#permalink a").removeAttr("disabled");
+ } else {
+ $("#permalink a").attr("disabled", "disabled");
+ }
+ },
/* Preload icons */
preload_images: function(){
if (typeof extra_url == 'undefined') return;
@@ -581,6 +608,15 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
$('#chimere_map_menu').css('left', offsetX);
}
},
+ loadMarker: function(object_id) {
+ var uri = extra_url + "get-marker/" + object_id;
+ $.ajax({url: uri,
+ dataType: "json",
+ success: function (data) {
+ for (idx in data) methods.addMarker(data[idx]);
+ }
+ });
+ },
/*
* Load markers and route from DB
*/
@@ -626,9 +662,14 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
},
complete: function () {
if($('#waiting').length){$('#waiting').hide();}
+ methods.update_permalink_activation();
}
});
},
+ razMap: function() {
+ settings.layerMarkers.clearMarkers();
+ settings.layerVectors.removeAllFeatures();
+ },
/*
* Update the categories div in ajax
*/
@@ -701,6 +742,7 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
settings.permalink.updateLink();
});
$('.subcategories li input').bind("click", function (e) {
+ $('#search-result').html('');
var c_name = $(this).attr('name');
c_name = c_name.substr(c_name.lastIndexOf("_")+1);
if($(this).is(':checked')){
@@ -734,8 +776,8 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
var id = this.id.substr(this.id.lastIndexOf("_")+1);
helpers.zoom_to_subcategories([id]);
});
- $(".toggle_category").parent().bind("click", function (e) {
- var item = $(this).children('.toggle_category');
+ $(".toggle_category").bind("click", function (e) {
+ var item = $(this);
var id = item.attr('id').substr(item.attr('id').lastIndexOf("_")+1);
methods.toggle_category(id);
});
@@ -851,6 +893,7 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
settings.current_popup.groupDiv.onclick = methods.hidePopup;
}
settings.permalink.updateLink();
+ methods.update_permalink_activation();
}
var _repan_popup = function(){
/* re-pan manually */
@@ -882,7 +925,7 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
if (settings.current_popup == feature.popup) {
feature.popup.hide();
if (!settings.simple){
- $('#detail').hide();
+ $('#detail').fadeOut();
}
} else {
settings.current_popup.hide();
@@ -1332,7 +1375,7 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
}
else {
if (!settings.popupContentFull) {
- $('#detail').html(data).show();
+ $('#detail').html(data).fadeIn();
}
else {
settings.current_popup.setContentHTML("<div class='cloud'>" + data + "</div>");
@@ -1451,6 +1494,10 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
settings.map.zoomToExtent(extent, true);
return true;
},
+ zoomToMarkerExtent: function(){
+ settings.map.zoomToExtent(
+ settings.layerMarkers.getDataExtent());
+ },
// methods for edition
setMarker: function (event){
event = event || window.event;
@@ -1567,6 +1614,24 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
var bounds = settings.layerVectors.getDataExtent();
if (bounds) settings.map.zoomToExtent(bounds);
},
+ showPopup: function (feature_pk) {
+ for(j=0; j<settings.layerMarkers.markers.length;j++){
+ var c_marker = settings.layerMarkers.markers[j];
+ if(c_marker.pk == feature_pk){
+ c_marker.events.triggerEvent('click');
+ return true
+ }
+ }
+ return false;
+ //feature.markerClick();
+ //OpenLayers.Popup.popupSelect.clickFeature(feature);
+ /*
+ settings.current_popup = feature.marker._popup();
+ if (!settings.current_popup.visible()){
+ settings.current_popup.show();
+ methods.display_feature_detail(feature.pk);
+ }*/
+ },
hidePopup: function (evt) {
$('#'+settings.marker_hover_id).hide();
if (settings.hide_popup_fx) {
@@ -1575,17 +1640,20 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
else { // Default behaviour
if (settings.current_popup)
{
+ settings.current_feature = null;
if (!settings.simple){
- $('#detail').hide();
+ $('#detail').fadeOut();
}
if (settings.current_popup.visible()){
settings.current_popup.hide();
if(evt)
settings.map.events.triggerEvent('click', evt);
+ methods.update_permalink_activation();
return true;
}
}
}
+ methods.update_permalink_activation();
return false;
},
saveExtent: function(){
@@ -1681,6 +1749,10 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
methods.loadCategories();
}
},
+ zoom_to_latlon: function (){
+ var lonlat = new OpenLayers.LonLat(lon, lat);
+ settings.map.setCenter(f.lonlat);
+ },
getSavedExtent: function() {
/* get the current extent from a cookie */
var cookies = document.cookie.split(';');
diff --git a/chimere/static/chimere/js/search-autocomplete.js b/chimere/static/chimere/js/search-autocomplete.js
new file mode 100644
index 0000000..5e8a85e
--- /dev/null
+++ b/chimere/static/chimere/js/search-autocomplete.js
@@ -0,0 +1,108 @@
+
+var do_you_mean = "Do you mean: ";
+var end_do_you_mean = "?";
+
+var Autocomplete = function(options) {
+ this.form_selector = options.form_selector;
+ this.url = options.url || '/search/autocomplete/';
+ this.delay = parseInt(options.delay || 300);
+ this.minimum_length = parseInt(options.minimum_length || 3);
+ this.form_elem = null;
+ this.query_box = null;
+}
+
+Autocomplete.prototype.setup = function() {
+ var self = this;
+
+ this.form_elem = $(this.form_selector);
+ this.query_box = this.form_elem.find('input[name=q]');
+
+ // watch the input box.
+ this.query_box.on('keyup', function() {
+ var query = self.query_box.val();
+ if (query){
+ $('#haystack-search').removeAttr("disabled");
+ } else {
+ $('#haystack-search').attr('disabled', 'disabled');
+ }
+
+ if(query.length < self.minimum_length) {
+ return false;
+ }
+
+ self.fetch(query);
+ });
+
+ // on selecting a result, populate the search field.
+ this.form_elem.on('click', '.ac-result', function(ev) {
+ self.query_box.val($(this).text());
+ $('.ac-results').remove();
+ $('#spelling').fadeOut();
+ return false;
+ });
+
+ // on selecting a suggestion, populate the search field.
+ $('#search-box').on('click', '.spelling-item', function(ev) {
+ self.query_box.val($(this).text());
+ $('.ac-results').remove();
+ $('#spelling').fadeOut();
+ return false;
+ });
+}
+
+Autocomplete.prototype.fetch = function(query) {
+ var self = this ;
+
+ $.ajax({
+ url: this.url,
+ data: { 'q': query },
+ success: function(data) {
+ if(data.results.length){
+ self.show_results(data);
+ } else {
+ $('.ac-results').remove();
+ }
+ if(data.spelling.length){
+ self.show_spelling(data.spelling)
+ } else {
+ $("#spelling").fadeOut();
+ }
+ return true;
+ }
+ })
+}
+
+Autocomplete.prototype.show_spelling = function(spelling) {
+ var text = do_you_mean;
+ var base_elem = '<a href="#" class="spelling-item">'
+ var end_base_elem = '</a>';
+ for(var offset in spelling) {
+ if (offset > 0){
+ text += ", ";
+ }
+ text += base_elem;
+ text += spelling[offset];
+ text += end_base_elem;
+ }
+ text += end_do_you_mean;
+ $("#spelling").html(text);
+ $("#spelling").fadeIn();
+}
+
+Autocomplete.prototype.show_results = function(data) {
+ // Remove any existing results.
+ $('.ac-results').remove();
+
+ var results = data.results || []
+ var results_wrapper = $('<div class="ac-results"></div>');
+ var base_elem = $('<div class="result-wrapper"><a href="#" class="ac-result"></a></div>');
+
+ for(var res_offset in results) {
+ var elem = base_elem.clone();
+ // don't use .html(...) here, as it opens to XSS.
+ elem.find('.ac-result').text(results[res_offset]);
+ results_wrapper.append(elem);
+ }
+
+ this.query_box.after(results_wrapper)
+}
diff --git a/chimere/static/chimere/js/search.js b/chimere/static/chimere/js/search.js
new file mode 100644
index 0000000..6f46f3f
--- /dev/null
+++ b/chimere/static/chimere/js/search.js
@@ -0,0 +1,32 @@
+function load_search_box(){
+ if (!search_url) return;
+ $.ajax({url: search_url}).done(function( data ) {
+ $("#search-box").html(data);
+ });
+}
+
+function haystack_search(evt, page){
+ search_result = new Array();
+ $('#categories').find('#ul_categories > li > input').attr("checked", false);
+ if (!$('#id_q').val()) return false;
+
+ var c_url = search_url + "?q=" + $('#id_q').val();
+ if (page){
+ c_url += '&page=' + page;
+ }
+ $.get(c_url).done(function( data ) {
+ $('.ac-results').remove();
+ $('#search-result').html(data).show('slow');
+ });
+ return false;
+}
+
+// disable enter
+$(window).keydown(function(event){
+ if ($("#haystack-search").length && event.keyCode == 13) {
+ event.preventDefault();
+ $("#haystack-search").click();
+ return false;
+ }
+});
+
diff --git a/chimere/templates/chimere/base.html b/chimere/templates/chimere/base.html
index 894cb01..82f28c4 100644
--- a/chimere/templates/chimere/base.html
+++ b/chimere/templates/chimere/base.html
@@ -24,6 +24,7 @@
{% endblock %}
{% block content %}
{% block top %}{% endblock %}
+ {% if has_search %} {% block search_box %}{% endblock %} {% endif %}
{% block message_map %}{% endblock %}
{% block message_edit %}{% endblock %}
{% block bottom %}{% endblock %}
diff --git a/chimere/templates/chimere/blocks/areas.html b/chimere/templates/chimere/blocks/areas.html
index a1ebe76..268912c 100644
--- a/chimere/templates/chimere/blocks/areas.html
+++ b/chimere/templates/chimere/blocks/areas.html
@@ -1,7 +1,7 @@
{% load i18n %}
{% if areas and areas.count > 1 %}
<div id='areas-div'>
- <label for='areas-select'>{% trans "Areas:" %}</label>
+ <label for='areas-select'>{% trans "Maps" %}</label>
<select id='areas-select'>
{% if not has_default_area %}<option value=''>--</option>{% endif %}
{% for area in areas %}
diff --git a/chimere/templates/chimere/blocks/footer.html b/chimere/templates/chimere/blocks/footer.html
index a783939..f5830ed 100644
--- a/chimere/templates/chimere/blocks/footer.html
+++ b/chimere/templates/chimere/blocks/footer.html
@@ -1,3 +1,3 @@
{% load i18n %}
-{% trans "This site uses Chimère"%} - <img src="{{STATIC_URL}}chimere/img/copyleft.png" alt="copyleft"/> 2008-2013 <a href='http://www.chymeres.net/'>Chimère project</a> - {% trans "Map"%} <img src="{{STATIC_URL}}chimere/img/copyleft.png" alt="copyleft"/> <a href='http://openstreetmap.org/'>OpenStreetMap</a>
+{% trans "This site uses Chimère"%} <img src="{{STATIC_URL}}chimere/img/copyleft.png" alt="copyleft"/> 2008-2015 <a href='http://www.chymeres.net/'>Chimère project</a> - {% trans "Map"%} <img src="{{STATIC_URL}}chimere/img/copyleft.png" alt="copyleft"/> <a href='http://openstreetmap.org/'>OpenStreetMap</a>
diff --git a/chimere/templates/chimere/detail.html b/chimere/templates/chimere/detail.html
index ed0ea5c..abdd76e 100644
--- a/chimere/templates/chimere/detail.html
+++ b/chimere/templates/chimere/detail.html
@@ -1,5 +1,6 @@
{% load i18n sanitize chimere_tags %}
-<h2 class='ui-widget ui-state-default ui-corner-all ui-widget-header'>{{ marker.name }}</h2>
+<button onclick='$("#detail").fadeOut();return false;' type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
+<h2>{{ marker.name }}</h2>
<div class='detail_content'>
{% if marker.default_pictures or marker.default_pictures or marker.default_multimedia_items%}
<div class='small-gallery'>
@@ -28,6 +29,7 @@
<a href='#' class='show_gallery_link'>{% trans "Show multimedia gallery" %}</a>
</p>{% endif %}
</div>
+ {% share_bar marker.name %}
<p class='detail_amendment'><a href='{% if marker.route %}{% url chimere:editroute-item area_name_slash|default_if_none:"" marker.route.pk "" %}{%else%}{% url chimere:edit-item area_name_slash|default_if_none:"" marker.pk "" %}{%endif%}'>
{% trans "Submit an amendment" %}
</a>
@@ -35,7 +37,6 @@
<a href="mailto:?from={{moderator_emails}}&subject={% trans "Propose amendment" %}&body={% trans "I would like to propose an amendment for this item:"%} {{share_url}}">
{% trans "Propose amendment" %}
</a>{%endif%}
- {% share_bar marker.name %}
</div>
{% if marker.multimedia_items %}
<div id='gallery-{{time_now}}' class='gallery'>
diff --git a/chimere/templates/chimere/edit.html b/chimere/templates/chimere/edit.html
index f9706d3..0e1b849 100644
--- a/chimere/templates/chimere/edit.html
+++ b/chimere/templates/chimere/edit.html
@@ -52,6 +52,12 @@
{{ form.description }}
<p class="help">{{ form.description.help_text }}</p>
</div>
+ <div class="fieldWrapper">
+ <label for="id_keywords">{% trans "Keywords" %}</label>
+ {{ form.keywords.errors }}
+ {{ form.keywords }}
+ <p class="help">{{ form.keywords.help_text }}</p>
+ </div>
{% if dated %}
<div class="fieldWrapper">
<label for="id_start_date">{% trans "Start date" %}</label>
diff --git a/chimere/templates/chimere/main_map.html b/chimere/templates/chimere/main_map.html
index b7aa868..a3dec9a 100644
--- a/chimere/templates/chimere/main_map.html
+++ b/chimere/templates/chimere/main_map.html
@@ -5,6 +5,9 @@
{% head_chimere %}
{% head_jme %}
<script src="{{ STATIC_URL }}chimere/js/jquery.chimere.js" type="text/javascript"></script>
+{% if has_search %}
+<script src="{{ STATIC_URL }}chimere/js/search.js" type="text/javascript"></script>
+<script src="{{ STATIC_URL }}chimere/js/search-autocomplete.js" type="text/javascript"></script>{% endif %}
{{ block.super }}
{% endblock %}
{% block message_edit %}{% endblock %}
@@ -36,6 +39,10 @@
</div>
<div id='detail' class='ui-widget ui-corner-all'></div>
{% endblock %}
+{% block search_box %}
+<div id='search-box' class='ui-widget ui-corner-all'>
+</div>
+{% endblock %}
{% block content %}
{{block.super}}
<div id='main-map'></div>
@@ -46,7 +53,16 @@
{% map 'main-map' %}
<div id='chimere_message'></div>
<script type='text/javascript'>
- $(function(){$('#chimere_message').dialog({'autoOpen':false});});
+ var has_search = {% if has_search %}true{% else %}false{% endif %};
+ // array to keep trace of already displayed items
+ var search_result = new Array();
+ var search_url = "/search/";
+ $(function(){
+ $('#chimere_message').dialog({'autoOpen':false});
+ if (has_search){
+ load_search_box();
+ }
+ });
</script>
{% endblock %}
{% block footer %}
diff --git a/chimere/templates/chimere/main_map_simple.html b/chimere/templates/chimere/main_map_simple.html
index 4a1b603..4e93f8c 100644
--- a/chimere/templates/chimere/main_map_simple.html
+++ b/chimere/templates/chimere/main_map_simple.html
@@ -7,7 +7,7 @@
{% block sidebar %}
<div id='panel'>
<a href='#' onclick='showHide("categories")'>
- <h2>{% trans "Categories"%}</h2>
+ <h2 class='btn'>{% trans "Categories"%}</h2>
</a>
<form method='post' name='frm_categories' id='frm_categories'>
<div id='categories' name='categories'></div>
diff --git a/chimere/templates/search/indexes/chimere/marker_text.txt b/chimere/templates/search/indexes/chimere/marker_text.txt
new file mode 100644
index 0000000..7c7929d
--- /dev/null
+++ b/chimere/templates/search/indexes/chimere/marker_text.txt
@@ -0,0 +1,4 @@
+{% load unescape %}
+{{object.name}}
+{{object.description|safe|striptags|unescape}}
+{{object.keywords}}
diff --git a/chimere/templates/search/indexes/chimere/route_text.txt b/chimere/templates/search/indexes/chimere/route_text.txt
new file mode 100644
index 0000000..5e612cd
--- /dev/null
+++ b/chimere/templates/search/indexes/chimere/route_text.txt
@@ -0,0 +1,2 @@
+{{object.name}}
+{{object.keywords}}
diff --git a/chimere/templates/search/search.html b/chimere/templates/search/search.html
new file mode 100644
index 0000000..95d3937
--- /dev/null
+++ b/chimere/templates/search/search.html
@@ -0,0 +1,51 @@
+{% load url from future %}{% load i18n %}
+<script type='text/javascript'>
+var do_you_mean = "{% trans 'Do you mean: ' %}";
+var end_do_you_mean = "{% trans '?' %}";
+</script>
+{% if query %}
+{% include "search/search_js.html" %}
+<div id='search-listing'>
+ <ul>
+{% for result in page.object_list %}
+ <li>
+ <img src='{{MEDIA_URL}}{{result.object.default_category.icon.image}}'/><a href="#" onclick="$('#main-map').chimere('showPopup', {{result.object.pk}});return false;">{{ result.object }}</a>
+ </li>
+{% empty %}
+ <li>{% trans "No results found." %}</li>
+{% endfor %}
+ </ul>
+</div>
+{% if page.has_previous or page.has_next %}
+ <div id='search-nav'>
+ <nav>
+ <ul class="pager">
+ {% if page.has_previous %}<li class="previous"><a href="#" onclick="haystack_search(this, {{ page.previous_page_number }});">&larr; {% trans "Previous" %}</a></li>{% endif %}
+ {% if page.has_next %}<li class="next"><a href="#" onclick="haystack_search(this, {{ page.next_page_number }});">{% trans "More results..." %} &rarr;</a></li>{% endif %}
+ </ul>
+ </nav>
+ </div>
+{% endif %}
+
+{% else %}
+<form id='search-form' class='autocomplete-me'>
+ <input type="text" id="id_q" name="q" autocomplete="off"/>
+ <button name='haystack-search' id='haystack-search' type='button' disabled='disabled' class="btn btn-default"><span class='action-label'>{% trans "Search" %} </span><span class="glyphicon glyphicon-search"></span></button>
+</form>
+<div id='spelling'></div>
+<div id='search-result'></div>
+<script type='text/javascript'>
+no_result_message = "{% trans 'No exact match.' %}";
+$(function(){
+ $('#haystack-search').click(
+ function(evt){
+ $("#main-map").chimere("razMap");
+ haystack_search(evt);
+ });
+ window.autocomplete = new Autocomplete({
+ form_selector: '.autocomplete-me'
+ });
+ window.autocomplete.setup();
+});
+</script>
+{% endif %}
diff --git a/chimere/templates/search/search_js.html b/chimere/templates/search/search_js.html
new file mode 100644
index 0000000..dc95007
--- /dev/null
+++ b/chimere/templates/search/search_js.html
@@ -0,0 +1,26 @@
+<script type='text/javascript'>
+$(function(){
+ // clean checked categories
+ $('.subcategory').each(function(){ $(this).removeClass('selected'); });
+ $('.subcategory input[type=checkbox]').attr('checked', false);
+
+ var geo_objects = [];
+ {% for result in page.object_list %}var c_lst ={{result.object.getGeoJSON|safe}};
+ for (idx in c_lst){
+ geo_objects.push(c_lst[idx]);
+ }{% endfor %}
+ var geo_features = {};
+ for (idx=0 ; idx < geo_objects.length ; idx++){
+ var c_idx = geo_objects[idx].properties.pk;
+ if (search_result.indexOf(c_idx) == -1){
+ search_result.push(c_idx);
+ geo_features[c_idx] = $('#main-map').chimere('addMarker',
+ geo_objects[idx]);
+ }
+ }
+ if (geo_objects.length){
+ window.setTimeout(function(){$("#main-map").chimere("zoomToMarkerExtent")}, 500);
+ }
+});
+</script>
+
diff --git a/chimere/templatetags/chimere_tags.py b/chimere/templatetags/chimere_tags.py
index 15b8afa..ff0a9c8 100644
--- a/chimere/templatetags/chimere_tags.py
+++ b/chimere/templatetags/chimere_tags.py
@@ -80,6 +80,13 @@ def display_welcome(context, display=False, title=''):
context_data['welcome_message'] = area.welcome_message
except ObjectDoesNotExist:
pass
+ else:
+ try:
+ area = Area.objects.get(default=True)
+ context_data['area_name'] = context['area_name']
+ context_data['welcome_message'] = area.welcome_message
+ except ObjectDoesNotExist:
+ pass
context_data['news_lst'] = get_news(area)[:3]
context_data['STATIC_URL'] = settings.STATIC_URL
context_data['title'] = title if title \
diff --git a/chimere/templatetags/unescape.py b/chimere/templatetags/unescape.py
new file mode 100644
index 0000000..59809a3
--- /dev/null
+++ b/chimere/templatetags/unescape.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from django import template
+import HTMLParser
+
+register = template.Library()
+
+def unescape(value):
+ parser = HTMLParser.HTMLParser()
+ return parser.unescape(value)
+
+register.filter(unescape)
+
+
diff --git a/chimere/urls.py b/chimere/urls.py
index 164ef68..28a7098 100644
--- a/chimere/urls.py
+++ b/chimere/urls.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (C) 2008-2012 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+# Copyright (C) 2008-2014 É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
@@ -68,6 +68,22 @@ if hasattr(settings, 'CHIMERE_ENABLE_ROUTING') \
'route', name="route"),
)
+if hasattr(settings, 'CHIMERE_SEARCH_ENGINE') \
+ and settings.CHIMERE_SEARCH_ENGINE:
+ from chimere.forms import SearchForm
+ from chimere.views import SearchView
+ from haystack.views import search_view_factory
+ urlpatterns += patterns('chimere.views',
+ url(r'^search/?$', search_view_factory(
+ view_class=SearchView,
+ template='search/search.html',
+ form_class=SearchForm
+ ), name='haystack_search'),
+ url(r'^search/autocomplete/$', 'autocomplete',
+ name='autocomplete-search')
+ )
+ #urlpatterns += [url(r'^search/', include('haystack.urls')),]
+
urlpatterns += patterns('chimere.views',
url(r'^charte/?$', 'charte', name="charte"),
url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?contact/?$', 'contactus',
@@ -87,6 +103,8 @@ urlpatterns += patterns('chimere.views',
url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?getGeoObjects/'\
r'(?P<category_ids>[a-zA-Z0-9_-]+)(/(?P<status>\w+))?$', 'getGeoObjects',
name="getgeoobjects"),
+ url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?get-marker/'\
+ r'(?P<pk>[0-9]+)$', 'getMarker', name="get-marker"),
url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?getAvailableCategories/$',
'get_available_categories', name="get_categories"),
url(r'^(?P<area_name>[a-zA-Z0-9_-]+/)?getAllCategories/$',
diff --git a/chimere/views.py b/chimere/views.py
index a417fad..5beb5fd 100644
--- a/chimere/views.py
+++ b/chimere/views.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (C) 2008-2013 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+# Copyright (C) 2008-2014 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
#
# RSS : Copyright (C) 2010 Pierre Clarenc <pierre.crc_AT_gmailDOTcom>,
# Samuel Renard <renard.samuel_AT_gmailDOTcom>,
@@ -27,6 +27,7 @@ Views of the project
import datetime
from itertools import groupby
import re
+import simplejson as json
from django.conf import settings
from django.contrib.gis.geos import GEOSGeometry
@@ -175,13 +176,15 @@ def index(request, area_name=None, default_area=None, simple=False,
'actions':actions(response_dct['area_name']),
'action_selected':('view',),
'error_message':'',
+ 'is_map':True,
'news_visible': news_visible,
'areas_visible': settings.CHIMERE_DISPLAY_AREAS,
'map_layer':settings.CHIMERE_DEFAULT_MAP_LAYER,
'dynamic_categories':response_dct['dynamic_categories'],
'zoomout':zoomout,
'has_default_area':Area.objects.filter(default=True).count(),
- 'zoomout':zoomout
+ 'zoomout':zoomout,
+ 'has_search':settings.CHIMERE_SEARCH_ENGINE
})
if hasattr(settings, 'CONTACT_EMAIL') and settings.CONTACT_EMAIL:
response_dct['contact_email'] = settings.CONTACT_EMAIL
@@ -615,9 +618,10 @@ def getGeoObjects(request, area_name, category_ids, status):
current_cat = c_cat
colors = list(Color.objects.filter(color_theme = c_cat.color_theme))
if colors:
- jsons.append(route.getGeoJSON(color=colors[idx % len(colors)].code))
+ jsons.append(json.loads(
+ route.getGeoJSON(color=colors[idx % len(colors)].code)))
else:
- jsons.append(route.getGeoJSON(color='000'))
+ jsons.append(json.loads(route.getGeoJSON(color='000')))
idx += 1
try:
q = checkDate(Q(status__in=status, categories__in=category_ids))
@@ -625,11 +629,19 @@ def getGeoObjects(request, area_name, category_ids, status):
except:
return HttpResponse('no results')
category_ids = [int(cat_id) for cat_id in category_ids]
- jsons += [geo_object.getGeoJSON(category_ids) for geo_object in list(query)]
+ for geo_object in list(query):
+ jsons += json.loads(geo_object.getGeoJSON(category_ids))
if not jsons:
return HttpResponse('no results')
- data = '{"type": "FeatureCollection", "features":[%s]}' % ",".join(jsons)
- return HttpResponse(data)
+ data = json.dumps({"type": "FeatureCollection", "features":jsons})
+ return HttpResponse(data, content_type="application/json")
+
+def getMarker(request, area_name, pk):
+ q = Marker.objects.filter(pk=pk, status='A')
+ if not q.count():
+ return HttpResponse('{}')
+ data = q.all()[0].getGeoJSON()
+ return HttpResponse(data, content_type="application/json")
def get_all_categories(request, area_name=None):
'''
@@ -945,3 +957,30 @@ def rss(request, area_name=''):
else:
return render_to_response('chimere/feeds/rss.html', response_dct,
context_instance=RequestContext(request))
+
+from django.core.paginator import Paginator, InvalidPage
+
+SearchView = None
+autocomplete = None
+if hasattr(settings, 'CHIMERE_SEARCH_ENGINE') \
+ and settings.CHIMERE_SEARCH_ENGINE:
+ from haystack.views import SearchView as HaystackSearchView
+ from haystack.query import SearchQuerySet
+ class SearchView(HaystackSearchView):
+ pass
+ def autocomplete(request):
+ sqs = SearchQuerySet().autocomplete(
+ content_auto=request.GET.get('q', ''))[:5]
+ suggestions = [result.object.name for result in sqs if result.object]
+ spelling = []
+ if not suggestions:
+ spelling = SearchQuerySet().spelling_suggestion(
+ request.GET.get('q', '')) or []
+ # convert to list spelling...
+ # make sure it returns a JSON object, not a bare list.
+ # otherwise, it could be vulnerable to an XSS attack.
+ the_data = json.dumps({
+ 'results': suggestions,
+ 'spelling':spelling,
+ })
+ return HttpResponse(the_data, content_type='application/json')
diff --git a/conf/solr/schema-fr.xml b/conf/solr/schema-fr.xml
new file mode 100644
index 0000000..68605c8
--- /dev/null
+++ b/conf/solr/schema-fr.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="default" version="1.4">
+ <types>
+ <fieldtype name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
+ <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
+ <fieldtype name="binary" class="solr.BinaryField"/>
+
+ <!-- Numeric field types that manipulate the value into
+ a string value that isn't human-readable in its internal form,
+ but with a lexicographic ordering the same as the numeric ordering,
+ so that range queries work correctly. -->
+ <fieldType name="int" class="solr.TrieIntField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
+ <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
+ <fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
+ <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
+
+ <fieldType name="tint" class="solr.TrieIntField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+ <fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+ <fieldType name="tlong" class="solr.TrieLongField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+ <fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+
+ <fieldType name="date" class="solr.TrieDateField" omitNorms="true" precisionStep="0" positionIncrementGap="0"/>
+ <!-- A Trie based date field for faster date range queries and date faceting. -->
+ <fieldType name="tdate" class="solr.TrieDateField" omitNorms="true" precisionStep="6" positionIncrementGap="0"/>
+
+ <fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
+ <fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
+ <fieldtype name="geohash" class="solr.GeoHashField"/>
+
+ <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
+ <analyzer type="index">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
+ <!-- in this example, we will only use synonyms at query time
+ <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
+ -->
+ <filter class="solr.LowerCaseFilterFactory"/>
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
+ <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="text_fr" class="solr.TextField" positionIncrementGap="100">
+ <analyzer type="index">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory"
+ ignoreCase="true"
+ words="lang/stopwords_fr.txt"
+ enablePositionIncrements="true"
+ />
+ <filter class="solr.LowerCaseFilterFactory"/>
+
+ <filter class="solr.ElisionFilterFactory" ignoreCase="true" articles="lang/contractions_fr.txt"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_fr.txt" format="snowball" enablePositionIncrements="true"/>
+ <filter class="solr.FrenchLightStemFilterFactory"/>
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory"
+ ignoreCase="true"
+ words="lang/stopwords_fr.txt"
+ enablePositionIncrements="true"
+ />
+ <filter class="solr.LowerCaseFilterFactory"/>
+
+ <filter class="solr.ElisionFilterFactory" ignoreCase="true" articles="lang/contractions_fr.txt"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_fr.txt" format="snowball" enablePositionIncrements="true"/>
+ <filter class="solr.FrenchLightStemFilterFactory"/>
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
+ <analyzer>
+ <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="ngram" class="solr.TextField" >
+ <analyzer type="index">
+ <tokenizer class="solr.KeywordTokenizerFactory"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.NGramFilterFactory" minGramSize="3" maxGramSize="15" />
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.KeywordTokenizerFactory"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="edge_ngram" class="solr.TextField" positionIncrementGap="1">
+ <analyzer type="index">
+ <tokenizer class="solr.WhitespaceTokenizerFactory" />
+ <filter class="solr.LowerCaseFilterFactory" />
+ <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
+ <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front" />
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.WhitespaceTokenizerFactory" />
+ <filter class="solr.LowerCaseFilterFactory" />
+ <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
+ </analyzer>
+ </fieldType>
+ </types>
+
+ <fields>
+ <!-- general -->
+ <field name="id" type="string" indexed="true" stored="true" multiValued="false" required="true"/>
+ <field name="django_ct" type="string" indexed="true" stored="true" multiValued="false"/>
+ <field name="django_id" type="string" indexed="true" stored="true" multiValued="false"/>
+
+ <dynamicField name="*_i" type="int" indexed="true" stored="true"/>
+ <dynamicField name="*_s" type="string" indexed="true" stored="true"/>
+ <dynamicField name="*_l" type="long" indexed="true" stored="true"/>
+ <dynamicField name="*_t" type="text_fr" indexed="true" stored="true"/>
+ <dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
+ <dynamicField name="*_f" type="float" indexed="true" stored="true"/>
+ <dynamicField name="*_d" type="double" indexed="true" stored="true"/>
+ <dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
+ <dynamicField name="*_p" type="location" indexed="true" stored="true"/>
+ <dynamicField name="*_coordinate" type="tdouble" indexed="true" stored="false"/>
+
+
+ <field name="text" type="text_fr" indexed="true" stored="true" multiValued="false" />
+
+ <field name="location" type="location" indexed="true" stored="true" multiValued="false" />
+
+ <field name="categories" type="text_fr" indexed="true" stored="true" multiValued="true" />
+
+ <field name="content_auto" type="edge_ngram" indexed="true" stored="true" multiValued="false" />
+
+ </fields>
+
+ <!-- field to use to determine and enforce document uniqueness. -->
+ <uniqueKey>id</uniqueKey>
+
+ <!-- field for the QueryParser to use when an explicit fieldname is absent -->
+ <defaultSearchField>text</defaultSearchField>
+
+ <!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->
+ <solrQueryParser defaultOperator="AND"/>
+</schema>
+
+
diff --git a/conf/solr/schema.xml b/conf/solr/schema.xml
new file mode 100644
index 0000000..ba25e5c
--- /dev/null
+++ b/conf/solr/schema.xml
@@ -0,0 +1,168 @@
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="default" version="1.4">
+ <types>
+ <fieldtype name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
+ <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
+ <fieldtype name="binary" class="solr.BinaryField"/>
+
+ <!-- Numeric field types that manipulate the value into
+ a string value that isn't human-readable in its internal form,
+ but with a lexicographic ordering the same as the numeric ordering,
+ so that range queries work correctly. -->
+ <fieldType name="int" class="solr.TrieIntField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
+ <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
+ <fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
+ <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" omitNorms="true" sortMissingLast="true" positionIncrementGap="0"/>
+
+ <fieldType name="tint" class="solr.TrieIntField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+ <fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+ <fieldType name="tlong" class="solr.TrieLongField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+ <fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
+
+ <fieldType name="date" class="solr.TrieDateField" omitNorms="true" precisionStep="0" positionIncrementGap="0"/>
+ <!-- A Trie based date field for faster date range queries and date faceting. -->
+ <fieldType name="tdate" class="solr.TrieDateField" omitNorms="true" precisionStep="6" positionIncrementGap="0"/>
+
+ <fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
+ <fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
+ <fieldtype name="geohash" class="solr.GeoHashField"/>
+
+ <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
+ <analyzer type="index">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
+ <!-- in this example, we will only use synonyms at query time
+ <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
+ -->
+ <filter class="solr.LowerCaseFilterFactory"/>
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
+ <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="text_en" class="solr.TextField" positionIncrementGap="100">
+ <analyzer type="index">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory"
+ ignoreCase="true"
+ words="stopwords_en.txt"
+ enablePositionIncrements="true"
+ />
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.EnglishPossessiveFilterFactory"/>
+ <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
+ <!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
+ <filter class="solr.EnglishMinimalStemFilterFactory"/>
+ -->
+ <filter class="solr.PorterStemFilterFactory"/>
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
+ <filter class="solr.StopFilterFactory"
+ ignoreCase="true"
+ words="stopwords_en.txt"
+ enablePositionIncrements="true"
+ />
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.EnglishPossessiveFilterFactory"/>
+ <filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
+ <!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
+ <filter class="solr.EnglishMinimalStemFilterFactory"/>
+ -->
+ <filter class="solr.PorterStemFilterFactory"/>
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
+ <analyzer>
+ <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="ngram" class="solr.TextField" >
+ <analyzer type="index">
+ <tokenizer class="solr.KeywordTokenizerFactory"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.NGramFilterFactory" minGramSize="3" maxGramSize="15" />
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.KeywordTokenizerFactory"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="edge_ngram" class="solr.TextField" positionIncrementGap="1">
+ <analyzer type="index">
+ <tokenizer class="solr.WhitespaceTokenizerFactory" />
+ <filter class="solr.LowerCaseFilterFactory" />
+ <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
+ <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front" />
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.WhitespaceTokenizerFactory" />
+ <filter class="solr.LowerCaseFilterFactory" />
+ <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
+ </analyzer>
+ </fieldType>
+ </types>
+
+ <fields>
+ <!-- general -->
+ <field name="id" type="string" indexed="true" stored="true" multiValued="false" required="true"/>
+ <field name="django_ct" type="string" indexed="true" stored="true" multiValued="false"/>
+ <field name="django_id" type="string" indexed="true" stored="true" multiValued="false"/>
+
+ <dynamicField name="*_i" type="int" indexed="true" stored="true"/>
+ <dynamicField name="*_s" type="string" indexed="true" stored="true"/>
+ <dynamicField name="*_l" type="long" indexed="true" stored="true"/>
+ <dynamicField name="*_t" type="text_en" indexed="true" stored="true"/>
+ <dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
+ <dynamicField name="*_f" type="float" indexed="true" stored="true"/>
+ <dynamicField name="*_d" type="double" indexed="true" stored="true"/>
+ <dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
+ <dynamicField name="*_p" type="location" indexed="true" stored="true"/>
+ <dynamicField name="*_coordinate" type="tdouble" indexed="true" stored="false"/>
+
+
+ <field name="text" type="text_en" indexed="true" stored="true" multiValued="false" />
+
+ <field name="location" type="location" indexed="true" stored="true" multiValued="false" />
+
+ <field name="categories" type="text_en" indexed="true" stored="true" multiValued="true" />
+
+ <field name="content_auto" type="edge_ngram" indexed="true" stored="true" multiValued="false" />
+
+ </fields>
+
+ <!-- field to use to determine and enforce document uniqueness. -->
+ <uniqueKey>id</uniqueKey>
+
+ <!-- field for the QueryParser to use when an explicit fieldname is absent -->
+ <defaultSearchField>text</defaultSearchField>
+
+ <!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->
+ <solrQueryParser defaultOperator="AND"/>
+</schema>
+
diff --git a/haystack-requirements.txt b/haystack-requirements.txt
new file mode 100644
index 0000000..8b85888
--- /dev/null
+++ b/haystack-requirements.txt
@@ -0,0 +1,4 @@
+python-geopy python-django-haystack python-pysolr
+# jetty solr-jetty
+# sudo rm /var/lib/jetty/webapps/solr
+# sudo ln -s /usr/share/solr/web/ /var/lib/jetty/webapps/solr
diff --git a/requirements.txt b/requirements.txt
index ca8db85..bd9e95d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
django>=1.4,<1.4.99
beautifulsoup
psycopg2
-pil
+Pillow
lxml
south>=0.7.3,<0.7.99
simplejson
diff --git a/requirements_searchengine.txt b/requirements_searchengine.txt
new file mode 100644
index 0000000..a87b1bb
--- /dev/null
+++ b/requirements_searchengine.txt
@@ -0,0 +1,12 @@
+django>=1.4,<1.4.99
+beautifulsoup
+psycopg2
+pil
+lxml
+south>=0.7.3,<0.7.99
+simplejson
+feedparser
+django-tinymce
+django-haystack==2.1
+geopy
+pysolr