diff options
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 : « Tracer »." -#: 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 " "« Arrêter le tracé »." -#: 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 « d » ou « Suppr »." -#: 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 }});">← {% 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..." %} →</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 |