diff options
author | Étienne Loks <etienne.loks@proxience.com> | 2014-09-22 01:23:23 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@proxience.com> | 2014-09-22 01:23:23 +0200 |
commit | 19d813b1bc40edc3be34fdcabe2dcd676f7a5d94 (patch) | |
tree | 2d6fdf71ec122671925300b41a6cfb140d4e9b4d | |
parent | 25c72bcd877868a0a00697c15fe68ca9e0002d66 (diff) | |
download | Chimère-19d813b1bc40edc3be34fdcabe2dcd676f7a5d94.tar.bz2 Chimère-19d813b1bc40edc3be34fdcabe2dcd676f7a5d94.zip |
Work on search engine
* templates
* styles
* search index definition
-rw-r--r-- | chimere/forms.py | 11 | ||||
-rw-r--r-- | chimere/search_indexes.py | 50 | ||||
-rw-r--r-- | chimere/static/chimere/css/styles.css | 39 | ||||
-rw-r--r-- | chimere/static/chimere/js/base.js | 2 | ||||
-rw-r--r-- | chimere/static/chimere/js/jquery.chimere.js | 37 | ||||
-rw-r--r-- | chimere/static/chimere/js/search.js | 30 | ||||
-rw-r--r-- | chimere/templates/chimere/base.html | 1 | ||||
-rw-r--r-- | chimere/templates/chimere/blocks/footer.html | 2 | ||||
-rw-r--r-- | chimere/templates/chimere/main_map.html | 17 | ||||
-rw-r--r-- | chimere/templates/search/indexes/chimere/marker_text.txt | 3 | ||||
-rw-r--r-- | chimere/templates/search/indexes/chimere/route_text.txt | 1 | ||||
-rw-r--r-- | chimere/templates/search/search.html | 47 | ||||
-rw-r--r-- | chimere/templatetags/unescape.py | 15 | ||||
-rw-r--r-- | chimere/urls.py | 14 | ||||
-rw-r--r-- | chimere/views.py | 15 | ||||
-rw-r--r-- | requirements.txt | 2 | ||||
-rw-r--r-- | requirements_searchengine.txt | 12 |
17 files changed, 284 insertions, 14 deletions
diff --git a/chimere/forms.py b/chimere/forms.py index 1ba90a0..618b529 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, \ @@ -631,3 +635,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/search_indexes.py b/chimere/search_indexes.py new file mode 100644 index 0000000..7f45b06 --- /dev/null +++ b/chimere/search_indexes.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 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 +# 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 chimere import models + +class GeographicItemIndex(indexes.SearchIndex): + text = indexes.CharField(document=True, use_template=True) + categories = indexes.MultiValueField() + + def index_queryset(self, using=None): + return self.get_model().objects.filter(status='A') + + 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/static/chimere/css/styles.css b/chimere/static/chimere/css/styles.css index ab68d68..c0b3b0e 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{ @@ -82,7 +83,7 @@ div.warning, .action li.selected, #content .olControlLayerSwitcher .layersDiv, #panel, #map-footer, #chimere_itinerary_panel, -#utils-div{ +#search-box, #utils-div{ border:1px solid #327e04; } @@ -772,6 +773,42 @@ table.inline-table td input[type=file]{ margin-right: auto; } +#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; } diff --git a/chimere/static/chimere/js/base.js b/chimere/static/chimere/js/base.js index fe8d954..0db11c1 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 diff --git a/chimere/static/chimere/js/jquery.chimere.js b/chimere/static/chimere/js/jquery.chimere.js index a9b237f..d04e1ae 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-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 @@ -623,6 +623,10 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, { } }); }, + razMap: function() { + settings.layerMarkers.clearMarkers(); + settings.layerVectors.removeAllFeatures(); + }, /* * Update the categories div in ajax */ @@ -876,7 +880,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(); @@ -1323,7 +1327,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>"); @@ -1442,6 +1446,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; @@ -1545,6 +1553,23 @@ 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 + } + } + //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) { @@ -1554,7 +1579,7 @@ OpenLayers.Layer.MapQuestOSM = OpenLayers.Class(OpenLayers.Layer.XYZ, { if (settings.current_popup) { if (!settings.simple){ - $('#detail').hide(); + $('#detail').fadeOut(); } if (settings.current_popup.visible()){ settings.current_popup.hide(); @@ -1659,6 +1684,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.js b/chimere/static/chimere/js/search.js new file mode 100644 index 0000000..779a32d --- /dev/null +++ b/chimere/static/chimere/js/search.js @@ -0,0 +1,30 @@ +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); + + var c_url = search_url + "?q=" + $('#id_q').val(); + if (page){ + c_url += '&page=' + page; + } + $.get(c_url).done(function( data ) { + $('#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/footer.html b/chimere/templates/chimere/blocks/footer.html index a783939..8958939 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-2014 <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/main_map.html b/chimere/templates/chimere/main_map.html index b7aa868..5f01fdb 100644 --- a/chimere/templates/chimere/main_map.html +++ b/chimere/templates/chimere/main_map.html @@ -5,6 +5,8 @@ {% 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>{% endif %} {{ block.super }} {% endblock %} {% block message_edit %}{% endblock %} @@ -36,6 +38,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 +52,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/search/indexes/chimere/marker_text.txt b/chimere/templates/search/indexes/chimere/marker_text.txt new file mode 100644 index 0000000..ad5bae1 --- /dev/null +++ b/chimere/templates/search/indexes/chimere/marker_text.txt @@ -0,0 +1,3 @@ +{% load unescape %} +{{object.name}} +{{object.description|safe|striptags|unescape}} 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..2fad18d --- /dev/null +++ b/chimere/templates/search/indexes/chimere/route_text.txt @@ -0,0 +1 @@ +{{object.name}} diff --git a/chimere/templates/search/search.html b/chimere/templates/search/search.html new file mode 100644 index 0000000..28b2b3a --- /dev/null +++ b/chimere/templates/search/search.html @@ -0,0 +1,47 @@ +{% load url from future %}{% load i18n%} +{% if query %} +<script type='text/javascript'> +var new_ids = [{% for result in page.object_list %}{{result.object.pk}}{% if not forloop.last %}, {% endif %}{% endfor %}]; +var geo_objects = [{% for result in page.object_list %}{{result.object.getGeoJSON|safe}}{% if not forloop.last %}, {% endif %}{% endfor %}]; +var geo_features = {}; +for (idx=0 ; idx < new_ids.length ; idx++){ + if (search_result.indexOf(new_ids[idx]) == -1){ + search_result.push(new_ids[idx]); + geo_features[new_ids[idx]] = $('#main-map').chimere('addMarker', geo_objects[idx]); + } +} +{% if page.object_list.count %}$("#main-map").chimere("zoomToMarkerExtent");{% endif %} +</script> +<div id='search-listing'> + <ul> +{% for result in page.object_list %} + <li> + <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'> + {% if page.has_previous %}<a href="#" onclick="haystack_search(this, {{ page.previous_page_number }});">{% trans "Previous" %}</a>{% endif %} + {% if page.has_next %}<a href="#" onclick="haystack_search(this, {{ page.next_page_number }});">{% trans "More results..." %}</a>{% endif %} + </div> +{% endif %} + +{% else %} +<form id='search-form'> + {{ form.q }} <button name='haystack-search' id='haystack-search' type='button'>{% trans "Search" %}</button> +</form> +<div id='search-result'></div> +<script type='text/javascript'> +$(function(){ + $('#haystack-search').click( + function(evt){ + $("#main-map").chimere("razMap"); + haystack_search(evt); + }); +}); +</script> +{% endif %} 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 459ed28..2ed88aa 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 @@ -70,7 +70,17 @@ if hasattr(settings, 'CHIMERE_ENABLE_ROUTING') \ if hasattr(settings, 'CHIMERE_SEARCH_ENGINE') \ and settings.CHIMERE_SEARCH_ENGINE: - urlpatterns += [url(r'^search/', include('haystack.urls')),] + 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'), + ) + #urlpatterns += [url(r'^search/', include('haystack.urls')),] urlpatterns += patterns('chimere.views', url(r'^charte/?$', 'charte', name="charte"), diff --git a/chimere/views.py b/chimere/views.py index 9b25458..5999af7 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>, @@ -175,13 +175,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 @@ -932,3 +934,12 @@ 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 +if hasattr(settings, 'CHIMERE_SEARCH_ENGINE') \ + and settings.CHIMERE_SEARCH_ENGINE: + from haystack.views import SearchView as HaystackSearchView + class SearchView(HaystackSearchView): + pass 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 |