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:
parent
7db2bcf356
commit
23ab01b374
14 changed files with 201 additions and 18 deletions
|
@ -153,7 +153,7 @@ class TeamForm(forms.ModelForm):
|
||||||
class NewTeamForm(forms.ModelForm):
|
class NewTeamForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Team
|
model = Team
|
||||||
fields = ['name', 'description', 'category', 'city', 'web_url', 'tz']
|
fields = ['name', 'city', 'tz']
|
||||||
widgets = {
|
widgets = {
|
||||||
'city': Lookup(source=City),
|
'city': Lookup(source=City),
|
||||||
}
|
}
|
||||||
|
@ -162,6 +162,11 @@ class NewTeamForm(forms.ModelForm):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['city'].required = True
|
self.fields['city'].required = True
|
||||||
|
|
||||||
|
class TeamDefinitionForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Team
|
||||||
|
fields = ['category', 'web_url', 'description']
|
||||||
|
|
||||||
class DeleteTeamForm(forms.Form):
|
class DeleteTeamForm(forms.Form):
|
||||||
confirm = forms.BooleanField(label="Yes, delete team", required=True)
|
confirm = forms.BooleanField(label="Yes, delete team", required=True)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from .models.locale import City
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import pytz
|
import pytz
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -27,8 +29,8 @@ def get_geoip(request):
|
||||||
client_ip = get_client_ip(request)
|
client_ip = get_client_ip(request)
|
||||||
if client_ip == '127.0.0.1' or client_ip == 'localhost':
|
if client_ip == '127.0.0.1' or client_ip == 'localhost':
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
client_ip = '8.8.8.8' # Try Google's server
|
client_ip = getattr(settings, 'DEBUG_IP', '8.8.8.8') # Try Google's server
|
||||||
print("Client is localhost, using 8.8.8.8 for geoip instead")
|
print("Client is localhost, using %s for geoip instead" % client_ip)
|
||||||
else:
|
else:
|
||||||
raise Exception("Client is localhost")
|
raise Exception("Client is localhost")
|
||||||
|
|
||||||
|
@ -47,7 +49,6 @@ def distance(center1, center2):
|
||||||
dlat = (center2[0] - center1[0]) * KM_PER_DEGREE_LAT
|
dlat = (center2[0] - center1[0]) * KM_PER_DEGREE_LAT
|
||||||
dlng = (center2[1] - center1[1]) * (KM_PER_DEGREE_LNG*math.cos(math.radians(avglat)))
|
dlng = (center2[1] - center1[1]) * (KM_PER_DEGREE_LNG*math.cos(math.radians(avglat)))
|
||||||
dkm = math.sqrt((dlat*dlat) + (dlng*dlng))
|
dkm = math.sqrt((dlat*dlat) + (dlng*dlng))
|
||||||
print("Distance between %s and %s is %s" % (center1, center2, dkm))
|
|
||||||
return dkm
|
return dkm
|
||||||
|
|
||||||
def city_distance_from(ll, city):
|
def city_distance_from(ll, city):
|
||||||
|
@ -76,4 +77,17 @@ def searchable_distance_from(ll, searchable):
|
||||||
else:
|
else:
|
||||||
return 99999
|
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
|
||||||
|
|
|
@ -197,7 +197,7 @@ class Team(models.Model):
|
||||||
name = models.CharField(_("Team Name"), max_length=256, null=False, blank=False)
|
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)
|
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)
|
country = models.ForeignKey(Country, on_delete=models.CASCADE)
|
||||||
spr = models.ForeignKey(SPR, null=True, blank=True, on_delete=models.CASCADE)
|
spr = models.ForeignKey(SPR, null=True, blank=True, on_delete=models.CASCADE)
|
||||||
|
|
3
events/templates/events/define_team_form.html
Normal file
3
events/templates/events/define_team_form.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<table>
|
||||||
|
{{ team_form }}
|
||||||
|
</table>
|
15
events/templates/events/new_team_form.html
Normal file
15
events/templates/events/new_team_form.html
Normal 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>
|
|
@ -21,7 +21,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">Next: Find a Place ></button>
|
<button type="submit" class="btn btn-primary">Schedule event</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -12,7 +12,7 @@
|
||||||
{% include "events/place_form.html" %}
|
{% include "events/place_form.html" %}
|
||||||
<div class="fluid-layout" id="place-map"></div>
|
<div class="fluid-layout" id="place-map"></div>
|
||||||
<br />
|
<br />
|
||||||
<button type="submit" class="btn btn-primary">Announce your Get Together!</button>
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
</form>
|
</form>
|
||||||
<a class="btn btn-secondary" href="{% url 'show-event' event.id event.slug %}">Add a Place later</a>
|
<a class="btn btn-secondary" href="{% url 'show-event' event.id event.slug %}">Add a Place later</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,18 +2,17 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<center>
|
<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' 'facebook' %}">Facebook</a><br/><br/>
|
||||||
<a class="btn btn-primary" href="{% url 'social:begin' 'twitter' %}">Twitter</a>
|
<a class="btn btn-primary" href="{% url 'social:begin' 'twitter' %}">Twitter</a>
|
||||||
-->
|
-->
|
||||||
{% 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_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' %}">Faceboook</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' %}">Twitter</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' %}">GitHub</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' %}">Local</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>
|
<br>
|
||||||
</center>
|
</center>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -60,7 +60,8 @@ urlpatterns = [
|
||||||
path('team/<int:team_id>/+delete/', views.delete_team, name='delete-team'),
|
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('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/+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('team/<int:team_id>/+create-event/', views.create_event, name='create-event'),
|
||||||
path('events/<int:event_id>/+edit/', views.edit_event, name='edit-event'),
|
path('events/<int:event_id>/+edit/', views.edit_event, name='edit-event'),
|
||||||
|
|
|
@ -26,6 +26,7 @@ from .events import *
|
||||||
from .places import *
|
from .places import *
|
||||||
from .user import *
|
from .user import *
|
||||||
from .new_user import *
|
from .new_user import *
|
||||||
|
from .new_team import *
|
||||||
from .utils import *
|
from .utils import *
|
||||||
|
|
||||||
KM_PER_DEGREE_LAT = 110.574
|
KM_PER_DEGREE_LAT = 110.574
|
||||||
|
@ -54,7 +55,7 @@ def home(request, *args, **kwards):
|
||||||
else :
|
else :
|
||||||
context['city_search'] = False
|
context['city_search'] = False
|
||||||
try:
|
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:
|
if g.latlng is not None and g.latlng[0] is not None and g.latlng[1] is not None:
|
||||||
ll = g.latlng
|
ll = g.latlng
|
||||||
context['geoip_lookup'] = True
|
context['geoip_lookup'] = True
|
||||||
|
@ -76,7 +77,7 @@ def home(request, *args, **kwards):
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
context['geoip_lookup'] = False
|
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()
|
traceback.print_exc()
|
||||||
|
|
||||||
#import pdb; pdb.set_trace()
|
#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)
|
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))
|
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:
|
except Exception as err:
|
||||||
print("Error looking up nearby teams and events", err)
|
print("Error looking up nearby teams and events", err)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
|
@ -111,6 +111,7 @@ def create_event(request, team_id):
|
||||||
new_series.save()
|
new_series.save()
|
||||||
new_event.series = new_series
|
new_event.series = new_series
|
||||||
new_event.save()
|
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)
|
return redirect('add-place', new_event.id)
|
||||||
else:
|
else:
|
||||||
context = {
|
context = {
|
||||||
|
|
71
get_together/views/new_team.py
Normal file
71
get_together/views/new_team.py
Normal 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')
|
||||||
|
|
Loading…
Reference in a new issue