Split event creation into two steps: 1) define the event, 2) pick the place. Make place selection optional. Use Google maps to easily find the address of a place

This commit is contained in:
Michael Hall 2018-02-06 23:20:38 -05:00
parent d47ec4635e
commit 29e58be896
11 changed files with 347 additions and 44 deletions

View file

@ -187,9 +187,8 @@ class TeamEventForm(forms.ModelForm):
class NewTeamEventForm(forms.ModelForm):
class Meta:
model = Event
fields = ['name', 'start_time', 'end_time', 'summary', 'place']
fields = ['name', 'start_time', 'end_time', 'summary']
widgets = {
'place': Lookup(source='/api/places/', label='name'),
'start_time': DateTimeWidget,
'end_time': DateTimeWidget
}

View file

@ -3,7 +3,7 @@
<table border="0" width="960px">
{% for place in places_list %}
<tr>
<td width="20%">{{ place.name }}</td>
<td width="20%"><a href="{% url 'show-place' place.id %}">{{ place.name }}</a></td>
<td width="40%">{{ place.address|default:'' }}</td>
<td width="40%">{{ place.city|default:'' }}</td>
</tr>

View file

@ -74,6 +74,22 @@ def city_list(request, *args, **kwargs):
serializer = CitySerializer(cities, many=True)
return Response(serializer.data)
@api_view(['GET'])
def find_city(request):
cities = City.objects.all()
if "city" in request.GET:
cities = cities.filter(name=request.GET.get("city"))
if "spr" in request.GET:
cities = cities.filter(spr__name=request.GET.get("spr"))
if "country" in request.GET:
cities = cities.filter(spr__country__name=request.GET.get("country"))
try:
city = cities[0]
serializer = CitySerializer(city)
return Response(serializer.data)
except:
return Response({})
def join_team(request, team_id):
if request.user.is_anonymous:
messages.add_message(request, messages.WARNING, message=_('You must be logged in to join a team.'))

View file

@ -152,6 +152,7 @@ GOOGLE_ANALYTICS_ID=None
SETTINGS_EXPORT = [
'DEBUG',
'GOOGLE_ANALYTICS_ID',
'GOOGLE_MAPS_API_KEY',
'SOCIAL_AUTH_GITHUB_KEY',
'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY',
]

View file

