Skip to content
Extraits de code Groupes Projets
Valider 7b5ea8df rédigé par Yohan Boniface's avatar Yohan Boniface
Parcourir les fichiers

First shot in Django 1.8 / python 3 support

This also replaced pg_index with a simple builting seach
based on tsvector, as pg_index is not compliant with Django 1.8,
and as some old dependencies that would need to be upgrade too.
parent 451e952a
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
...@@ -21,7 +21,7 @@ Create a virtual environment:: ...@@ -21,7 +21,7 @@ Create a virtual environment::
Install dependencies and project:: Install dependencies and project::
cd YOUR_SOURCE_DIR cd YOUR_SOURCE_DIR
git clone git clone https://bitbucket.org/yohanboniface/umap.git git clone https://bitbucket.org/yohanboniface/umap.git
pip install -r requirements.txt pip install -r requirements.txt
pip install -e . pip install -e .
...@@ -58,7 +58,7 @@ needs. For example:: ...@@ -58,7 +58,7 @@ needs. For example::
TWITTER_CONSUMER_KEY = "xxx" TWITTER_CONSUMER_KEY = "xxx"
TWITTER_CONSUMER_SECRET = "yyy" TWITTER_CONSUMER_SECRET = "yyy"
Example of callback URL to use for settings up OAuth apps:: Example of callback URL to use for setting up OAuth apps::
http://umap.foo.bar/complete/github/ http://umap.foo.bar/complete/github/
...@@ -66,7 +66,7 @@ Adapt the `STATIC_ROOT` and `MEDIA_ROOT` to your local environment. ...@@ -66,7 +66,7 @@ Adapt the `STATIC_ROOT` and `MEDIA_ROOT` to your local environment.
Create the tables:: Create the tables::
python manage.py syncdb --migrate python manage.py migrate
Collect and compress the statics:: Collect and compress the statics::
...@@ -87,6 +87,18 @@ Go to the admin (http://localhost:8000/admin/) and add: ...@@ -87,6 +87,18 @@ Go to the admin (http://localhost:8000/admin/) and add:
- at least one license - at least one license
- at least one tile layer - at least one tile layer
Search
------
UMap uses Postgresql tsvector for searching. It case your database is big, you
may want to add an index. For that, you sould do so:
CREATE EXTENSION unaccent;
CREATE EXTENSION btree_gin;
ALTER FUNCTION unaccent(text) IMMUTABLE;
ALTER FUNCTION to_tsvector(text) IMMUTABLE;
CREATE INDEX search_idx ON leaflet_storage_map USING gin(to_tsvector(unaccent(name)), share_status);
Translating Translating
----------- -----------
......
from django.conf import settings
from pgindex import IndexBase, Vector, register
from leaflet_storage.models import Map
class UnaccentVector(Vector):
@property
def tsvector(self):
if getattr(settings, "UMAP_USE_UNACCENT", False):
return u"setweight(to_tsvector('%s', unaccent(E'%s')), '%s')" % (
self.dictionary, self.value, self.weight
)
else:
return super(UnaccentVector, self).tsvector
class MapIndex(IndexBase):
def get_title(self):
return self.obj.name
def get_start_publish(self):
return self.obj.modified_at
def get_publish(self):
return self.obj.share_status == Map.PUBLIC
def get_vectors(self):
vectors = []
if self.obj.name:
vectors.append(UnaccentVector(self.obj.name, weight='A'))
if self.obj.description:
vectors.append(UnaccentVector(self.obj.description, weight='B'))
if self.obj.owner:
vectors.append(UnaccentVector(self.obj.owner.username))
return vectors
register(Map, MapIndex)
...@@ -46,14 +46,6 @@ LANGUAGES = ( ...@@ -46,14 +46,6 @@ LANGUAGES = (
SECRET_KEY = '' SECRET_KEY = ''
INSTALLED_APPS = ( INSTALLED_APPS = (
'leaflet_storage',
'umap',
'pgindex',
'compressor',
'social.apps.django_app.default',
'south',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
...@@ -61,8 +53,12 @@ INSTALLED_APPS = ( ...@@ -61,8 +53,12 @@ INSTALLED_APPS = (
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.admindocs', 'django.contrib.gis',
'django.contrib.gis'
'leaflet_storage',
'umap',
'compressor',
'social.apps.django_app.default',
) )
#============================================================================== #==============================================================================
...@@ -107,7 +103,7 @@ TEMPLATE_DIRS = ( ...@@ -107,7 +103,7 @@ TEMPLATE_DIRS = (
) )
TEMPLATE_CONTEXT_PROCESSORS += ( TEMPLATE_CONTEXT_PROCESSORS += (
'django.core.context_processors.request', 'django.template.context_processors.request',
'social.apps.django_app.context_processors.backends', 'social.apps.django_app.context_processors.backends',
'social.apps.django_app.context_processors.login_redirect', 'social.apps.django_app.context_processors.login_redirect',
'umap.context_processors.feedback_link', 'umap.context_processors.feedback_link',
...@@ -139,9 +135,9 @@ MIDDLEWARE_CLASSES = ( ...@@ -139,9 +135,9 @@ MIDDLEWARE_CLASSES = (
AUTHENTICATION_BACKENDS += ( AUTHENTICATION_BACKENDS += (
) )
#============================================================================== # =============================================================================
# Miscellaneous project settings # Miscellaneous project settings
#============================================================================== # =============================================================================
LEAFLET_STORAGE_ALLOW_ANONYMOUS = False LEAFLET_STORAGE_ALLOW_ANONYMOUS = False
LEAFLET_STORAGE_EXTRA_URLS = { LEAFLET_STORAGE_EXTRA_URLS = {
'routing': 'http://map.project-osrm.org/?loc={lat},{lng}&hl={locale}', 'routing': 'http://map.project-osrm.org/?loc={lat},{lng}&hl={locale}',
...@@ -151,11 +147,12 @@ SITE_URL = "http://umap.org" ...@@ -151,11 +147,12 @@ SITE_URL = "http://umap.org"
UMAP_DEMO_SITE = False UMAP_DEMO_SITE = False
MAP_SHORT_URL_NAME = "umap_short_url" MAP_SHORT_URL_NAME = "umap_short_url"
UMAP_USE_UNACCENT = False UMAP_USE_UNACCENT = False
UMAP_FEEDBACK_LINK = "http://wiki.openstreetmap.org/wiki/UMap#Feedback_and_help" UMAP_FEEDBACK_LINK = "http://wiki.openstreetmap.org/wiki/UMap#Feedback_and_help" # noqa
USER_MAPS_URL = 'user_maps'
#============================================================================== # =============================================================================
# Third party app settings # Third party app settings
#============================================================================== # =============================================================================
COMPRESS_ENABLED = True COMPRESS_ENABLED = True
COMPRESS_OFFLINE = True COMPRESS_OFFLINE = True
......
...@@ -11,12 +11,12 @@ ...@@ -11,12 +11,12 @@
<script src="{{ STATIC_URL }}storage/reqs/osmtogeojson/osmtogeojson.js"></script> <script src="{{ STATIC_URL }}storage/reqs/osmtogeojson/osmtogeojson.js"></script>
<script src="{{ STATIC_URL }}storage/reqs/loading/Control.Loading.js"></script> <script src="{{ STATIC_URL }}storage/reqs/loading/Control.Loading.js"></script>
<script src="{{ STATIC_URL }}storage/reqs/markercluster/leaflet.markercluster-src.js"></script> <script src="{{ STATIC_URL }}storage/reqs/markercluster/leaflet.markercluster-src.js"></script>
<script src="{{ STATIC_URL }}storage/reqs/draw/leaflet.draw-src.js"></script>
<script src="{{ STATIC_URL }}storage/reqs/measure/leaflet.measurecontrol.js"></script>
<script src="{{ STATIC_URL }}storage/reqs/contextmenu/leaflet.contextmenu-src.js"></script> <script src="{{ STATIC_URL }}storage/reqs/contextmenu/leaflet.contextmenu-src.js"></script>
<script src="{{ STATIC_URL }}storage/reqs/label/leaflet.label-src.js"></script> <script src="{{ STATIC_URL }}storage/reqs/label/leaflet.label-src.js"></script>
<script src="{{ STATIC_URL }}storage/reqs/georsstogeojson/GeoRSSToGeoJSON.js"></script> <script src="{{ STATIC_URL }}storage/reqs/georsstogeojson/GeoRSSToGeoJSON.js"></script>
<script src="{{ STATIC_URL }}storage/reqs/heat/leaflet-heat.js"></script> <script src="{{ STATIC_URL }}storage/reqs/heat/leaflet-heat.js"></script>
<script src="{{ STATIC_URL }}storage/reqs/toolbar/leaflet.toolbar-src.js"></script>
<script src="{{ STATIC_URL }}storage/reqs/formbuilder/Leaflet.FormBuilder.js"></script>
<script src="{{ STATIC_URL }}storage/reqs/togpx/togpx.js"></script> <script src="{{ STATIC_URL }}storage/reqs/togpx/togpx.js"></script>
<script src="{{ STATIC_URL }}storage/reqs/tokml/tokml.js"></script> <script src="{{ STATIC_URL }}storage/reqs/tokml/tokml.js"></script>
{% endcompress %} {% endcompress %}
......
...@@ -9,7 +9,8 @@ from umap.views import validate_url ...@@ -9,7 +9,8 @@ from umap.views import validate_url
class TestsValidateProxyURL(TestCase): class TestsValidateProxyURL(TestCase):
def buildRequest(self, target="http://osm.org/georss.xml", verb="get", **kwargs): def buildRequest(self, target="http://osm.org/georss.xml", verb="get",
**kwargs):
defaults = { defaults = {
'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest', 'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest',
'HTTP_REFERER': '%s/path/' % settings.SITE_URL 'HTTP_REFERER': '%s/path/' % settings.SITE_URL
...@@ -72,5 +73,5 @@ class TestsProxy(TestCase): ...@@ -72,5 +73,5 @@ class TestsProxy(TestCase):
} }
response = self.client.get(url, params, **headers) response = self.client.get(url, params, **headers)
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
self.assertIn('Example Domain', response.content) self.assertIn('Example Domain', response.content.decode())
self.assertNotIn("Cookie", response['Vary']) self.assertNotIn("Cookie", response['Vary'])
...@@ -17,12 +17,12 @@ urlpatterns = patterns( ...@@ -17,12 +17,12 @@ urlpatterns = patterns(
(r'^admin/', include(admin.site.urls)), (r'^admin/', include(admin.site.urls)),
url('', include('social.apps.django_app.urls', namespace='social')), url('', include('social.apps.django_app.urls', namespace='social')),
url(r'^m/(?P<pk>\d+)/$', MapShortUrl.as_view(), name='umap_short_url'), url(r'^m/(?P<pk>\d+)/$', MapShortUrl.as_view(), name='umap_short_url'),
url(r'^ajax-proxy/$', cache_page(180)(views.ajax_proxy), name='ajax-proxy'), url(r'^ajax-proxy/$', cache_page(180)(views.ajax_proxy), name='ajax-proxy'), # noqa
) )
urlpatterns += i18n_patterns( urlpatterns += i18n_patterns(
'', '',
url(r'^$', views.home, name="home"), url(r'^$', views.home, name="home"),
url(r'^showcase/$', cache_page(24 * 60 * 60)(views.showcase), name='maps_showcase'), url(r'^showcase/$', cache_page(24 * 60 * 60)(views.showcase), name='maps_showcase'), # noqa
url(r'^search/$', views.search, name="search"), url(r'^search/$', views.search, name="search"),
url(r'^about/$', views.about, name="about"), url(r'^about/$', views.about, name="about"),
url(r'^user/(?P<username>[-_\w@]+)/$', views.user_maps, name='user_maps'), url(r'^user/(?P<username>[-_\w@]+)/$', views.user_maps, name='user_maps'),
...@@ -30,5 +30,6 @@ urlpatterns += i18n_patterns( ...@@ -30,5 +30,6 @@ urlpatterns += i18n_patterns(
) )
if settings.DEBUG and settings.MEDIA_ROOT: if settings.DEBUG and settings.MEDIA_ROOT:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
urlpatterns += staticfiles_urlpatterns() urlpatterns += staticfiles_urlpatterns()
import simplejson import json
import mimetypes import mimetypes
import urllib2
import socket import socket
from urlparse import urlparse try:
# python3
from urllib.parse import urlparse
from urllib.request import Request, build_opener
from urllib.error import HTTPError
except ImportError:
from urlparse import urlparse
from urllib2 import Request, HTTPError, build_opener
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
...@@ -15,8 +21,7 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger ...@@ -15,8 +21,7 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.http import HttpResponse, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseBadRequest
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models.sql.where import ExtraWhere, OR from django.core.validators import URLValidator, ValidationError
from pgindex import search as pg_search
from leaflet_storage.models import Map from leaflet_storage.models import Map
from leaflet_storage.forms import DEFAULT_CENTER from leaflet_storage.forms import DEFAULT_CENTER
...@@ -36,7 +41,8 @@ class PaginatorMixin(object): ...@@ -36,7 +41,8 @@ class PaginatorMixin(object):
# If page is not an integer, deliver first page. # If page is not an integer, deliver first page.
qs = paginator.page(1) qs = paginator.page(1)
except EmptyPage: except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results. # If page is out of range (e.g. 9999), deliver last page of
# results.
qs = paginator.page(paginator.num_pages) qs = paginator.page(paginator.num_pages)
return qs return qs
...@@ -47,7 +53,7 @@ class Home(TemplateView, PaginatorMixin): ...@@ -47,7 +53,7 @@ class Home(TemplateView, PaginatorMixin):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
qs = Map.public qs = Map.public
if not 'spatialite' in settings.DATABASES['default']['ENGINE']: if 'spatialite' not in settings.DATABASES['default']['ENGINE']:
# Unsupported query type for sqlite. # Unsupported query type for sqlite.
qs = qs.filter(center__distance_gt=(DEFAULT_CENTER, D(km=1))) qs = qs.filter(center__distance_gt=(DEFAULT_CENTER, D(km=1)))
demo_map = None demo_map = None
...@@ -103,8 +109,10 @@ class UserMaps(DetailView, PaginatorMixin): ...@@ -103,8 +109,10 @@ class UserMaps(DetailView, PaginatorMixin):
context_object_name = "current_user" context_object_name = "current_user"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
manager = Map.objects if self.request.user == self.object else Map.public manager = Map.objects if self.request.user == self.object\
maps = manager.filter(Q(owner=self.object) | Q(editors=self.object)).distinct().order_by('-modified_at')[:50] else Map.public
maps = manager.filter(Q(owner=self.object) | Q(editors=self.object))
maps = maps.distinct().order_by('-modified_at')[:50]
maps = self.paginate(maps) maps = self.paginate(maps)
kwargs.update({ kwargs.update({
"maps": maps "maps": maps
...@@ -131,13 +139,14 @@ class Search(TemplateView, PaginatorMixin): ...@@ -131,13 +139,14 @@ class Search(TemplateView, PaginatorMixin):
q = self.request.GET.get('q') q = self.request.GET.get('q')
results = [] results = []
if q: if q:
results = pg_search(q) where = "to_tsvector(name) @@ to_tsquery(%s)"
if getattr(settings, 'UMAP_USE_UNACCENT', False): if getattr(settings, 'UMAP_USE_UNACCENT', False):
# Add unaccent support where = "to_tsvector(unaccent(name)) @@ to_tsquery(unaccent(%s))" # noqa
results.query.where.add(ExtraWhere(("ts @@ plainto_tsquery('simple', unaccent(%s))", ), [q, ]), OR) results = Map.objects.filter(share_status=Map.PUBLIC)
results = results.order_by('-rank', '-start_publish') results = results.extra(where=[where], params=[q])
results = results.order_by('-modified_at')
print(results.query)
results = self.paginate(results) results = self.paginate(results)
results.object_list = [Map.objects.get(pk=i.obj_pk) for i in results]
kwargs.update({ kwargs.update({
'maps': results, 'maps': results,
'q': q 'q': q
...@@ -159,7 +168,8 @@ search = Search.as_view() ...@@ -159,7 +168,8 @@ search = Search.as_view()
class MapsShowCase(View): class MapsShowCase(View):
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
maps = Map.public.filter(center__distance_gt=(DEFAULT_CENTER, D(km=1))).order_by('-modified_at')[:2500] maps = Map.public.filter(center__distance_gt=(DEFAULT_CENTER, D(km=1)))
maps = maps.order_by('-modified_at')[:2500]
def make(m): def make(m):
description = m.description or "" description = m.description or ""
...@@ -167,11 +177,13 @@ class MapsShowCase(View): ...@@ -167,11 +177,13 @@ class MapsShowCase(View):
description = u"{description}\n{by} [[{url}|{name}]]".format( description = u"{description}\n{by} [[{url}|{name}]]".format(
description=description, description=description,
by=_("by"), by=_("by"),
url=reverse('user_maps', kwargs={"username": m.owner.username}), url=reverse('user_maps',
kwargs={"username": m.owner.username}),
name=m.owner, name=m.owner,
) )
description = u"{}\n[[{}|{}]]".format(description, m.get_absolute_url(), _("View the map")) description = u"{}\n[[{}|{}]]".format(
geometry = m.settings['geometry'] if "geometry" in m.settings else simplejson.loads(m.center.geojson) description, m.get_absolute_url(), _("View the map"))
geometry = m.settings.get('geometry', json.loads(m.center.geojson))
return { return {
"type": "Feature", "type": "Feature",
"geometry": geometry, "geometry": geometry,
...@@ -185,14 +197,11 @@ class MapsShowCase(View): ...@@ -185,14 +197,11 @@ class MapsShowCase(View):
"type": "FeatureCollection", "type": "FeatureCollection",
"features": [make(m) for m in maps] "features": [make(m) for m in maps]
} }
return HttpResponse(simplejson.dumps(geojson)) return HttpResponse(json.dumps(geojson))
showcase = MapsShowCase.as_view() showcase = MapsShowCase.as_view()
from django.core.validators import URLValidator, ValidationError
def validate_url(request): def validate_url(request):
assert request.method == "GET" assert request.method == "GET"
assert request.is_ajax() assert request.is_ajax()
...@@ -226,22 +235,24 @@ class AjaxProxy(View): ...@@ -226,22 +235,24 @@ class AjaxProxy(View):
# You should not use this in production (use Nginx or so) # You should not use this in production (use Nginx or so)
try: try:
url = validate_url(self.request) url = validate_url(self.request)
except AssertionError: except AssertionError as e:
return HttpResponseBadRequest() return HttpResponseBadRequest()
headers = { headers = {
'User-Agent': 'uMapProxy +http://wiki.openstreetmap.org/wiki/UMap' 'User-Agent': 'uMapProxy +http://wiki.openstreetmap.org/wiki/UMap'
} }
request = urllib2.Request(url, headers=headers) request = Request(url, headers=headers)
opener = urllib2.build_opener() opener = build_opener()
try: try:
proxied_request = opener.open(request) proxied_request = opener.open(request)
except urllib2.HTTPError as e: except HTTPError as e:
return HttpResponse(e.msg, status=e.code, mimetype='text/plain') return HttpResponse(e.msg, status=e.code,
content_type='text/plain')
else: else:
status_code = proxied_request.code status_code = proxied_request.code
mimetype = proxied_request.headers.typeheader or mimetypes.guess_type(url) mimetype = proxied_request.headers.get('Content-Type') or mimetypes.guess_type(url) # noqa
content = proxied_request.read() content = proxied_request.read()
# Quick hack to prevent Django from adding a Vary: Cookie header # Quick hack to prevent Django from adding a Vary: Cookie header
self.request.session.accessed = False self.request.session.accessed = False
return HttpResponse(content, status=status_code, mimetype=mimetype) return HttpResponse(content, status=status_code,
content_type=mimetype)
ajax_proxy = AjaxProxy.as_view() ajax_proxy = AjaxProxy.as_view()
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter