Rework the new team creation workflow to make it simpler and include a little more instruction. If a user doesn't have a team near them, prompt them to create one instead of showing them an empty homepage. Also impoves some of the instructions around creating a new event. Fixes #55

This commit is contained in:
Michael Hall 2018-04-16 21:52:24 -04:00
parent 7db2bcf356
commit 23ab01b374
14 changed files with 201 additions and 18 deletions

View file

@ -153,7 +153,7 @@ class TeamForm(forms.ModelForm):
class NewTeamForm(forms.ModelForm):
class Meta:
model = Team
fields = ['name', 'description', 'category', 'city', 'web_url', 'tz']
fields = ['name', 'city', 'tz']
widgets = {
'city': Lookup(source=City),
}
@ -162,6 +162,11 @@ class NewTeamForm(forms.ModelForm):
super().__init__(*args, **kwargs)
self.fields['city'].required = True
class TeamDefinitionForm(forms.ModelForm):
class Meta:
model = Team
fields = ['category', 'web_url', 'description']
class DeleteTeamForm(forms.Form):
confirm = forms.BooleanField(label="Yes, delete team", required=True)

View file

@ -1,6 +1,8 @@
from django.utils import timezone
from django.conf import settings
from .models.locale import City
import math
import pytz
import datetime
@ -27,8 +29,8 @@ def get_geoip(request):
client_ip = get_client_ip(request)
if client_ip == '127.0.0.1' or client_ip == 'localhost':
if settings.DEBUG:
client_ip = '8.8.8.8' # Try Google's server
print("Client is localhost, using 8.8.8.8 for geoip instead")
client_ip = getattr(settings, 'DEBUG_IP', '8.8.8.8') # Try Google's server
print("Client is localhost, using %s for geoip instead" % client_ip)
else:
raise Exception("Client is localhost")
@ -47,7 +49,6 @@ def distance(center1, center2):
dlat = (center2[0] - center1[0]) * KM_PER_DEGREE_LAT
dlng = (center2[1] - center1[1]) * (KM_PER_DEGREE_LNG*math.cos(math.radians(avglat)))
dkm = math.sqrt((dlat*dlat) + (dlng*dlng))
print("Distance between %s and %s is %s" % (center1, center2, dkm))
return dkm
def city_distance_from(ll, city):
@ -76,4 +77,17 @@ def searchable_distance_from(ll, searchable):
else:
return 99999
def get_nearest_city(ll, max_distance=100):
city = None
city_distance = 1 #km
while city is None and city_distance <= max_distance:
minlat = ll[0]-(city_distance/KM_PER_DEGREE_LAT)
maxlat = ll[0]+(city_distance/KM_PER_DEGREE_LAT)
minlng = ll[1]-(city_distance/(KM_PER_DEGREE_LNG*math.cos(math.radians(ll[0]))))
maxlng = ll[1]+(city_distance/(KM_PER_DEGREE_LNG*math.cos(math.radians(ll[0]))))
nearby_cities = City.objects.filter(latitude__gte=minlat, latitude__lte=maxlat, longitude__gte=minlng, longitude__lte=maxlng)
if len(nearby_cities) == 0:
city_distance += 1
else:
return sorted(nearby_cities, key=lambda city: city_distance_from(ll, city))[0]
return city

View file

