summaryrefslogtreecommitdiff
path: root/chimere/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'chimere/views.py')
-rw-r--r--chimere/views.py1361
1 files changed, 1361 insertions, 0 deletions
diff --git a/chimere/views.py b/chimere/views.py
new file mode 100644
index 0000000..8eaa83a
--- /dev/null
+++ b/chimere/views.py
@@ -0,0 +1,1361 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008-2016 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+#
+# RSS : Copyright (C) 2010 Pierre Clarenc <pierre.crc_AT_gmailDOTcom>,
+# Samuel Renard <renard.samuel_AT_gmailDOTcom>,
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# See the file COPYING for details.
+
+"""
+Views of the project
+"""
+
+import datetime
+from itertools import groupby
+import re
+
+from django.conf import settings
+from django.contrib.gis.geos import GEOSGeometry, Polygon as GEOSPolygon
+from django.contrib.gis.gdal.error import OGRException
+from django.contrib.gis.measure import D
+from django.contrib.sites.models import get_current_site
+from django.core.exceptions import ObjectDoesNotExist
+from django.core.urlresolvers import reverse
+from django.db.models import Q
+from django.http import HttpResponseRedirect, HttpResponse, Http404
+from django.shortcuts import get_object_or_404, redirect, render_to_response
+from django.template import RequestContext, defaultfilters
+from django.utils import simplejson as json
+from django.utils.http import urlquote
+from django.utils.translation import ugettext as _
+from django.views.generic import TemplateView, ListView
+
+from chimere.actions import actions
+from chimere.models import Category, SubCategory, PropertyModel, Page,\
+ Marker, Route, Polygon, SimpleArea, Area, Color, TinyUrl, RouteFile,\
+ AggregatedRoute, AggregatedPolygon, PropertyModelChoice
+
+from chimere.widgets import PointChooserWidget, RouteChooserWidget, AreaWidget,\
+ PolygonChooserWidget
+from chimere.forms import MarkerForm, RouteForm, ContactForm, FileForm, \
+ FullFileForm, MultimediaFileFormSet, PictureFileFormSet, notifySubmission,\
+ notifyStaff, AreaForm, RoutingForm, PolygonForm
+
+from chimere.route import router
+
+
+def get_base_uri(request):
+ base_uri = 'http://'
+ if 'HTTP_REFERER' in request.META:
+ if request.META['HTTP_REFERER'].startswith('https:'):
+ base_uri = 'https://'
+ if 'SERVER_NAME' in request.META:
+ base_uri += request.META['SERVER_NAME']
+ if 'SERVER_PORT' in request.META and \
+ str(request.META['SERVER_PORT']) != '80':
+ base_uri += ":" + str(request.META['SERVER_PORT'])
+ return base_uri
+
+# TODO: convert to requestcontext
+
+
+def get_base_response(request, area_name=""):
+ """
+ Get the base url
+ """
+ base_response_dct = {'media_path': settings.MEDIA_URL}
+ base_response_dct['MOBILE'] = \
+ settings.MOBILE_TEST or \
+ get_current_site(request).domain in settings.MOBILE_DOMAINS
+ base_url = reverse("chimere:index")
+ if not base_url.startswith('/'):
+ base_url = '/' + base_url
+ if area_name and area_name.endswith('/'):
+ area_name = area_name[:-1]
+ if area_name:
+ base_response_dct['area_name_slash'] = area_name + "/"
+ if base_url[-1] != '/':
+ base_url += '/'
+ base_url += area_name + '/'
+ base_response_dct['extra_url'] = base_url
+ area = None
+ if area_name:
+ try:
+ area = Area.objects.get(urn=area_name, available=True)
+ except ObjectDoesNotExist:
+ return None, redirect(reverse('chimere:index'))
+ else:
+ try:
+ area = Area.objects.get(default=True)
+ area_name = area.urn
+ except ObjectDoesNotExist:
+ pass
+
+ base_response_dct['area'] = area
+ base_response_dct['area_name'] = area_name
+ if area and area.external_css:
+ base_response_dct['css_area'] = area.external_css
+ base_response_dct['dynamic_categories'] = \
+ True if area and area.dynamic_categories else False
+ base_response_dct['JQUERY_JS_URLS'] = settings.JQUERY_JS_URLS
+ base_response_dct['JQUERY_CSS_URLS'] = settings.JQUERY_CSS_URLS
+ base_response_dct['PROJECT_NAME'] = settings.PROJECT_NAME
+ if hasattr(settings, 'EXTRA_CSS'):
+ base_response_dct['EXTRA_CSS'] = settings.EXTRA_CSS
+ return base_response_dct, None
+
+
+def getShareUrl(request, area_name='', network=''):
+ """
+ Get a share url
+ """
+ data = getTinyfiedUrl(request, request.GET.urlencode(), area_name)
+ for name, url, img in settings.CHIMERE_SHARE_NETWORKS:
+ if defaultfilters.slugify(name) == network:
+ return HttpResponse(url % {'text': data['text'],
+ 'url': data['url']})
+ return HttpResponse('')
+
+
+def getShareNetwork(request, area_name='', marker=None):
+ """
+ Get URLs to share items
+ """
+ parameters = ""
+ if marker:
+ parameters = u'current_feature=%d' % marker.pk
+ parameters += u"&checked_categories=%s" % "_".join(
+ [str(m.id) for m in marker.categories.all()])
+ net_dct = getTinyfiedUrl(request, parameters, area_name)
+ share_networks = []
+ for network in settings.CHIMERE_SHARE_NETWORKS:
+ share_networks.append((network[0], network[1] % net_dct, network[2]))
+ return share_networks, net_dct
+
+
+def index(request, area_name=None, default_area=None, simple=False,
+ get_response=False):
+ """
+ Main page
+ """
+ # show the news
+ # only if user is not came yet today
+ today = datetime.date.today().strftime('%y-%m-%d')
+ news_visible = False
+ if 'last_visit' not in request.session or \
+ request.session['last_visit'] != today:
+ request.session['last_visit'] = today
+ news_visible = True
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir
+ # don't mess with permalink
+ zoomout = True
+ if request.GET and 'lat' in request.GET \
+ and 'lon' in request.GET:
+ zoomout = None
+ if hasattr(settings, 'CHIMERE_ENABLE_ROUTING') and \
+ settings.CHIMERE_ENABLE_ROUTING:
+ response_dct['itinerary_form'] = RoutingForm()
+ response_dct['routing_transport'] = settings.CHIMERE_ROUTING_TRANSPORT
+ if request.GET and 'current_feature' in request.GET:
+ try:
+ m = Marker.objects.get(pk=request.GET['current_feature'])
+ if m.route:
+ response_dct['current_route'] = m.route.pk
+ except:
+ pass
+ response_dct.update({
+ 'actions': actions(response_dct['area_name']),
+ 'action_selected': ('view',),
+ 'error_message': '',
+ 'is_map': True,
+ 'news_visible': news_visible,
+ 'areas_visible': settings.CHIMERE_DISPLAY_AREAS
+ and Area.objects.filter(available=True).count() > 1,
+ '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,
+ 'has_search': hasattr(settings, 'CHIMERE_SEARCH_ENGINE') and
+ settings.CHIMERE_SEARCH_ENGINE
+ })
+ if hasattr(settings, 'CONTACT_EMAIL') and settings.CONTACT_EMAIL:
+ response_dct['contact_email'] = settings.CONTACT_EMAIL
+ response_dct['share_networks'], net_dct = \
+ getShareNetwork(request, response_dct['area_name'])
+ tpl = 'chimere/main_map.html'
+ response_dct['simple'] = simple
+ if simple:
+ tpl = 'chimere/main_map_simple.html'
+ if get_response:
+ return tpl, response_dct
+ return render_to_response(tpl, response_dct,
+ context_instance=RequestContext(request))
+
+
+def edit(request, area_name="", item_id=None, submited=False):
+ """
+ Edition page
+ """
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir
+ current_actions = actions(response_dct['area_name'])
+ redir = action_do_redirect('edit-no-page', current_actions)
+ # a redir is always send... or there is a problem
+ return redirect(redir)
+
+
+def get_edit_page(redirect_url, item_cls, item_form,
+ multimediafile_formset=MultimediaFileFormSet,
+ picturefile_formset=PictureFileFormSet):
+ """
+ Edition page
+ """
+ def func(request, area_name="", item_id=None, cat_type=['M']):
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir, None, None
+ if 'area_name' in response_dct:
+ area_name = response_dct['area_name']
+ subcategories = SubCategory.getAvailable(cat_type, area_name,
+ public=True)
+ listed_subcats = []
+ if subcategories:
+ for cat, subcats in subcategories:
+ listed_subcats.append(
+ (unicode(cat),
+ [(subcat.pk, subcat.name) for subcat in subcats]))
+ # if an item_id is provided: modification
+ init_item, ref_item = None, None
+ if item_id:
+ try:
+ init_item = item_cls.objects.get(pk=item_id)
+ except:
+ return redirect(redirect_url, area_name + '/' if area_name
+ else ''), None, None
+ ref_item = init_item
+ modified_item = item_cls.objects.filter(
+ ref_item=init_item,
+ submiter_session_key=request.session.session_key)
+ if modified_item.count():
+ init_item = modified_item.all()[0]
+ response_dct['is_modification'] = True
+
+ init_multi = init_item.get_init_multi() if init_item else None
+ init_picture = init_item.get_init_picture() if init_item else None
+ if init_item and not request.user.is_superuser and \
+ not init_item.submiter_session_key == \
+ request.session.session_key:
+ # hide personal information
+ for k in ('submiter_name', 'submiter_email', 'submiter_comment'):
+ setattr(init_item, k, '')
+ response_dct['is_superuser'] = request.user.is_superuser
+ # If the form has been submited
+ if request.method == 'POST':
+ inst = None
+ # allow to directly modify only if owner or superuser
+ if init_item and (
+ request.user.is_superuser or
+ init_item.submiter_session_key ==
+ request.session.session_key):
+ inst = init_item
+ form = item_form(request.POST, request.FILES, instance=inst,
+ subcategories=listed_subcats,
+ area_name=area_name)
+ formset_multi = multimediafile_formset(
+ request.POST, request.FILES, initial=init_multi,
+ prefix='multimedia')
+ formset_picture = picturefile_formset(
+ request.POST, request.FILES, initial=init_picture,
+ prefix='picture')
+ # All validation rules pass
+ if form.is_valid() and formset_multi.is_valid() and \
+ formset_picture.is_valid():
+ item = form.save()
+ # set the session key (to permit modifications)
+ item.submiter_session_key = request.session.session_key
+
+ # associate to the reference item
+ if ref_item:
+ item.ref_item = ref_item
+ if item.pk != ref_item.pk:
+ item.status = 'M'
+ if hasattr(ref_item, 'has_associated_marker'):
+ item.has_associated_marker = \
+ ref_item.has_associated_marker
+ elif not item.ref_item:
+ # initialisation
+ item.ref_item = item
+
+ # just submited
+ if not item.status:
+ item.status = 'S'
+ item.save()
+
+ marker = item
+ if not isinstance(marker, Marker) \
+ and hasattr(item, 'associated_marker') \
+ and item.associated_marker.count():
+ marker = item.associated_marker.all()[0]
+ if marker:
+ # manage multimedia items
+ for f in formset_multi:
+ f.save(marker)
+
+ for f in formset_picture:
+ f.save(marker)
+ base_uri = get_base_uri(request)
+ notifySubmission(base_uri, item)
+ response_dct = get_base_response(request, area_name)
+ return redirect(
+ redirect_url + '-item',
+ area_name + '/' if area_name else '',
+ item.ref_item.pk, 'submited'), None, subcategories
+ else:
+ response_dct['error_message'] = _(
+ u"There are missing field(s)"
+ u" and/or errors in the submited form.")
+ else:
+ form = item_form(instance=init_item, subcategories=listed_subcats,
+ area_name=area_name)
+ formset_multi = multimediafile_formset(initial=init_multi,
+ prefix='multimedia')
+ formset_picture = picturefile_formset(initial=init_picture,
+ prefix='picture')
+ return None, (item_id, init_item, response_dct, form, formset_multi,
+ formset_picture), subcategories
+ return func
+
+
+def action_do_redirect(action_name, available_actions):
+ # redirect to an edit
+ is_edit = 'edit' in action_name
+ redir = None
+ for action, subactions in available_actions:
+ if action.id == action_name:
+ return
+ if not redir and action.id != action_name:
+ redir = action.url
+ for subaction in subactions:
+ if subaction.id == action_name:
+ return
+ if is_edit and 'edit' not in redir \
+ and 'edit' in subaction.id:
+ redir = subaction.url
+ return redir
+
+get_edit_marker = get_edit_page('chimere:editmarker', Marker, MarkerForm)
+
+
+def editMarker(request, area_name="", item_id=None, submited=False):
+ """
+ Edition page
+ """
+ response, values, sub_categories = get_edit_marker(request, area_name,
+ item_id, ['M', 'B'])
+ if response:
+ return response
+ item_id, init_item, response_dct, form, formset_multi, formset_picture = \
+ values
+
+ # verify action is available
+ current_actions = actions(response_dct['area_name'])
+ redir = action_do_redirect('edit-marker', current_actions)
+ if redir:
+ return redirect(redir)
+
+ # get the "manualy" declared_fields. Ie: properties
+ querys = PropertyModel.getAvailable(area_name=area_name)
+ declared_fields, filtered_properties = [], []
+ for query in querys:
+ declared_fields += query.all()
+ filtered_properties += query.filter(
+ subcategories__id__isnull=False).all()
+ point_value = init_item.point if init_item else None
+ if request.POST and request.POST.get('point'):
+ point_value = request.POST.get('point')
+ response_dct.update({
+ 'actions': current_actions,
+ 'action_selected': ('contribute', 'edit-marker'),
+ 'map_layer': settings.CHIMERE_DEFAULT_MAP_LAYER,
+ 'form': form,
+ 'formset_multi': formset_multi,
+ 'formset_picture': formset_picture,
+ 'dated': settings.CHIMERE_DAYS_BEFORE_EVENT,
+ 'extra_head': form.media,
+ 'marker_id': item_id,
+ 'sub_categories': sub_categories,
+ 'point_widget': PointChooserWidget().render(
+ 'point', point_value, area_name=response_dct['area_name']),
+ 'properties': declared_fields,
+ 'filtered_properties': filtered_properties,
+ 'submited': submited
+ })
+ # manualy populate the custom widget
+ if 'subcategory' in form.data and form.data['subcategory']:
+ response_dct['current_category'] = int(form.data['subcategory'])
+ return render_to_response('chimere/edit.html', response_dct,
+ context_instance=RequestContext(request))
+
+
+def uploadFile(request, category_id='', area_name=''):
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir
+ Form = FileForm if not category_id else FullFileForm
+ category = None
+ if category_id:
+ try:
+ category = SubCategory.objects.get(pk=category_id)
+ response_dct['category'] = unicode(category)
+ except:
+ pass
+ # If the form has been submited
+ if request.method == 'POST':
+ form = Form(request.POST, request.FILES)
+ # All validation rules pass
+ if form.is_valid():
+ raw_file = form.cleaned_data['raw_file']
+ name = raw_file.name.split('.')[0]
+ file_type = raw_file.name.split('.')[-1][0].upper()
+ routefile = RouteFile(raw_file=raw_file, name=name,
+ file_type=file_type)
+ routefile.save()
+ if not category_id:
+ response_dct['gpx_id'] = routefile.pk
+ return render_to_response(
+ 'chimere/upload_file.html', response_dct,
+ context_instance=RequestContext(request))
+ routefile.process()
+ if not routefile.route:
+ response_dct['errors'] = _(
+ u"Bad file. Please check it with an external software.")
+ response_dct.update({'form': form})
+ return render_to_response(
+ 'chimere/upload_file.html', response_dct,
+ context_instance=RequestContext(request))
+ route = Route(name=form.cleaned_data['name'],
+ route=routefile.route, associated_file=routefile,
+ status='S')
+ route.save()
+ route.categories.add(category)
+ route.save()
+ response_dct['thanks'] = True
+ form = Form()
+ else:
+ # An unbound form
+ form = Form()
+ response_dct.update({'form': form})
+ return render_to_response('chimere/upload_file.html', response_dct,
+ context_instance=RequestContext(request))
+
+
+def processRouteFile(request, area_name='', file_id=None):
+ if file_id:
+ try:
+ route_file = RouteFile.objects.get(pk=file_id)
+ route_file.process()
+ route = route_file.route
+ if not route:
+ return HttpResponse(status=500)
+ return HttpResponse('(' + json.dumps({'wkt': route,
+ 'file_id': file_id}) + ')',
+ 'application/javascript', status=200)
+ except OSError as e:
+ return HttpResponse(e.strerror, status=500)
+ else:
+ return HttpResponse(status=400)
+
+get_edit_route = get_edit_page('chimere:editroute', Route, RouteForm)
+
+
+def editRoute(request, area_name="", item_id=None, submited=False):
+ """
+ Route edition page
+ """
+ response, values, sub_categories = get_edit_route(request, area_name,
+ item_id, ['R', 'B'])
+ if response:
+ return response
+ item_id, init_item, response_dct, form, formset_multi, formset_picture = \
+ values
+
+ # verify action is available
+ current_actions = actions(response_dct['area_name'])
+ redir = action_do_redirect('edit-route', current_actions)
+ if redir:
+ return redirect(redir)
+
+ # get the "manualy" declared_fields. Ie: properties
+ declared_fields = form.declared_fields.keys()
+ if 'description' in declared_fields:
+ declared_fields.pop(declared_fields.index('description'))
+ route_value = init_item.route if init_item else None
+ if request.POST and request.POST.get('route'):
+ route_value = request.POST.get('route')
+ response_dct.update({
+ 'actions': current_actions,
+ 'action_selected': ('contribute', 'edit-route'),
+ 'error_message': '',
+ 'map_layer': settings.CHIMERE_DEFAULT_MAP_LAYER,
+ 'form': form,
+ 'formset_multi': formset_multi,
+ 'formset_picture': formset_picture,
+ 'dated': settings.CHIMERE_DAYS_BEFORE_EVENT,
+ 'extra_head': form.media,
+ 'sub_categories': sub_categories,
+ 'route_widget': RouteChooserWidget().render(
+ 'route', route_value, area_name=response_dct['area_name'],
+ routefile_id='',),
+ 'properties': declared_fields,
+ 'submited': submited
+ })
+ # manualy populate the custom widget
+ if 'subcategory' in form.data and form.data['subcategory']:
+ response_dct['current_category'] = int(form.data['subcategory'])
+ return render_to_response('chimere/edit_route.html', response_dct,
+ context_instance=RequestContext(request))
+
+get_edit_polygon = get_edit_page('chimere:editpolygon', Polygon, PolygonForm)
+
+
+def editPolygon(request, area_name="", item_id=None, submited=False):
+ """
+ Polygon edition page
+ """
+ response, values, sub_categories = get_edit_polygon(request, area_name,
+ item_id, ['P'])
+ if response:
+ return response
+ item_id, init_item, response_dct, form, formset_multi, formset_picture = \
+ values
+
+ # verify action is available
+ current_actions = actions(response_dct['area_name'])
+ redir = action_do_redirect('edit-polygon', current_actions)
+ if redir:
+ return redirect(redir)
+
+ # get the "manualy" declared_fields. Ie: properties
+ querys = PropertyModel.getAvailable(area_name=area_name)
+ declared_fields, filtered_properties = [], []
+ for query in querys:
+ declared_fields += query.all()
+ filtered_properties += query.filter(
+ subcategories__id__isnull=False).all()
+ polygon_value = init_item.polygon if init_item else None
+ if request.POST and request.POST.get('polygon'):
+ polygon_value = request.POST.get('polygon')
+ response_dct.update({
+ 'actions': current_actions,
+ 'action_selected': ('contribute', 'edit-polygon'),
+ 'error_message': '',
+ 'map_layer': settings.CHIMERE_DEFAULT_MAP_LAYER,
+ 'form': form,
+ 'formset_multi': formset_multi,
+ 'formset_picture': formset_picture,
+ 'dated': settings.CHIMERE_DAYS_BEFORE_EVENT,
+ 'extra_head': form.media,
+ 'sub_categories': sub_categories,
+ 'polygon_widget': PolygonChooserWidget().render(
+ 'polygon', polygon_value, area_name=response_dct['area_name'],),
+ 'properties': declared_fields,
+ 'filtered_properties': filtered_properties,
+ 'submited': submited
+ })
+ # manualy populate the custom widget
+ if 'subcategory' in form.data and form.data['subcategory']:
+ response_dct['current_category'] = int(form.data['subcategory'])
+ return render_to_response('chimere/edit_polygon.html', response_dct,
+ context_instance=RequestContext(request))
+
+
+def submited(request, area_name="", action=""):
+ """
+ Successful submission page
+ """
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir
+ dct = {'actions': actions(response_dct['area_name']),
+ 'action_selected': action}
+ if hasattr(settings, 'CONTACT_EMAIL') and settings.CONTACT_EMAIL:
+ response_dct['contact_email'] = settings.CONTACT_EMAIL
+ response_dct.update(dct)
+ return render_to_response('chimere/submited.html', response_dct,
+ context_instance=RequestContext(request))
+
+
+def charte(request, area_name=""):
+ """
+ Affichage de la charte
+ """
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir
+ response_dct.update({'actions': actions(response_dct['area_name']),
+ 'action_selected': ('charte',)})
+ return render_to_response('chimere/charte.html', response_dct,
+ context_instance=RequestContext(request))
+
+
+def contactus(request, area_name=""):
+ """
+ Contact page
+ """
+ form = None
+ msg = ''
+ # If the form has been submited
+ if request.method == 'POST':
+ form = ContactForm(request.POST)
+ # All validation rules pass
+ if form.is_valid():
+ response = notifyStaff(
+ _(u"Comments/request on the map"),
+ form.cleaned_data['content'], form.cleaned_data['email'])
+ if response:
+ msg = _(u"Thank you for your contribution. It will be taken "
+ u"into account. If you have left your email you may "
+ u"be contacted soon for more details.")
+ else:
+ msg = _(u"Temporary error. Renew your message later.")
+ else:
+ form = ContactForm()
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir
+ response_dct.update({'actions': actions(response_dct['area_name']),
+ 'action_selected': ('contact',),
+ 'contact_form': form, 'message': msg})
+ return render_to_response('chimere/contactus.html', response_dct,
+ context_instance=RequestContext(request))
+
+
+def extraPage(request, area_name="", page_id=""):
+ """
+ Extra dynamic pages
+ """
+ try:
+ page = Page.objects.get(available=True, mnemonic=page_id)
+ except ObjectDoesNotExist:
+ return redirect(reverse('chimere:index'))
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir
+ response_dct.update({'actions': actions(response_dct['area_name']),
+ 'action_selected': (page_id,),
+ 'content': page.content,
+ 'title': page.title})
+ tpl = page.template_path if page.template_path \
+ else 'chimere/default_extra_page.html'
+ return render_to_response(tpl, response_dct,
+ context_instance=RequestContext(request))
+
+
+def getDetailUndefined(request, area_name):
+ return HttpResponse('')
+
+
+def getDetail(request, area_name, key):
+ '''
+ Get the detail of a geographic item
+ '''
+ cls = Marker
+ pk = key
+ if '-' in key:
+ geo_type, pk = key.split('-')
+ if geo_type == 'route':
+ cls = Route
+ elif geo_type == 'aggroute':
+ cls = AggregatedRoute
+ elif geo_type == 'polygon':
+ cls = Polygon
+ try:
+ marker = cls.objects.filter(id=int(pk),
+ status__in=['A', 'S'])[0]
+ except (ValueError, IndexError):
+ return HttpResponse('no results')
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir
+ response_dct['marker'] = marker
+ if request.method == 'GET':
+ if 'simple' in request.GET and request.GET['simple']:
+ response_dct['simple'] = True
+ response_dct['share_networks'], net_dct = \
+ getShareNetwork(request, response_dct['area_name'], marker)
+ response_dct['share_url'] = net_dct['url']
+ net_dct['to'] = settings.CONTACT_EMAIL
+ if net_dct['to']:
+ net_dct["body"] = _(settings.CHIMERE_MODIF_EMAIL)
+ response_dct['modif_by_email'] = 'mailto:?to=%(to)s&subject='\
+ '%(text)s&body=%(body)s%(url)s' % net_dct
+ # to be sure there is unique IDs during a browsing
+ response_dct['time_now'] = datetime.datetime.now().strftime('%H%M%S')
+ response_dct['dated'] = settings.CHIMERE_DAYS_BEFORE_EVENT \
+ and marker.start_date
+ response_dct['routing_enabled'] = settings.CHIMERE_ENABLE_ROUTING
+ response_dct['properties'] = marker.getProperties(area_name=area_name)
+ return render_to_response('chimere/detail.html', response_dct,
+ context_instance=RequestContext(request))
+
+
+def getDescriptionDetail(request, area_name, category_id):
+ '''
+ Get the description for a category
+ '''
+ try:
+ category = Category.objects.filter(id=int(category_id))[0]
+ except (ValueError, IndexError):
+ return HttpResponse('no results')
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir
+ response_dct['category'] = category
+ return render_to_response('chimere/category_detail.html', response_dct,
+ context_instance=RequestContext(request))
+
+
+def checkDate(q):
+ """
+ Filter a queryset to manage dates
+ """
+ if not settings.CHIMERE_DAYS_BEFORE_EVENT:
+ return q
+ today = datetime.date.today()
+ after = today + datetime.timedelta(settings.CHIMERE_DAYS_BEFORE_EVENT)
+
+ q = q & (Q(start_date__isnull=True) | Q(start_date__gte=today,
+ start_date__lte=after) | Q(
+ start_date__lte=today, end_date__gte=today))
+ return q
+
+
+def _getGeoObjects(area_name, category_ids, status='A', getjson=True,
+ item_types=('Marker', 'Route', 'Polygon'),
+ bounding_box=None, zoom_level=None):
+ '''
+ Get geo objects
+ '''
+ zoom_need_reload = None
+ items = []
+ empty = [] if not getjson else {}
+ subcategories = list(
+ SubCategory.getAvailable(None, area_name, public=True,
+ instance=True).all())
+ aggregated_category_ids = []
+ try:
+ zoom_level = int(zoom_level)
+ except ValueError:
+ zoom_level = None
+ if zoom_level:
+ # pop from main category list
+ len_subcats = len(subcategories)
+ for idx, subcat in enumerate(reversed(subcategories)):
+ if not subcat.min_zoom:
+ continue
+ if zoom_level < subcat.min_zoom:
+ if not zoom_need_reload or zoom_need_reload > subcat.min_zoom:
+ zoom_need_reload = subcat.min_zoom
+ aggregated_category_ids.append(
+ subcategories.pop(len_subcats - 1 - idx).pk)
+
+ category_ids = [subcat.pk for subcat in subcategories]
+
+ # marker
+ if 'Marker' in item_types:
+ try:
+ q = checkDate(Q(status__in=status, categories__in=category_ids))
+ query = Marker.objects.filter(q)
+ if bounding_box:
+ query = query.filter(point__contained=bounding_box)
+ query = query.distinct('pk').order_by('-pk')
+ except:
+ return empty, zoom_need_reload
+
+ if getjson:
+ for geo_object in list(query):
+ items += json.loads(geo_object.getGeoJSON(category_ids))
+ else:
+ items += list(query)
+
+ # polygon
+ if 'Polygon' in item_types:
+ try:
+ q = checkDate(Q(status__in=status, categories__in=category_ids))
+ query = Polygon.objects.filter(q)
+ if bounding_box:
+ query = query.filter(polygon__contained=bounding_box)
+ query = query.distinct('pk').order_by('-pk')
+ except:
+ return empty, zoom_need_reload
+
+ if aggregated_category_ids:
+ query = AggregatedPolygon.objects.filter(
+ status__in=status,
+ subcategory__in=aggregated_category_ids).order_by(
+ 'subcategory', '-pk')
+ # no bounding box filter
+ if getjson:
+ for poly in query.all():
+ items.append(json.loads(poly.getGeoJSON()))
+ else:
+ items += list(query)
+ else:
+ if getjson:
+ current_cat, colors, idx = None, None, 0
+ items += Polygon.getGeoJSONs(
+ query, limit_to_categories=category_ids)
+ else:
+ items += list(query)
+
+ # routes
+ if 'Route' in item_types:
+ # TODO: manage non aggregated and bounding box in non-aggregated
+ query = AggregatedRoute.objects.filter(
+ status__in=status, subcategory__in=category_ids).order_by(
+ 'subcategory', '-pk')
+ if getjson:
+ current_cat, colors, idx = None, None, 0
+ for route in query.all():
+ color = ""
+ # aggregated view has no color and no categories
+ if hasattr(route, 'color') and route.color:
+ color = route.color
+ elif hasattr(route, 'categories'):
+ c_cat = None
+ for cat in route.categories.all():
+ if cat.id in category_ids:
+ c_cat = cat
+ break
+ if c_cat and not current_cat or current_cat != c_cat:
+ idx = 0
+ current_cat = c_cat
+ colors = list(Color.objects.filter(
+ color_theme=c_cat.color_theme))
+ if colors:
+ color = colors[idx % len(colors)].code
+ idx += 1
+ else:
+ color = "#000"
+ items.append(json.loads(route.getGeoJSON(color=color)))
+ else:
+ items += list(query)
+
+ if not items:
+ return empty, zoom_need_reload
+ return items, zoom_need_reload
+
+
+def getGeoObjects(request, area_name, category_ids, status):
+ '''
+ Get the JSON for markers and routes
+ '''
+ if not status:
+ status = 'A'
+ status = status.split('_')
+ category_ids = unicode(category_ids).split('_')
+
+ bounding_box = []
+ for attr in ['min_lon', 'min_lat', 'max_lon', 'max_lat']:
+ value = request.GET.get(attr, None)
+ if not value:
+ bounding_box = None
+ break
+ bounding_box.append(value)
+ if bounding_box:
+ bounding_box = GEOSPolygon.from_bbox(bounding_box)
+ zoom_level = request.GET.get('zoom_level', None)
+
+ jsons, zoom_need_reload = _getGeoObjects(
+ area_name, category_ids, status, bounding_box=bounding_box,
+ zoom_level=zoom_level)
+ if not jsons:
+ return HttpResponse("[]", content_type="application/json")
+ data = json.dumps({"type": "FeatureCollection", "features": jsons,
+ "zoom_need_reload": zoom_need_reload or ''})
+ 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):
+ '''
+ Get all available categories in JSON
+ '''
+ context_data, redir = get_base_response(request, area_name)
+ area = context_data["area"]
+ subcategories = []
+ if area:
+ subcategories = list(area.getCategories(
+ 'A', area_name=context_data['area_name']))
+ else:
+ categories = SubCategory.getAvailable()
+ for cat, subcats in categories:
+ subcategories += subcats
+ subcats = [subcat.getJSONDict() for subcat in subcategories]
+ jsons = json.dumps({'categories': subcats})
+ return HttpResponse(jsons)
+
+
+def get_available_categories(request, area_name=None, area=None, status='A',
+ force=None):
+ '''
+ Get category menu for a designed area
+ '''
+ context_data, redir = get_base_response(request, area_name)
+ area = context_data["area"]
+ if redir:
+ return redir
+ if area and area.dynamic_categories and \
+ "current_extent" not in request.GET:
+ context_data['sub_categories'] = []
+ return render_to_response(
+ 'chimere/blocks/categories.html', context_data,
+ context_instance=RequestContext(request))
+ if not area or not area.dynamic_categories:
+ # Categories are not updated dynamicaly when the user move the map
+ # so we return ALL the categories
+ subcategories = SubCategory.getAvailable(
+ area_name=context_data['area_name'])
+ context_data['sub_categories'] = subcategories
+ return render_to_response(
+ 'chimere/blocks/categories.html', context_data,
+ context_instance=RequestContext(request))
+ default_message = "<p class='warning'>%s</p>" % \
+ _("No category available in this area.")
+ if "status" not in request.GET: # there must be a status
+ status = 'A'
+ try:
+ status = status.split('_')
+ current_extent = request.GET["current_extent"].replace('M', '-')\
+ .replace('D', '.')
+ area = SimpleArea([float(pt) for pt in current_extent.split('_')])
+ except:
+ # bad extent format
+ return HttpResponse(default_message)
+ # if not force and area.isIn(SimpleArea(cookie.AREA):return
+ categories = area.getCategories(
+ status, area_name=context_data['area_name'])
+ if not categories:
+ return HttpResponse(default_message)
+ get_cat = lambda subcat: subcat.category
+ get_cat_order = lambda subcat: (subcat.category.order, subcat.category.pk,
+ subcat.order)
+ categories = sorted(categories, key=get_cat_order)
+ subcategories = [(cat, list(subcats))
+ for cat, subcats in groupby(categories, get_cat)]
+ context_data['sub_categories'] = subcategories
+ return render_to_response('chimere/blocks/categories.html', context_data,
+ context_instance=RequestContext(request))
+
+
+def getCategory(request, area_name='', category_id=0):
+ '''
+ Get the JSON for a category (mainly in order to get the description)
+ '''
+ try:
+ category = SubCategory.objects.get(pk=category_id)
+ except ObjectDoesNotExist:
+ return HttpResponse('[]', content_type="application/json")
+ return HttpResponse(category.getJSON(), content_type="application/json")
+
+
+def getTinyfiedUrl(request, parameters, area_name=''):
+ '''
+ Get the tinyfied version of parameters
+ '''
+ data = {"urn": "", "url": "", "text": ""}
+ try:
+ urn = TinyUrl.getUrnByParameters(parameters)
+ except:
+ return {}
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir
+ url = reverse('chimere:tiny', args=[(response_dct['area_name']
+ if response_dct['area_name'] else '') + '/', urn])
+ if not url.startswith('http'):
+ url = get_base_uri(request) + url
+ url = re.sub("([^:])\/\/", "\g<1>/", url)
+ text = settings.PROJECT_NAME
+ if 'current_feature' in parameters:
+ for item in parameters.split('&'):
+ if 'current_feature' in item:
+ try:
+ text = unicode(Marker.objects.get(id=item.split('=')[1]))
+ except (IndexError, Marker.DoesNotExist):
+ pass
+ data["urn"] = urlquote(urn)
+ data["url"] = urlquote(url)
+ data["text"] = urlquote(text)
+ return data
+
+
+def redirectFromTinyURN(request, area_name='', tiny_urn=''):
+ """
+ Redirect from a tiny Urn
+ """
+ parameters = '?' + TinyUrl.getParametersByUrn(tiny_urn)
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir
+ return HttpResponseRedirect(response_dct['extra_url'] + parameters)
+
+
+class CategoryDirectoryView(ListView):
+ template_name = "chimere/category_directory.html"
+
+ def get_queryset(self):
+ self.area_name = self.kwargs.get('area_name', None)
+ if self.area_name:
+ self.area_name = self.area_name.split('/')[0]
+ area = get_object_or_404(Area, urn=self.area_name, available=True)
+ q = area.subcategories.filter(
+ available=True, category__available=True).order_by(
+ 'category__order', 'category__id', 'order')
+ if q.count():
+ return q
+ return SubCategory.objects.filter(
+ available=True, category__available=True).order_by(
+ 'category__order', 'category__id', 'order')
+
+ def get_context_data(self, *args, **kwargs):
+ context = super(CategoryDirectoryView, self).get_context_data(
+ *args, **kwargs)
+ new_context, redirect = get_base_response(self.request, self.area_name)
+ context.update(new_context)
+ context.update({
+ 'actions': actions(self.area_name),
+ 'action_selected': ('categories',),
+ })
+ return context
+
+
+class CategoryView(TemplateView):
+ template_name = "chimere/category_directory_detail.html"
+
+ def get_geo_items(self):
+ # TODO: simplify on v2.3 when slug are available
+ category_slug = self.kwargs.get('category_slug')
+ self.area_name = self.kwargs.get('area_name', None)
+ q = None
+ if self.area_name:
+ self.area_name = self.area_name.split('/')[0]
+ area = get_object_or_404(Area, urn=self.area_name, available=True)
+ q = area.subcategories.filter(available=True,
+ category__available=True)
+ if not q.count():
+ q = None
+ if not q:
+ q = SubCategory.objects.filter(available=True,
+ category__available=True)
+ self.category = None
+ for subcat in q:
+ if defaultfilters.slugify(subcat.name) == category_slug:
+ self.category = subcat
+ break
+ if not self.category:
+ raise Http404(_("Category does not exist"))
+
+ items, zoom_need_reload = _getGeoObjects(
+ self.area_name, [unicode(self.category.pk)], getjson=False,
+ item_types=('Marker',))
+ return items
+
+ def get_context_data(self, *args, **kwargs):
+ context = super(CategoryView, self).get_context_data(
+ *args, **kwargs)
+ self.items = self.get_geo_items()
+ new_context, redirect = get_base_response(self.request, self.area_name)
+ context.update(new_context)
+ context.update({
+ 'actions': actions(self.area_name),
+ 'action_selected': ('categories',),
+ 'category': self.category,
+ 'items': self.items
+ })
+ return context
+
+
+def route(request, area_name, lon1, lat1, lonlat_steps, lon2, lat2,
+ transport='foot', speed=''):
+ '''
+ Get the JSON for a route
+ '''
+ try:
+ lon1, lat1 = float(lon1), float(lat1)
+ lon2, lat2 = float(lon2), float(lat2)
+ steps = [float(lonlat) for lonlat in lonlat_steps.split('_') if lonlat]
+ # regroup by 2
+ steps = [(steps[i * 2], steps[i * 2 + 1])
+ for i in range(len(steps) / 2)]
+ except ValueError:
+ return HttpResponse('no results')
+
+ # prevent incoherent transport and speed
+ if transport not in dict(settings.CHIMERE_ROUTING_TRANSPORT):
+ transport = settings.CHIMERE_ROUTING_TRANSPORT[0][0]
+ if speed:
+ speed = unicode(speed)
+ available_speed = [
+ unicode(sp)
+ for sp, lbl in settings.CHIMERE_ROUTING_SPEEDS[transport]]
+ if speed not in available_speed:
+ speed = None
+ if not speed:
+ speed = settings.CHIMERE_ROUTING_SPEEDS[transport][0][0]
+
+ jsons, desc, total = router.route(lon1, lat1, lon2, lat2, steps=steps,
+ transport=transport, speed=speed)
+ if not jsons:
+ return HttpResponse('no results')
+ jsonencoder = json.JSONEncoder()
+ total = jsonencoder.encode(total)
+ desc = jsonencoder.encode(desc)
+
+ # get associated POIs
+ try:
+ route = GEOSGeometry(jsons[0])
+ except OGRException:
+ return HttpResponse(_(u"Bad geometry"), status=500)
+ cats = SubCategory.objects.filter(routing_warn=True)
+ message = ''
+ if cats.count():
+ st_string = '{"type":"Feature", "geometry":{ "type": "Point", '\
+ '"coordinates": [ %f, %f ] }, "properties":{"icon_path":"%s",'\
+ '"icon_width":%d, "icon_height":%d}}'
+ points = [(m.point, m.categories.all()[0].icon)
+ for m in list(Marker.objects.filter(
+ status='A', categories__in=cats,
+ point__distance_lte=(route, D(m=15))).all())]
+ for pt, icon in points:
+ st = st_string % (pt.x, pt.y, icon.image.url, icon.image.width,
+ icon.image.height)
+ jsons.append(st)
+ routes = Route.objects.filter(status='A', categories__in=cats,
+ route__crosses=route)
+ intersect = False
+ for rout in routes.intersection(route):
+ pts = rout.intersection
+ icon = rout.categories.all()[0].icon
+ if hasattr(pts, 'x'):
+ pts = [pts]
+ if pts:
+ pt = pts[0]
+ st = st_string % (pt.x, pt.y, icon.image.url, icon.image.width,
+ icon.image.height)
+ jsons.append(st)
+ if points or intersect:
+ message = getattr(settings, 'CHIMERE_ROUTING_WARN_MESSAGE', '')
+ if message:
+ message = ', "message":%s' % jsonencoder.encode(
+ "%s" % _(message))
+ else:
+ message = ''
+ data = '{"properties":{"transport":%s, "total":%s, "description":%s}, '\
+ '"type": "FeatureCollection", "features":[%s]%s}' % (
+ jsonencoder.encode(transport), total, desc, ",".join(jsons),
+ message)
+ return HttpResponse(data)
+
+
+def rss(request, area_name=''):
+ '''
+ Redirect to RSS subscription page
+ '''
+ response_dct, redir = get_base_response(request, area_name)
+ if redir:
+ return redir
+ response_dct.update({'actions': actions(response_dct['area_name']),
+ 'action_selected': ('rss',),
+ 'category_rss_feed': ''})
+ # If the form has been submited
+ if request.method == "POST":
+ # User has defined the kind of POI he is interested in : POI in a area
+ # (GET method is used for the link with RSS icon in the browser)
+ if 'rss_category' in request.POST:
+ # User wants to follow all the new POI
+ if request.POST['rss_category'] == 'global':
+ feeds_link = reverse('chimere:feeds-global')
+ return redirect(feeds_link)
+ # User wants to follow all the new POI by category or subcategory
+ elif request.POST['rss_category'] == 'poi':
+ response_dct['category_rss_feed'] = 'category'
+ response_dct['sub_categories'] = SubCategory.getAvailable()
+ return render_to_response(
+ 'chimere/feeds/rss.html', response_dct,
+ context_instance=RequestContext(request))
+ # User wants to follow all the new POI situated in a defined area
+ elif request.POST['rss_category'] == 'area':
+ # An unbound form
+ form = AreaForm()
+ area_widget = AreaWidget().render('area', None)
+ response_dct.update({
+ 'map_layer': settings.CHIMERE_DEFAULT_MAP_LAYER,
+ 'extra_head': form.media,
+ 'form': form,
+ 'category_rss_feed': 'area',
+ 'area_id': Area.getAvailable(),
+ 'area_widget': area_widget
+ })
+ return render_to_response(
+ 'chimere/feeds/rss.html', response_dct,
+ context_instance=RequestContext(request))
+ # Error when submitting the form
+ else:
+ error = _("Incorrect choice in the list")
+ response_dct.update({
+ 'error_message': error,
+ 'category_rss_feed': '',
+ 'sub_categories': SubCategory.getAvailable()})
+ return render_to_response(
+ 'chimere/feeds/rss.html', response_dct,
+ context_instance=RequestContext(request))
+
+ # User has specified the category or subcategory he wants to follow =>
+ # we redirect him towards the related rss feed
+ if 'subcategory' in request.POST and request.POST['subcategory'] != '':
+ cat_id = request.POST['subcategory']
+ if cat_id.find("cat_") != -1:
+ cat_id = cat_id.split('_')[1]
+ feeds_link = reverse('chimere:feeds-cat',
+ kwargs={'category_id': cat_id})
+ return redirect(feeds_link)
+
+ else:
+ feeds_link = reverse('chimere:feeds-subcat',
+ kwargs={'category_id': cat_id})
+ return redirect(feeds_link)
+
+ # User has specified the ID of the area he wants to follow
+ if 'id_area' in request.POST and request.POST['id_area'] != '':
+ feeds_link = reverse('chimere:feeds-areaid',
+ kwargs={'area_id': request.POST['id_area']})
+ return redirect(feeds_link)
+
+ # User has specified the area he wants to follow => we redirect him
+ # towards the related rss feed (using upper left and lower right
+ # coordinates)
+ elif 'upper_left_lat' in request.POST and \
+ request.POST['upper_left_lat'] != '' and \
+ 'upper_left_lon' in request.POST and \
+ request.POST['upper_left_lon'] != '' and \
+ 'lower_right_lon' in request.POST and \
+ request.POST['lower_right_lon'] != '' and \
+ 'lower_right_lat' in request.POST and \
+ request.POST['lower_right_lat'] != '':
+ coords = request.POST['upper_left_lat'] + '_' + \
+ request.POST['upper_left_lon'] + '_' + \
+ request.POST['lower_right_lat'] + '_' + \
+ request.POST['lower_right_lon']
+ feeds_link = reverse('chimere:feeds-area',
+ kwargs={'area': coords})
+ return redirect(feeds_link)
+
+ # GET method is used for linking with the RSS icon in the browser when user
+ # wants to choose a category to follow
+ elif request.method == "GET" and 'rss_category' in request.GET:
+ if request.GET['rss_category'] == 'global':
+ feeds_link = reverse('chimere:feeds-global')
+ return redirect(feeds_link)
+ if request.GET['rss_category'] == 'poi':
+ response_dct['category_rss_feed'] = 'category'
+ response_dct['sub_categories'] = SubCategory.getAvailable(
+ ['M', 'B'])
+ return render_to_response('chimere/feeds/rss.html', response_dct,
+ context_instance=RequestContext(request))
+ if request.GET['rss_category'] == 'area':
+ # An unbound form
+ form = AreaForm()
+ response_dct.update({
+ 'map_layer': settings.MAP_LAYER,
+ 'extra_head': form.media,
+ 'form': form,
+ 'category_rss_feed': 'area',
+ 'area_id': Area.getAvailable(),
+ 'area_widget': AreaWidget().render('area', None)})
+ return render_to_response('chimere/feeds/rss.html', response_dct,
+ context_instance=RequestContext(request))
+
+ # User access to the RSS tab
+ else:
+ return render_to_response('chimere/feeds/rss.html', response_dct,
+ context_instance=RequestContext(request))
+
+# from django.core.paginator import Paginator, InvalidPage
+
+
+def property_choice_list(request, area_name='', property_slug=''):
+ data = {}
+ try:
+ pm = PropertyModel.objects.get(slug=property_slug, available=True)
+ except PropertyModel.DoesNotExist:
+ return HttpResponse(json.dumps(data),
+ content_type="application/json")
+ if not request.GET or not request.GET.get('term') or \
+ pm.areas.count() and not pm.areas.filter(urn=area_name).count():
+ return HttpResponse(json.dumps(data),
+ content_type="application/json")
+ fltr = {'propertymodel': pm, 'available': True,
+ 'value__icontains': request.GET.get('term')}
+ q = PropertyModelChoice.objects.filter(**fltr).order_by('value')
+ data = [{"id": p.pk, "value": p.value} for p in q.all()]
+ return HttpResponse(json.dumps(data),
+ content_type="application/json")
+
+
+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):
+ def extra_context(self, *args, **kwargs):
+ context = super(SearchView, self).extra_context(*args, **kwargs)
+ context["autocomplete"] = \
+ settings.HAYSTACK_AUTOCOMPLETE \
+ if hasattr(settings, 'HAYSTACK_AUTOCOMPLETE') else False
+ return context
+
+ 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')