summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
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
commit19d813b1bc40edc3be34fdcabe2dcd676f7a5d94 (patch)
tree2d6fdf71ec122671925300b41a6cfb140d4e9b4d
parent25c72bcd877868a0a00697c15fe68ca9e0002d66 (diff)
downloadChimère-19d813b1bc40edc3be34fdcabe2dcd676f7a5d94.tar.bz2
Chimère-19d813b1bc40edc3be34fdcabe2dcd676f7a5d94.zip
Work on search engine
* templates * styles * search index definition
-rw-r--r--chimere/forms.py11
-rw-r--r--chimere/search_indexes.py50
-rw-r--r--chimere/static/chimere/css/styles.css39
-rw-r--r--chimere/static/chimere/js/base.js2
-rw-r--r--chimere/static/chimere/js/jquery.chimere.js37
-rw-r--r--chimere/static/chimere/js/search.js30
-rw-r--r--chimere/templates/chimere/base.html1
-rw-r--r--chimere/templates/chimere/blocks/footer.html2
-rw-r--r--chimere/templates/chimere/main_map.html17
-rw-r--r--chimere/templates/search/indexes/chimere/marker_text.txt3
-rw-r--r--chimere/templates/search/indexes/chimere/route_text.txt1
-rw-r--r--chimere/templates/search/search.html47
-rw-r--r--chimere/templatetags/unescape.py15
-rw-r--r--chimere/urls.py14
-rw-r--r--chimere/views.py15
-rw-r--r--requirements.txt2
-rw-r--r--requirements_searchengine.txt12
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