@ -197,7 +197,7 @@ class Team(models.Model):
name = models.CharField(_("Team Name"), max_length=256, null=False, blank=False)
organization = models.ForeignKey(Organization, related_name='teams', null=True, blank=True, on_delete=models.CASCADE)
description = models.TextField(help_text=_('Team Description'), blank=True, null=True)
description = models.TextField(blank=True, null=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
spr = models.ForeignKey(SPR, null=True, blank=True, on_delete=models.CASCADE)

View file

@ -0,0 +1,3 @@
<table>
{{ team_form }}
</table>

View file

@ -0,0 +1,15 @@
<table>
<tr>
<th><label for="id_name">{{ team_form.name.label }}:</label></th>
<td>{{ team_form.name }}</td>
</tr>
<tr><td colspan="2"><hr /></td></tr>
<tr>
<th><label for="id_city">{{ team_form.city.label }}:</label></th>
<td>{{ team_form.city }}</td>
</tr>
<tr>
<th><label for="id_tz">{{ team_form.tz.label }}:</label></th>
<td>{{ team_form.tz }}</td>
</tr>
</table>

View file

@ -21,7 +21,7 @@
<div class="form-group">
{% include "events/event_form.html" %}
<br />
<button type="submit" class="btn btn-primary">Next: Find a Place &gt;</button>
<button type="submit" class="btn btn-primary">Schedule event</button>
</div>
</form>
{% endblock %}

View file

@ -0,0 +1,19 @@
{% extends "get_together/base.html" %}
{% load static %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-12">
<center>
<h3>Tell people more about your team</h3>
<form action="{% url "define-team" team.id %}" method="post" class="form">
{% csrf_token %}
<p>{% include "events/define_team_form.html" %}</p>
<p><button type="submit" class="btn btn-primary">Continue</button></p>
</form>
</center>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,49 @@
{% extends "get_together/base.html" %}
{% load static %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-12">
<center>
<h3>Pick a name and location for your new team</h3>
<form action="{% url "create-team" %}" method="post" class="form">
{% csrf_token %}
<p>{% include "events/new_team_form.html" %}</p>
<p><button type="submit" class="btn btn-primary">Continue</button></p>
</form>
</center>
</div>
</div>
</div>
{% endblock %}
{% block javascript %}
<script src="{% static 'js/jquery-ui-lookup.js' %}"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#city_select").lookup({
search: function(searchText, callback) {
if (searchText.length < 3) return callback(searchText, []);
$.getJSON("/api/cities/?q="+searchText, function(data) {
var m = this.url.match(/q=([^&]+)/);
var q = "";
if (m && m.length > 0) {
q = this.url.match(/q=([^&]+)/)[1]
}
return callback(q, data);
});
},
select: function( event, ui ) {
$("#id_tz").val(ui.data.tz);
$("#id_tz").selectmenu("refresh");
}
})
$("#id_category").selectmenu();
$("#id_tz").selectmenu();
});
</script>
{% endblock %}

View file

