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 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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
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">
|
||||
{% include "events/event_form.html" %}
|
||||
<br />
|
||||
<button type="submit" class="btn btn-primary">Next: Find a Place ></button>
|
||||
<button type="submit" class="btn btn-primary">Schedule event</button>
|
||||
</div>
|
||||
</form>
|
||||
{% 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" %}
|
||||
<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>
|
||||
|
|
|
@ -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>
|
||||
-->
|
||||
{% 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 %}
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 = {
|
||||
|
|
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