Fix #1416 -- Add canonical geodata field (#1458)

* Fix #1416 -- Add canonical geodata field for events and subevents

* Add optional geocoding through OpenCageData

* Fix markup everywhere

* Add Leaflet map to geo coordinates

* Fix tests, add credits

* Fix spelling
This commit is contained in:
Raphael Michel
2019-10-21 13:07:35 +02:00
committed by GitHub
parent 19b10e3ca4
commit 27538d220e
30 changed files with 1098 additions and 9 deletions

View File

@@ -112,6 +112,8 @@ class EventWizardBasicsForm(I18nModelForm):
'presale_start',
'presale_end',
'location',
'geo_lat',
'geo_lon',
]
field_classes = {
'date_from': SplitDateTimeField,
@@ -282,6 +284,8 @@ class EventUpdateForm(I18nModelForm):
'presale_start',
'presale_end',
'location',
'geo_lat',
'geo_lon',
]
field_classes = {
'date_from': SplitDateTimeField,

View File

@@ -37,6 +37,20 @@ class GlobalSettingsForm(SettingsForm):
required=False,
label=_("Global message banner detail text"),
)),
('opencagedata_apikey', forms.CharField(
required=False,
label=_("OpenCage API key for geocoding"),
)),
('leaflet_tiles', forms.CharField(
required=False,
label=_("Leaflet tiles URL pattern"),
help_text=_("e.g. {sample}").format(sample="https://a.tile.openstreetmap.org/{z}/{x}/{y}.png")
)),
('leaflet_tiles_attribution', forms.CharField(
required=False,
label=_("Leaflet tiles attribution"),
help_text=_("e.g. {sample}").format(sample='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors')
)),
])
responses = register_global_settings.send(self)
for r, response in sorted(responses, key=lambda r: str(r[0])):

View File

@@ -37,6 +37,8 @@ class SubEventForm(I18nModelForm):
'presale_end',
'location',
'frontpage_text',
'geo_lat',
'geo_lon',
]
field_classes = {
'date_from': SplitDateTimeField,

View File

@@ -50,6 +50,8 @@
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/quicksetup.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/dashboard.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/tabs.js" %}"></script>
<script type="text/javascript" src="{% static "leaflet/leaflet.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/geo.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/details.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/asynctask.js" %}"></script>
<script type="text/javascript" src="{% static "sortable/Sortable.js" %}"></script>

View File

@@ -1,6 +1,7 @@
{% extends "pretixcontrol/event/settings_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load static %}
{% load hierarkey_form %}
{% block custom_header %}
{{ block.super }}
@@ -19,7 +20,30 @@
{% bootstrap_field form.slug layout="control" %}
{% bootstrap_field form.date_from layout="control" %}
{% bootstrap_field form.date_to layout="control" %}
{% bootstrap_field form.location layout="control" %}
<div class="geodata-section">
{% bootstrap_field form.location layout="control" %}
<div class="form-group geodata-group" data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}" data-attrib="{{ global_settings.leaflet_tiles_attribution }}" data-icon="{% static "leaflet/images/marker-icon.png" %}" data-shadow="{% static "leaflet/images/marker-shadow.png" %}">
<label class="col-md-3 control-label">
{% trans "Geo coordinates" %}<br>
<span class="optional">{% trans "Optional" %}</span>
</label>
<div class="col-md-4">
{% bootstrap_field form.geo_lat layout="inline" %}
{% if global_settings.opencagedata_apikey %}
<p class="attrib">
<a href="https://openstreetmap.org/" target="_blank">
{% trans "Geocoding data © OpenStreetMap" %}
</a>
</p>
{% endif %}
</div>
<div class="col-md-4">
{% bootstrap_field form.geo_lon layout="inline" %}
</div>
<div class="col-md-1">
</div>
</div>
</div>
{% bootstrap_field form.date_admission layout="control" %}
{% bootstrap_field form.currency layout="control" %}
{% bootstrap_field sform.contact_mail layout="control" %}

View File

@@ -1,6 +1,7 @@
{% extends "pretixcontrol/events/create_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load static %}
{% block form %}
<fieldset>
<legend>{% trans "General information" %}</legend>
@@ -36,7 +37,30 @@
</div>
{% bootstrap_field form.date_from layout="control" %}
{% bootstrap_field form.date_to layout="control" %}
{% bootstrap_field form.location layout="control" %}
<div class="geodata-section">
{% bootstrap_field form.location layout="control" %}
<div class="form-group geodata-group" data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}" data-attrib="{{ global_settings.leaflet_tiles_attribution }}" data-icon="{% static "leaflet/images/marker-icon.png" %}" data-shadow="{% static "leaflet/images/marker-shadow.png" %}">
<label class="col-md-3 control-label">
{% trans "Geo coordinates" %}<br>
<span class="optional">{% trans "Optional" %}</span>
</label>
<div class="col-md-4">
{% bootstrap_field form.geo_lat layout="inline" %}
{% if global_settings.opencagedata_apikey %}
<p class="attrib">
<a href="https://openstreetmap.org/" target="_blank">
{% trans "Geocoding data © OpenStreetMap" %}
</a>
</p>
{% endif %}
</div>
<div class="col-md-4">
{% bootstrap_field form.geo_lon layout="inline" %}
</div>
<div class="col-md-1">
</div>
</div>
</div>
{% bootstrap_field form.currency layout="control" %}
{% bootstrap_field form.tax_rate addon_after="%" layout="control" %}
</fieldset>

View File

@@ -3,6 +3,7 @@
{% load bootstrap3 %}
{% load formset_tags %}
{% load captureas %}
{% load static %}
{% load eventsignal %}
{% block title %}{% trans "Date" context "subevent" %}{% endblock %}
{% block content %}
@@ -266,7 +267,30 @@
{% bootstrap_field form.active layout="control" %}
{% bootstrap_field form.time_from layout="control" %}
{% bootstrap_field form.time_to layout="control" %}
{% bootstrap_field form.location layout="control" %}
<div class="geodata-section">
{% bootstrap_field form.location layout="control" %}
<div class="form-group geodata-group" data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}" data-attrib="{{ global_settings.leaflet_tiles_attribution }}" data-icon="{% static "leaflet/images/marker-icon.png" %}" data-shadow="{% static "leaflet/images/marker-shadow.png" %}">
<label class="col-md-3 control-label">
{% trans "Geo coordinates" %}<br>
<span class="optional">{% trans "Optional" %}</span>
</label>
<div class="col-md-4">
{% bootstrap_field form.geo_lat layout="inline" %}
{% if global_settings.opencagedata_apikey %}
<p class="attrib">
<a href="https://openstreetmap.org/" target="_blank">
{% trans "Geocoding data © OpenStreetMap" %}
</a>
</p>
{% endif %}
</div>
<div class="col-md-4">
{% bootstrap_field form.geo_lon layout="inline" %}
</div>
<div class="col-md-1">
</div>
</div>
</div>
{% bootstrap_field form.time_admission layout="control" %}
{% bootstrap_field form.frontpage_text layout="control" %}
{% bootstrap_field form.is_public layout="control" %}

View File

@@ -3,6 +3,7 @@
{% load bootstrap3 %}
{% load formset_tags %}
{% load eventsignal %}
{% load static %}
{% block title %}{% trans "Date" context "subevent" %}{% endblock %}
{% block content %}
{% if not subevent.pk %}
@@ -24,7 +25,30 @@
{% bootstrap_field form.active layout="control" %}
{% bootstrap_field form.date_from layout="control" %}
{% bootstrap_field form.date_to layout="control" %}
{% bootstrap_field form.location layout="control" %}
<div class="geodata-section">
{% bootstrap_field form.location layout="control" %}
<div class="form-group geodata-group" data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}" data-attrib="{{ global_settings.leaflet_tiles_attribution }}" data-icon="{% static "leaflet/images/marker-icon.png" %}" data-shadow="{% static "leaflet/images/marker-shadow.png" %}">
<label class="col-md-3 control-label">
{% trans "Geo coordinates" %}<br>
<span class="optional">{% trans "Optional" %}</span>
</label>
<div class="col-md-4">
{% bootstrap_field form.geo_lat layout="inline" %}
{% if global_settings.opencagedata_apikey %}
<p class="attrib">
<a href="https://openstreetmap.org/" target="_blank">
{% trans "Geocoding data © OpenStreetMap" %}
</a>
</p>
{% endif %}
</div>
<div class="col-md-4">
{% bootstrap_field form.geo_lon layout="inline" %}
</div>
<div class="col-md-1">
</div>
</div>
</div>
{% bootstrap_field form.date_admission layout="control" %}
{% bootstrap_field form.frontpage_text layout="control" %}
{% bootstrap_field form.is_public layout="control" %}

