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 NewTeamEventForm(forms.ModelForm):
class Meta: class Meta:
model = Event model = Event
fields = ['name', 'start_time', 'end_time', 'summary', 'place'] fields = ['name', 'start_time', 'end_time', 'summary']
widgets = { widgets = {
'place': Lookup(source='/api/places/', label='name'),
'start_time': DateTimeWidget, 'start_time': DateTimeWidget,
'end_time': DateTimeWidget 'end_time': DateTimeWidget
} }

View file

@ -3,7 +3,7 @@
<table border="0" width="960px"> <table border="0" width="960px">
{% for place in places_list %} {% for place in places_list %}
<tr> <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.address|default:'' }}</td>
<td width="40%">{{ place.city|default:'' }}</td> <td width="40%">{{ place.city|default:'' }}</td>
</tr> </tr>

View file

@ -74,6 +74,22 @@ def city_list(request, *args, **kwargs):
serializer = CitySerializer(cities, many=True) serializer = CitySerializer(cities, many=True)
return Response(serializer.data) 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): def join_team(request, team_id):
if request.user.is_anonymous: if request.user.is_anonymous:
messages.add_message(request, messages.WARNING, message=_('You must be logged in to join a team.')) 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 = [ SETTINGS_EXPORT = [
'DEBUG', 'DEBUG',
'GOOGLE_ANALYTICS_ID', 'GOOGLE_ANALYTICS_ID',
'GOOGLE_MAPS_API_KEY',
'SOCIAL_AUTH_GITHUB_KEY', 'SOCIAL_AUTH_GITHUB_KEY',
'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY',
] ]

View file

@ -11,7 +11,7 @@
<div class="form-group"> <div class="form-group">
{% include "events/event_form.html" %} {% include "events/event_form.html" %}
<br /> <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> </div>
</form> </form>
{% endblock %} {% endblock %}
@ -19,22 +19,6 @@
{% block javascript %} {% block javascript %}
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ $(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({ $.datepicker.setDefaults({
showOn: 'focus', showOn: 'focus',

View file

@ -34,7 +34,15 @@
<tr> <tr>
<td><b>Time:</b></td><td>{{ event.start_time }} - {{ event.end_time }}</td> <td><b>Time:</b></td><td>{{ event.start_time }} - {{ event.end_time }}</td>
</tr><tr> </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> </tr><tr>
{% if event.web_url %} {% if event.web_url %}
</tr><tr> </tr><tr>

View file

@ -1,38 +1,223 @@
{% extends "get_together/base.html" %} {% extends "get_together/base.html" %}
{% block content %} {% block content %}
<h2>Choose your meeting place</h2> <div class="container">
<form action="{% url "create-place" %}" method="post"> <div class="row">
{% csrf_token %} <div class="col-md col-10">
<div class="form-group">
{% include "events/place_form.html" %} <h2>Choose your meeting place</h2>
<br /> <form action="{% url "add-place" event.id %}" method="post">
<button type="submit" class="btn btn-primary">Save your Place</button> {% 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> </div>
</form>
{% endblock %} {% endblock %}
{% block javascript %} {% 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"> <script type="text/javascript">
$(document).ready(function(){ /*
$("#city_search").keyup(function() { * jQuery Google Map Plugin 0.2.3
var searchText = this.value; * https://wiki.ubuntu.com/ubuntu-django-foundations/map
if (searchText.length < 3) return; * Requires jQuery 1.4.2
$.getJSON("/api/cities/?q="+searchText, function(data) { *
var searchField = $("#city_search")[0]; * Copyright 2011, Ronnie van den Crommenacker
var q = this.url.match(/q=([^&]+)/)[1] * Dual licensed under the MIT or GPL Version 2 licenses.
var c = searchField.value * http://jquery.org/license
if (c != q) return; */
var selectField = $("#city_select"); (function ($) {
selectField.empty(); $.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" var selected = " selected"
selectField.append('<option value="">--------</option>') selectField.append('<option value="">--------</option>')
$.each(data, function(){ $.each(data, function(){
selectField.append('<option value="'+ this.id +'"'+ selected +'>'+ this.display + '</option>'); selectField.append('<option value="'+ this.id +'"'+ selected +'>'+ this.display + '</option>');
selected=""; selected="";
}); });
}); });
}); });
}); });
</script> </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/countries/', event_views.country_list),
path('api/spr/', event_views.spr_list), path('api/spr/', event_views.spr_list),
path('api/cities/', event_views.city_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'), 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>/+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>/+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>/+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('events/<int:event_id>/<str:event_slug>/', views.show_event, name='show-event'),
path('places/', views.places_list, name='places'), 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('+create-place/', views.create_place, name='create-place'),
path('oauth/', include('social_django.urls', namespace='social')), 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 django.http import HttpResponse, JsonResponse
from events.models.profiles import Team, UserProfile, Member 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 from events.models.events import Event, Place, Attendee
@ -52,7 +52,7 @@ def create_event(request, team_id):
form.instance.team = team form.instance.team = team
form.instance.created_by = request.user.profile form.instance.created_by = request.user.profile
new_event = form.save() new_event = form.save()
return redirect(new_event.get_absolute_url()) return redirect('add-place', new_event.id)
else: else:
context = { context = {
'team': team, 'team': team,
@ -62,6 +62,36 @@ def create_event(request, team_id):
else: else:
return redirect('home') 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): def edit_event(request, event_id):
event = Event.objects.get(id=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 django.http import HttpResponse, JsonResponse
from events.models.profiles import Team, UserProfile, Member 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 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) 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): def create_place(request):
if request.method == 'GET': if request.method == 'GET':
form = NewPlaceForm() form = NewPlaceForm()