@ -11,7 +11,7 @@
<div class="form-group">
{% include "events/event_form.html" %}
<br />
<button type="submit" class="btn btn-primary">Announce your Get Together</button>
<button type="submit" class="btn btn-primary">Next: Find a Place &gt;</button>
</div>
</form>
{% endblock %}
@ -19,22 +19,6 @@
{% block javascript %}
<script type="text/javascript">
$(document).ready(function(){
$("#place_search").keyup(function() {
var searchText = this.value;
$.getJSON("/api/places/?q="+searchText, function(data) {
var searchField = $("#place_search")[0];
var q = this.url.match(/q=([^&]+)/)[1]
var c = searchField.value
if (c != q) return;
var selectField = $("#place_select");
selectField.empty();
$.each(data, function(){
selectField.append('<option value="'+ this.id +'">'+ this.name+' '+this.city + '</option>')
});
});
});
$.datepicker.setDefaults({
showOn: 'focus',

View file

@ -34,7 +34,15 @@
<tr>
<td><b>Time:</b></td><td>{{ event.start_time }} - {{ event.end_time }}</td>
</tr><tr>
<td><b>Place:</b></td><td>{% if event.place %}{{ event.place }}{% else %}No place selected yet.{% endif %}</td>
<td><b>Place:</b></td><td>
{% if event.place %}
<a class="" href="{% url 'show-place' event.place.id %}">{{ event.place.name }}</a>
{% elif can_edit_event %}
<a class="" href="{% url 'add-place' event.id %}">No place selected yet.</a>
{% else %}
No place selected yet.
{% endif %}
</td>
</tr><tr>
{% if event.web_url %}
</tr><tr>

View file

@ -1,38 +1,223 @@
{% extends "get_together/base.html" %}
{% block content %}
<h2>Choose your meeting place</h2>
<form action="{% url "create-place" %}" method="post">
{% csrf_token %}
<div class="form-group">
{% include "events/place_form.html" %}
<br />
<button type="submit" class="btn btn-primary">Save your Place</button>
<div class="container">
<div class="row">
<div class="col-md col-10">
<h2>Choose your meeting place</h2>
<form action="{% url "add-place" event.id %}" method="post">
{% csrf_token %}
{% include "events/place_form.html" %}
<div class="fluid-layout" id="place-map"></div>
<br />
<button type="submit" class="btn btn-primary">Announce your Get Together!</button>
</form>
<a class="btn btn-secondary" href="{{ event.get_absolute_url }}">Add a Place later</a>
</div>
<div id="map" class="col-5"></div>
</div>
</div>
</form>
{% endblock %}
{% block javascript %}
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key={{settings.GOOGLE_MAPS_API_KEY}}"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#city_search").keyup(function() {
var searchText = this.value;
if (searchText.length < 3) return;
$.getJSON("/api/cities/?q="+searchText, function(data) {
var searchField = $("#city_search")[0];
var q = this.url.match(/q=([^&]+)/)[1]
var c = searchField.value
if (c != q) return;
/*
* jQuery Google Map Plugin 0.2.3
* https://wiki.ubuntu.com/ubuntu-django-foundations/map
* Requires jQuery 1.4.2
*
* Copyright 2011, Ronnie van den Crommenacker
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
var selectField = $("#city_select");
selectField.empty();
(function ($) {
$.fn.extend({
selectLocation: function (options) {
var defaults = {
html_lng: null,
html_lat: null,
html_tz: null,
html_country: null,
html_continent: null,
marker_icon: null,
markers: [],
html_addr: null,
mapOptions: {
zoom: 4,
center: {lat: 51.8211, lng: 5.591},
mapTypeId: google.maps.MapTypeId.ROADMAP,
mapTypeControl: false
}
};
options = $.extend(defaults, options);
function showPositionHTML(location) {
var geocoder = new google.maps.Geocoder();
geocoder.geocode({'latLng': location}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[0]) {
$('#id_address').val(results[0].formatted_address);
var name, city, spr, country = "";
$.each(results[0].address_components, function(){
if(this.types[0]=="country") {
country = this.long_name
} else if(this.types[0]=="administrative_area_level_1"){
spr = this.long_name
} else if(this.types[0]=="locality"){
city = this.long_name
} else if(this.types[0]=="premise"){
name = this.long_name
}
});
if (name != "") {
$("#id_name").val(name);
}
$.getJSON("/api/find_city/?country="+country+"&spr="+spr+"&city="+city,
function(json) {
if (json.id) {
var selectField = $("#city_select");
selectField.empty();
selectField.append('<option value="'+ json.id +'" selected>'+ json.display + '</option>');
$("#id_tz").val(json.tz)
}
}
);
}
}
});
if (options.html_lng && options.html_lat) {
if (location.lat() && location.lng()) {
options.html_lat.val(location.lat());
options.html_lng.val(location.lng());
if (options.html_tz && options.html_country && options.html_continent) {
$.getJSON("http://ws.geonames.org/timezoneJSON?lat=" + location.lat() + "&lng=" + location.lng(),
function(json) {
$(options.html_tz).filter(function() {
return $(this).text() == json.timezoneId;
}).prop('selected', true);
$(options.html_country).filter(function() {
return $(this).text() == json.countryName;
}).prop('selected', true);
$.getJSON("http://ws.geonames.org/countryInfoJSON?country=" + json.countryCode,
function(json) {
if (typeof json.geonames[0].continentName !== "undefined") {
$(options.html_continent).filter(function() {
return $(this).text() == json.geonames[0].continentName;
}).prop('selected', true);
}
}
);
}
);
}
}
}
}
function setMarker(map, location) {
var marker = null;
if (options.markers.length) {
marker = options.markers[0];
marker.setPosition(location);
marker.setAnimation(google.maps.Animation.DROP);
} else {
marker = new google.maps.Marker({
map: map,
position: location,
draggable: true,
animation: google.maps.Animation.DROP
});
if (options.marker_icon) {
marker.icon = options.marker_icon;
}
options.markers.push(marker);
google.maps.event.addListener(options.markers[0], 'mouseup', function () {
showPositionHTML(marker.getPosition());
});
}
map.setCenter(location);
showPositionHTML(marker.getPosition());
}
return $(this).each(function (i, html_element) {
var map = new google.maps.Map($(html_element)[0], options.mapOptions),
geoCoder = new google.maps.Geocoder(),
location = null;
if (options.html_addr) {
options.html_addr.change(function () {
var address = [];
options.html_addr.each(function (i, item) {
address.push(item.value);
});
geoCoder.geocode({address: address.join(' ')}, function (results, status) {
if (status === google.maps.GeocoderStatus.OK) {
setMarker(map, results[0].geometry.location);
}
});
});
}
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var pos = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
map.setCenter(pos);
map.setZoom(12);
}, function() {
handleLocationError(true, infoWindow, map.getCenter());
});
}
google.maps.event.addListener(map, 'click', function (event) {
setMarker(map, event.latLng);
});
if (options.html_lat.val() && options.html_lng.val()) {
location = new google.maps.LatLng(options.html_lat.val(), options.html_lng.val());
setMarker(map, location);
}
});
}
});
}(jQuery));
$("#map").selectLocation({
html_lat: $("#id_latitude"),
html_lng: $("#id_longitude")
});
$(document).ready(function(){
$("#city_search").keyup(function() {
var searchText = this.value;
if (searchText.length < 3) return;
$.getJSON("/api/cities/?q="+searchText, function(data) {
var searchField = $("#city_search")[0];
var q = this.url.match(/q=([^&]+)/)[1]
var c = searchField.value
if (c != q) return;
var selectField = $("#city_select");
selectField.empty();
var selected = " selected"
selectField.append('<option value="">--------</option>')
$.each(data, function(){
selectField.append('<option value="'+ this.id +'"'+ selected +'>'+ this.display + '</option>');
selected="";
});
});
});
});
});
</script>

View file

@ -0,0 +1,68 @@
{% extends "get_together/base.html" %}
{% load markup %}
{% block styles %}
<style>
.gt-profile {
position: relative;
}
.gt-profile .gt-profile-badges {
position: absolute;
top: 16px;
left: 6px;
}
</style>
{% endblock %}
{% block content %}
<div class="fluid-container">
<div class="row">
<div class="col-md">
<h2>{{ place.name }}
{% if can_edit_place %}
<a href="{% url 'edit-place' place.id %}" class="btn btn-secondary btn-sm">Edit Place</a>
{% endif %}
</h2>
<table class="table">
<tr>
<td><b>Address:</b></td><td>{{ place.address }}</td>
</tr><tr>
<td><b>City:</b></td><td>{{ place.city }}</td>
</tr><tr>
{% if place.place_url %}
</tr><tr>
<td><b>Website:</b></td><td><a href="{{ place.place_url }}" target="_blank">{{ place.place_url }}</a></td>
{% endif %}
</tr>
</table>
<iframe
width="720"
height="400"
frameborder="0" style="border:0"
src="https://www.google.com/maps/embed/v1/place?key={{ settings.GOOGLE_MAPS_API_KEY }}
&q={{place.name}},{{place.city}}" allowfullscreen>
</iframe>
</div>
<div class="col-3-sm">
<div class="container">
<div class="row">
<div class="col"><h4>Events ({{event_list.count}})</h4><hr/></div>
</div>
{% for event in event_list %}
<div class="row mb-3">
<div class="col">
<h6 class="mt-2 mb-0"><a href="{{event.get_absolute_url}}">{{event.name}}</a></h6>
<small class="text-muted">{{ event.start_time }}</small>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -32,6 +32,7 @@ urlpatterns = [
path('api/countries/', event_views.country_list),
path('api/spr/', event_views.spr_list),
path('api/cities/', event_views.city_list),
path('api/find_city/', event_views.find_city),
path('profile/+edit', views.edit_profile, name='edit-profile'),
@ -48,9 +49,11 @@ urlpatterns = [
path('events/<int:event_id>/+edit/', views.edit_event, name='edit-event'),
path('events/<int:event_id>/+attend/', event_views.attend_event, name='attend-event'),
path('events/<int:event_id>/+delete/', views.delete_event, name='delete-event'),
path('events/<int:event_id>/+add_place/', views.add_place_to_event, name='add-place'),
path('events/<int:event_id>/<str:event_slug>/', views.show_event, name='show-event'),
path('places/', views.places_list, name='places'),
path('places/<int:place_id>/', views.show_place, name='show-place'),
path('+create-place/', views.create_place, name='create-place'),
path('oauth/', include('social_django.urls', namespace='social')),

View file

@ -6,7 +6,7 @@ from django.shortcuts import render, redirect
from django.http import HttpResponse, JsonResponse
from events.models.profiles import Team, UserProfile, Member
from events.forms import TeamEventForm, NewTeamEventForm, DeleteEventForm
from events.forms import TeamEventForm, NewTeamEventForm, DeleteEventForm, NewPlaceForm
from events.models.events import Event, Place, Attendee
@ -52,7 +52,7 @@ def create_event(request, team_id):
form.instance.team = team
form.instance.created_by = request.user.profile
new_event = form.save()
return redirect(new_event.get_absolute_url())
return redirect('add-place', new_event.id)
else:
context = {
'team': team,
@ -62,6 +62,36 @@ def create_event(request, team_id):
else:
return redirect('home')
def add_place_to_event(request, event_id):
event = Event.objects.get(id=event_id)
if not request.user.profile.can_edit_event(event):
messages.add_message(request, messages.WARNING, message=_('You can not make changes to this event.'))
return redirect(event.get_absolute_url())
if request.method == 'GET':
form = NewPlaceForm()
context = {
'event': event,
'place_form': form,
}
return render(request, 'get_together/places/create_place.html', context)
elif request.method == 'POST':
form = NewPlaceForm(request.POST)
if form.is_valid:
new_place = form.save()
event.place = new_place
event.save()
return redirect(event.get_absolute_url())
else:
context = {
'event': event,
'place_form': form,
}
return render(request, 'get_together/places/create_place.html', context)
else:
return redirect('home')
def edit_event(request, event_id):
event = Event.objects.get(id=event_id)

View file

@ -6,7 +6,7 @@ from django.shortcuts import render, redirect
from django.http import HttpResponse, JsonResponse
from events.models.profiles import Team, UserProfile, Member
from events.forms import TeamForm, NewTeamForm, TeamEventForm, NewTeamEventForm, NewPlaceForm
from events.forms import NewPlaceForm
from events.models.events import Event, Place, Attendee
@ -21,6 +21,15 @@ def places_list(request, *args, **kwargs):
}
return render(request, 'get_together/places/list_places.html', context)
def show_place(request, place_id):
place = Place.objects.get(id=place_id)
context = {
'place': place,
'event_list': Event.objects.filter(place=place).order_by('-start_time'),
}
return render(request, 'get_together/places/show_place.html', context)
def create_place(request):
if request.method == 'GET':
form = NewPlaceForm()