View File

@@ -1,7 +1,7 @@
from django.conf.urls import include, url
from pretix.control.views import (
auth, checkin, dashboards, event, global_settings, item, main, oauth,
auth, checkin, dashboards, event, geo, global_settings, item, main, oauth,
orders, organizer, pdf, search, shredder, subevents, typeahead, user,
users, vouchers, waitinglist,
)
@@ -22,6 +22,7 @@ urlpatterns = [
url(r'^logdetail/$', global_settings.LogDetailView.as_view(), name='global.logdetail'),
url(r'^logdetail/payment/$', global_settings.PaymentDetailView.as_view(), name='global.paymentdetail'),
url(r'^logdetail/refund/$', global_settings.RefundDetailView.as_view(), name='global.refunddetail'),
url(r'^geocode/$', geo.GeoCodeView.as_view(), name='global.geocode'),
url(r'^reauth/$', user.ReauthView.as_view(), name='user.reauth'),
url(r'^sudo/$', user.StartStaffSession.as_view(), name='user.sudo'),
url(r'^sudo/stop/$', user.StopStaffSession.as_view(), name='user.sudo.stop'),

View File

@@ -0,0 +1,59 @@
import logging
from urllib.parse import quote
import requests
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.cache import cache
from django.http import JsonResponse
from django.views.generic.base import View
from pretix.base.settings import GlobalSettingsObject
logger = logging.getLogger(__name__)
class GeoCodeView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
gs = GlobalSettingsObject()
if not gs.settings.opencagedata_apikey:
return JsonResponse({
'success': False,
'results': []
}, status=200)
q = request.GET.get('q')
cd = cache.get('geocode:{}'.format(q))
if cd:
return JsonResponse({
'success': True,
'results': cd
}, status=200)
try:
r = requests.get(
'https://api.opencagedata.com/geocode/v1/json?q={}&key={}'.format(
quote(q), gs.settings.opencagedata_apikey
)
)
r.raise_for_status()
except IOError:
logger.exception("Geocoding failed")
return JsonResponse({
'success': False,
'results': []
}, status=200)
else:
d = r.json()
res = [
{
'formatted': r['formatted'],
'lat': r['geometry']['lat'],
'lon': r['geometry']['lng'],
} for r in d['results']
]
cache.set('geocode:{}'.format(q), res, timeout=3600 * 6)
return JsonResponse({
'success': True,
'results': res
}, status=200)

View File

@@ -134,6 +134,8 @@ class EventWizard(SafeSessionWizardView):
initial['currency'] = self.clone_from.currency
initial['date_from'] = self.clone_from.date_from
initial['date_to'] = self.clone_from.date_to
initial['geo_lat'] = self.clone_from.geo_lat
initial['geo_lon'] = self.clone_from.geo_lon
initial['presale_start'] = self.clone_from.presale_start
initial['presale_end'] = self.clone_from.presale_end
initial['location'] = self.clone_from.location
@@ -245,6 +247,8 @@ class EventWizard(SafeSessionWizardView):
presale_start=event.presale_start,
presale_end=event.presale_end,
location=event.location,
geo_lat=event.geo_lat,
geo_lon=event.geo_lon,
active=True
)