@ -12,7 +12,7 @@
{% 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>
<button type="submit" class="btn btn-primary">Save</button>
</form>
<a class="btn btn-secondary" href="{% url 'show-event' event.id event.slug %}">Add a Place later</a>
</div>

View file

@ -2,18 +2,17 @@
{% block content %}
<center>
<h2>Welcome to Get Together!</h2>
<h4>Login</h4>
<h4>Signup or Login to get started</h4>
<!--
<a class="btn btn-primary" href="{% url 'social:begin' 'facebook' %}">Facebook</a><br/><br/>
<a class="btn btn-primary" href="{% url 'social:begin' 'twitter' %}">Twitter</a>&nbsp; &nbsp;
-->
{% if settings.SOCIAL_AUTH_GOOGLE_OAUTH2_KEY %}<span><a class="btn btn-primary" href="{% url 'social:begin' 'google-oauth2' %}">Google</a></span>{% endif %}
{% if settings.SOCIAL_AUTH_FACEBOOK_KEY %}<span><a class="btn btn-primary" href="{% url 'social:begin' 'facebook' %}">Faceboook</a></span>{% endif %}
{% if settings.SOCIAL_AUTH_TWITTER_KEY %}<span><a class="btn btn-primary" href="{% url 'social:begin' 'twitter' %}">Twitter</a></span>{% endif %}
{% if settings.SOCIAL_AUTH_GITHUB_KEY %}<span><a class="btn btn-primary" href="{% url 'social:begin' 'github' %}">GitHub</a></span>{% endif %}
{% if settings.DEBUG %}<span><a class="btn btn-secondary" href="{% url 'admin:login' %}">Local</a></span>{% endif %}
{% if settings.SOCIAL_AUTH_GOOGLE_OAUTH2_KEY %}<span><a class="btn btn-primary" href="{% url 'social:begin' 'google-oauth2' %}?{{ request.META.QUERY_STRING }}">Google</a></span>{% endif %}
{% if settings.SOCIAL_AUTH_FACEBOOK_KEY %}<span><a class="btn btn-primary" href="{% url 'social:begin' 'facebook' %}?{{ request.META.QUERY_STRING }}">Faceboook</a></span>{% endif %}
{% if settings.SOCIAL_AUTH_TWITTER_KEY %}<span><a class="btn btn-primary" href="{% url 'social:begin' 'twitter' %}?{{ request.META.QUERY_STRING }}">Twitter</a></span>{% endif %}
{% if settings.SOCIAL_AUTH_GITHUB_KEY %}<span><a class="btn btn-primary" href="{% url 'social:begin' 'github' %}?{{ request.META.QUERY_STRING }}">GitHub</a></span>{% endif %}
{% if settings.DEBUG %}<span><a class="btn btn-secondary" href="{% url 'admin:login' %}?{{ request.META.QUERY_STRING }}">Local</a></span>{% endif %}
<br>
</center>
{% endblock %}

View file

@ -60,7 +60,8 @@ urlpatterns = [
path('team/<int:team_id>/+delete/', views.delete_team, name='delete-team'),
path('team/<int:team_id>/events.ics', feeds.TeamEventsCalendar(), name='team-event-ical'),
path('+create-team/', views.create_team, name='create-team'),
path('+create-team/', views.start_new_team, name='create-team'),
path('team/<int:team_id>/+define/', views.define_new_team, name='define-team'),
path('team/+create-event/', views.create_event_team_select, name='create-event-team-select'),
path('team/<int:team_id>/+create-event/', views.create_event, name='create-event'),
path('events/<int:event_id>/+edit/', views.edit_event, name='edit-event'),

View file

@ -26,6 +26,7 @@ from .events import *
from .places import *
from .user import *
from .new_user import *
from .new_team import *
from .utils import *
KM_PER_DEGREE_LAT = 110.574
@ -54,7 +55,7 @@ def home(request, *args, **kwards):
else :
context['city_search'] = False
try:
g = get_geoip(request)
g = location.get_geoip(request)
if g.latlng is not None and g.latlng[0] is not None and g.latlng[1] is not None:
ll = g.latlng
context['geoip_lookup'] = True
@ -76,7 +77,7 @@ def home(request, *args, **kwards):
except Exception as err:
context['geoip_lookup'] = False
print("Geocoder lookup failed for %s" % client_ip, err)
print("Geocoder lookup failed for %s" % request.META.get('REMOTE_ADDR'), err)
traceback.print_exc()
#import pdb; pdb.set_trace()
@ -98,6 +99,11 @@ def home(request, *args, **kwards):
near_teams = Team.objects.filter(city__latitude__gte=minlat, city__latitude__lte=maxlat, city__longitude__gte=minlng, city__longitude__lte=maxlng)
context['near_teams'] = sorted(near_teams, key=lambda team: location.team_distance_from(ll, team))
# # If there aren't any teams in the user's geoip area, direct them to start one
if context['geoip_lookup'] and len(near_teams) < 1 and len(near_events) < 1:
messages.add_message(request, messages.INFO, message=_('There are no teams or events yet in your area, be the first to start one!'))
return redirect('create-team')
except Exception as err:
print("Error looking up nearby teams and events", err)
traceback.print_exc()

View file

@ -111,6 +111,7 @@ def create_event(request, team_id):
new_series.save()
new_event.series = new_series
new_event.save()
messages.add_message(request, messages.SUCCESS, message=_('Your event has been scheduled! Next, find a place for your event.'))
return redirect('add-place', new_event.id)
else:
context = {

View file

@ -0,0 +1,71 @@
from django.utils.translation import ugettext_lazy as _
from django.contrib import messages
from django.contrib.auth import logout as logout_user
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponse, JsonResponse
from events.models.profiles import Organization, Team, UserProfile, Member
from events.models.events import Event, CommonEvent, Place, Attendee
from events.forms import TeamForm, NewTeamForm, TeamDefinitionForm
from events import location
import datetime
import simplejson
@login_required
def start_new_team(request, *args, **kwargs):
if request.method == 'GET':
form = NewTeamForm()
g = location.get_geoip(request)
if g.latlng is not None and g.latlng[0] is not None and g.latlng[1] is not None:
city = location.get_nearest_city(g.latlng)
if city:
form.initial={'city': city, 'tz': city.tz}
context = {
'team_form': form,
}
return render(request, 'get_together/new_team/start_new_team.html', context)
elif request.method == 'POST':
form = NewTeamForm(request.POST)
if form.is_valid():
new_team = form.save()
new_team.owner_profile = request.user.profile
new_team.save()
Member.objects.create(team=new_team, user=request.user.profile, role=Member.ADMIN)
return redirect('define-team', team_id=new_team.pk)
else:
context = {
'team_form': form,
}
return render(request, 'get_together/new_team/start_new_team.html', context)
else:
return redirect('home')
def define_new_team(request, team_id):
team = get_object_or_404(Team, id=team_id)
if request.method == 'GET':
form = TeamDefinitionForm(instance=team)
context = {
'team': team,
'team_form': form,
}
return render(request, 'get_together/new_team/define_team.html', context)
elif request.method == 'POST':
form = TeamDefinitionForm(request.POST, instance=team)
if form.is_valid():
form.save()
messages.add_message(request, messages.SUCCESS, message=_('Your new team is ready to go! Now it\'s time to plan your first event.'))
return redirect('create-event', team_id=team.id)
else:
context = {
'team': team,
'team_form': form,
}
return render(request, 'get_together/new_team/define_team.html', context)
else:
return redirect('home')