diff --git a/accounts/admin.py b/accounts/admin.py
index 3df2525..aa47805 100644
--- a/accounts/admin.py
+++ b/accounts/admin.py
@@ -5,7 +5,8 @@ from .models import Account, Badge, BadgeGrant, EmailConfirmation
# Register your models here.
class AccountAdmin(admin.ModelAdmin):
- list_display = ('user', 'acctname', 'email', 'is_email_confirmed')
+ list_display = ('user', 'acctname', 'email', 'is_email_confirmed', 'has_completed_setup')
+ list_filter = ('is_email_confirmed', 'has_completed_setup')
def email(self, obj):
return obj.user.email
email.short_description = 'Email'
diff --git a/accounts/decorators.py b/accounts/decorators.py
new file mode 100644
index 0000000..1140f00
--- /dev/null
+++ b/accounts/decorators.py
@@ -0,0 +1,27 @@
+from functools import wraps
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.views import redirect_to_login
+from django.contrib.auth import REDIRECT_FIELD_NAME
+
+from django.shortcuts import render, redirect, resolve_url
+from django.conf import settings
+
+from .models import Account
+
+def setup_wanted(view_func, setup_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
+ """
+ Decorator for views that checks that the user has completed the setup
+ process, redirecting to settings.SETUP_URL if required
+ """
+ @wraps(view_func)
+ def wrap(request, *args, **kwargs):
+ if not request.user.is_authenticated or request.user.account.has_completed_setup:
+ return view_func(request, *args, **kwargs)
+ else:
+ resolved_setup_url = resolve_url(setup_url or settings.SETUP_URL)
+ path = request.get_full_path()
+ return redirect_to_login(
+ path, resolved_setup_url, redirect_field_name)
+ return wrap
+
+setup_required = login_required(setup_wanted)
diff --git a/accounts/migrations/0003_auto_20180304_1649.py b/accounts/migrations/0003_auto_20180304_1649.py
new file mode 100644
index 0000000..580fdf6
--- /dev/null
+++ b/accounts/migrations/0003_auto_20180304_1649.py
@@ -0,0 +1,23 @@
+# Generated by Django 2.0 on 2018-03-04 16:49
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('accounts', '0002_auto_20180226_1532'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='account',
+ name='has_completed_setup',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='account',
+ name='setup_completed_date',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ ]
diff --git a/accounts/models.py b/accounts/models.py
index a1a6e82..5476e09 100644
--- a/accounts/models.py
+++ b/accounts/models.py
@@ -16,11 +16,19 @@ class Account(models.Model):
acctname = models.CharField(_("Account Name"), max_length=150, blank=True)
is_email_confirmed = models.BooleanField(default=False)
+ has_completed_setup = models.BooleanField(default=False)
+ setup_completed_date = models.DateTimeField(blank=True, null=True)
+
badges = models.ManyToManyField('Badge', through='BadgeGrant')
class Meta:
ordering = ('user__username',)
+ def setup_complete(self):
+ self.has_completed_setup = True
+ self.setup_completed_date = datetime.datetime.now()
+ self.save()
+
def new_confirmation_request(self):
valid_for = getattr(settings, 'EMAIL_CONFIRMAION_EXPIRATION_DAYS', 5)
confirmation_key=get_random_string(length=32)
diff --git a/events/admin.py b/events/admin.py
index a5d0ab4..b5416c8 100644
--- a/events/admin.py
+++ b/events/admin.py
@@ -59,8 +59,15 @@ class EventAdmin(admin.ModelAdmin):
attendee_count.short_description = 'Attendees'
admin.site.register(Event, EventAdmin)
-admin.site.register(Member)
-admin.site.register(Attendee)
+class MemberAdmin(admin.ModelAdmin):
+ list_display = ('__str__', 'role')
+ list_filter = ('role', 'team')
+admin.site.register(Member, MemberAdmin)
+
+class AttendeeAdmin(admin.ModelAdmin):
+ list_display = ('__str__', 'role', 'status')
+ list_filter = ('role', 'status')
+admin.site.register(Attendee, AttendeeAdmin)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'image')
diff --git a/events/forms.py b/events/forms.py
index 11fa813..4ea1808 100644
--- a/events/forms.py
+++ b/events/forms.py
@@ -220,6 +220,11 @@ class UserProfileForm(forms.ModelForm):
'send_notifications': _('Send me notification emails'),
}
+class ConfirmProfileForm(forms.ModelForm):
+ class Meta:
+ model = UserProfile
+ fields = ['realname', 'tz']
+
class SendNotificationsForm(forms.ModelForm):
class Meta:
model = UserProfile
diff --git a/events/migrations/0013_auto_20180304_1649.py b/events/migrations/0013_auto_20180304_1649.py
new file mode 100644
index 0000000..62cc865
--- /dev/null
+++ b/events/migrations/0013_auto_20180304_1649.py
@@ -0,0 +1,23 @@
+# Generated by Django 2.0 on 2018-03-04 16:49
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('events', '0012_auto_20180227_0358'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='userprofile',
+ name='categories',
+ field=models.ManyToManyField(blank=True, to='events.Category'),
+ ),
+ migrations.AddField(
+ model_name='userprofile',
+ name='topics',
+ field=models.ManyToManyField(blank=True, to='events.Topic'),
+ ),
+ ]
diff --git a/events/models/profiles.py b/events/models/profiles.py
index 7e0867d..dee5a85 100644
--- a/events/models/profiles.py
+++ b/events/models/profiles.py
@@ -14,7 +14,7 @@ class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
realname = models.CharField(verbose_name=_("Real Name"), max_length=150, blank=True)
- tz = models.CharField(max_length=32, verbose_name=_('Timezone'), default='UTC', choices=[(tz, tz) for tz in pytz.all_timezones], blank=False, null=False, help_text=_('The most commonly used timezone for this User.'))
+ tz = models.CharField(max_length=32, verbose_name=_('Timezone'), default='UTC', choices=[(tz, tz) for tz in pytz.all_timezones], blank=False, null=False)
avatar = models.URLField(verbose_name=_("Photo Image"), max_length=150, blank=True, null=True)
web_url = models.URLField(verbose_name=_('Website URL'), blank=True, null=True)
@@ -23,6 +23,9 @@ class UserProfile(models.Model):
send_notifications = models.BooleanField(verbose_name=_('Send notification emails'), default=True)
+ categories = models.ManyToManyField('Category', blank=True)
+ topics = models.ManyToManyField('Topic', blank=True)
+
class Meta:
ordering = ('user__username',)
diff --git a/get_together/settings.py b/get_together/settings.py
index 425564b..232f771 100644
--- a/get_together/settings.py
+++ b/get_together/settings.py
@@ -51,6 +51,7 @@ INSTALLED_APPS = [
LOGIN_URL = 'login'
LOGOUT_URL = 'logout'
+SETUP_URL = 'profile/+confirm_profile'
LOGIN_REDIRECT_URL = 'home'
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
diff --git a/get_together/templates/get_together/users/bad_email_confirmation.html b/get_together/templates/get_together/new_user/bad_email_confirmation.html
similarity index 100%
rename from get_together/templates/get_together/users/bad_email_confirmation.html
rename to get_together/templates/get_together/new_user/bad_email_confirmation.html
diff --git a/get_together/templates/get_together/users/confirm_notifications.html b/get_together/templates/get_together/new_user/confirm_notifications.html
similarity index 100%
rename from get_together/templates/get_together/users/confirm_notifications.html
rename to get_together/templates/get_together/new_user/confirm_notifications.html
diff --git a/get_together/templates/get_together/users/sent_email_confirmation.html b/get_together/templates/get_together/new_user/sent_email_confirmation.html
similarity index 100%
rename from get_together/templates/get_together/users/sent_email_confirmation.html
rename to get_together/templates/get_together/new_user/sent_email_confirmation.html
diff --git a/get_together/templates/get_together/new_user/setup_1_confirm_profile.html b/get_together/templates/get_together/new_user/setup_1_confirm_profile.html
new file mode 100644
index 0000000..edade4a
--- /dev/null
+++ b/get_together/templates/get_together/new_user/setup_1_confirm_profile.html
@@ -0,0 +1,30 @@
+{% extends "get_together/base.html" %}
+
+{% block content %}
+
+
+
+
+
+ Please confirm your profile information
+
+
+
+
+
+
+{% endblock %}
+
+{% block javascript %}
+
+
+{% endblock %}
diff --git a/get_together/templates/get_together/new_user/setup_2_pick_categories.html b/get_together/templates/get_together/new_user/setup_2_pick_categories.html
new file mode 100644
index 0000000..26ab9ed
--- /dev/null
+++ b/get_together/templates/get_together/new_user/setup_2_pick_categories.html
@@ -0,0 +1,52 @@
+{% extends "get_together/base.html" %}
+{% load static %}
+
+{% block styles %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+ Tell us what kinds of events interest you
+
+
+
+
+
+{% endblock %}
+
+
diff --git a/get_together/templates/get_together/new_user/setup_3_find_teams.html b/get_together/templates/get_together/new_user/setup_3_find_teams.html
new file mode 100644
index 0000000..4e3c86a
--- /dev/null
+++ b/get_together/templates/get_together/new_user/setup_3_find_teams.html
@@ -0,0 +1,55 @@
+{% extends "get_together/base.html" %}
+{% load static %}
+
+{% block styles %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+ Here are some nearby teams you might want to join
+
+
+
+
+
+{% endblock %}
+
diff --git a/get_together/templates/get_together/new_user/setup_4_attend_events.html b/get_together/templates/get_together/new_user/setup_4_attend_events.html
new file mode 100644
index 0000000..00275b7
--- /dev/null
+++ b/get_together/templates/get_together/new_user/setup_4_attend_events.html
@@ -0,0 +1,55 @@
+{% extends "get_together/base.html" %}
+{% load static %}
+
+{% block styles %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+ Now pick some events that you'd like to attend
+
+
+
+
+
+{% endblock %}
+
diff --git a/get_together/urls.py b/get_together/urls.py
index 7710256..21b0b88 100644
--- a/get_together/urls.py
+++ b/get_together/urls.py
@@ -35,6 +35,12 @@ urlpatterns = [
path('api/cities/', event_views.city_list),
path('api/find_city/', event_views.find_city),
+ path('profile/+confirm_profile', views.setup_1_confirm_profile, name='setup-1-confirm-profile'),
+ path('profile/+pick_categories', views.setup_2_pick_categories, name='setup-2-pick-categories'),
+ path('profile/+find_teams', views.setup_3_find_teams, name='setup-3-find-teams'),
+ path('profile/+attend_events', views.setup_4_attend_events, name='setup-4-attend-events'),
+ path('profile/+setup_complete', views.setup_complete, name='setup-complete'),
+
path('profile/+edit', views.edit_profile, name='edit-profile'),
path('profile/+send_confirmation_email', views.user_send_confirmation_email, name='send-confirm-email'),
path('profile/+confirm_email/', views.user_confirm_email, name='confirm-email'),
diff --git a/get_together/views/__init__.py b/get_together/views/__init__.py
index a841d27..1793320 100644
--- a/get_together/views/__init__.py
+++ b/get_together/views/__init__.py
@@ -11,6 +11,7 @@ from events.models.profiles import Team, UserProfile, Member
from events.models.search import Searchable
from events.forms import SearchForm
+from accounts.decorators import setup_wanted
from django.conf import settings
import datetime
@@ -24,12 +25,14 @@ from .events import *
from .places import *
from .user import *
from .new_user import *
+from .utils import *
KM_PER_DEGREE_LAT = 110.574
KM_PER_DEGREE_LNG = 111.320 # At the equator
DEFAULT_NEAR_DISTANCE = 100 # kilometeres
# Create your views here.
+@setup_wanted
def home(request, *args, **kwards):
context = {}
if request.user.is_authenticated:
@@ -49,14 +52,7 @@ def home(request, *args, **kwards):
else :
context['city_search'] = False
try:
- 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
- else:
- raise Exception("Client is localhost")
-
- g = geocoder.ip(client_ip)
+ g = 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
@@ -92,11 +88,3 @@ def home(request, *args, **kwards):
context['search_form'] = search_form
return render(request, 'get_together/index.html', context)
-
-def get_client_ip(request):
- x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
- if x_forwarded_for:
- ip = x_forwarded_for.split(',')[0]
- else:
- ip = request.META.get('REMOTE_ADDR')
- return ip
diff --git a/get_together/views/new_user.py b/get_together/views/new_user.py
index 43f2a78..30d0a2c 100644
--- a/get_together/views/new_user.py
+++ b/get_together/views/new_user.py
@@ -9,21 +9,126 @@ from django.core.mail import send_mail
from django.template.loader import get_template, render_to_string
from django.conf import settings
-from events.models.profiles import Team, UserProfile, Member
+from events.models.profiles import Team, UserProfile, Member, Category
from events.models.events import Event, Place, Attendee
-from events.forms import SendNotificationsForm
+from events.forms import SendNotificationsForm, UserForm, ConfirmProfileForm
+
+from .utils import get_nearby_teams
import datetime
import simplejson
-def new_user_confirm_profile(request):
- pass
+@login_required
+def setup_1_confirm_profile(request):
+ user = request.user
+ profile = request.user.profile
-def new_user_find_teams(request):
- pass
+ if request.method == 'GET':
+ user_form = UserForm(instance=user)
+ profile_form = ConfirmProfileForm(instance=profile)
+ context = {
+ 'user': user,
+ 'profile': profile,
+ 'user_form': user_form,
+ 'profile_form': profile_form,
+ }
+ return render(request, 'get_together/new_user/setup_1_confirm_profile.html', context)
+ elif request.method == 'POST':
+ user_form = UserForm(request.POST, instance=user)
+ profile_form = ConfirmProfileForm(request.POST, instance=profile)
+ if user_form.is_valid() and profile_form.is_valid():
+ saved_user = user_form.save()
+ profile_form.save()
+ if saved_user.email is not None and saved_user.email != '' and not saved_user.account.is_email_confirmed:
+ # Call the view to trigger sending a confirmation email, but ignore it's response
+ user_send_confirmation_email(request)
+ return redirect('setup-2-pick-categories')
+ else:
+ return redirect('home')
-def new_user_find_events(request):
- pass
+
+@login_required
+def setup_2_pick_categories(request):
+ user = request.user
+ profile = request.user.profile
+
+ if request.method == 'GET':
+ categories = Category.objects.all()
+ context = {
+ 'user': user,
+ 'profile': profile,
+ 'categories': categories,
+ }
+ return render(request, 'get_together/new_user/setup_2_pick_categories.html', context)
+ elif request.method == 'POST':
+ for entry in request.POST:
+ if entry.startswith('category_'):
+ category_id = entry.split('_')[1]
+ try:
+ profile.categories.add(category_id)
+ except:
+ pass
+ return redirect('setup-3-find-teams')
+ else:
+ return redirect('home')
+
+@login_required
+def setup_3_find_teams(request):
+ user = request.user
+ profile = request.user.profile
+ if request.method == 'GET':
+ teams = get_nearby_teams(request)
+ if (teams.count() < 1):
+ return redirect('setup-complete')
+ context = {
+ 'user': user,
+ 'profile': profile,
+ 'teams': teams,
+ }
+ return render(request, 'get_together/new_user/setup_3_find_teams.html', context)
+ elif request.method == 'POST':
+ for entry in request.POST:
+ if entry.startswith('team_'):
+ team_id = entry.split('_')[1]
+ try:
+ Member.objects.get_or_create(team_id=team_id, user=profile, defaults={'role': Member.NORMAL})
+ except Member.MultipleObjectsReturned:
+ pass
+ return redirect('setup-4-attend-events')
+ else:
+ return redirect('home')
+
+@login_required
+def setup_4_attend_events(request):
+ user = request.user
+ profile = request.user.profile
+ if request.method == 'GET':
+ events = Event.objects.filter(team__in=profile.memberships.all(), end_time__gte=datetime.datetime.now())
+ if (events.count() < 1):
+ return redirect('setup-complete')
+ context = {
+ 'user': user,
+ 'profile': profile,
+ 'events': events,
+ }
+ return render(request, 'get_together/new_user/setup_4_attend_events.html', context)
+ elif request.method == 'POST':
+ for entry in request.POST:
+ if entry.startswith('event_'):
+ event_id = entry.split('_')[1]
+ try:
+ Attendee.objects.get_or_create(event_id=event_id, user=profile, defaults={'role': Attendee.NORMAL, 'status': Attendee.YES})
+ except Attendee.MultipleObjectsReturned:
+ pass
+ return redirect('setup-complete')
+ else:
+ return redirect('home')
+
+@login_required
+def setup_complete(request):
+ messages.add_message(request, messages.SUCCESS, message=_('Your setup is complete, welcome to GetTogether!'))
+ request.user.account.setup_complete()
+ return redirect('home')
# These views are for confirming a user's email address before sending them mail
@login_required
@@ -48,7 +153,7 @@ def user_send_confirmation_email(request):
recipient_list=email_recipients,
html_message=email_body_html
)
- return render(request, 'get_together/users/sent_email_confirmation.html', context)
+ return render(request, 'get_together/new_user/sent_email_confirmation.html', context)
@login_required
def user_confirm_email(request, confirmation_key):
@@ -56,7 +161,7 @@ def user_confirm_email(request, confirmation_key):
messages.add_message(request, messages.SUCCESS, message=_('Your email address has been confirmed.'))
return redirect('confirm-notifications')
else:
- return render(request, 'get_together/users/bad_email_confirmation.html')
+ return render(request, 'get_together/new_user/bad_email_confirmation.html')
@login_required
def user_confirm_notifications(request):
@@ -65,7 +170,7 @@ def user_confirm_notifications(request):
context = {
'notifications_form': form
}
- return render(request, 'get_together/users/confirm_notifications.html', context)
+ return render(request, 'get_together/new_user/confirm_notifications.html', context)
elif request.method == 'POST':
form = SendNotificationsForm(request.POST, instance=request.user.profile)
if form.is_valid():
diff --git a/get_together/views/utils.py b/get_together/views/utils.py
new file mode 100644
index 0000000..bd18090
--- /dev/null
+++ b/get_together/views/utils.py
@@ -0,0 +1,68 @@
+from django.utils.translation import ugettext_lazy as _
+
+from django.contrib import messages
+from django.contrib.auth import logout as logout_user
+from django.shortcuts import render, redirect
+from django.http import HttpResponse, JsonResponse
+
+from events.models.locale import City
+from events.models.events import Event, Place, Attendee
+from events.models.profiles import Team, UserProfile, Member
+from events.models.search import Searchable
+from events.forms import SearchForm
+
+from accounts.decorators import setup_wanted
+from django.conf import settings
+
+import datetime
+import simplejson
+import geocoder
+import math
+import traceback
+
+from .teams import *
+from .events import *
+from .places import *
+from .user import *
+from .new_user import *
+
+KM_PER_DEGREE_LAT = 110.574
+KM_PER_DEGREE_LNG = 111.320 # At the equator
+DEFAULT_NEAR_DISTANCE = 100 # kilometeres
+
+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")
+ else:
+ raise Exception("Client is localhost")
+
+ g = geocoder.ip(client_ip)
+ return g
+
+def get_nearby_teams(request, near_distance=DEFAULT_NEAR_DISTANCE):
+ g = get_geoip(request)
+ if g.latlng is None or g.latlng[0] is None or g.latlng[1] is None:
+ print("Could not identify latlng from geoip")
+ return Team.objects.none()
+ try:
+ minlat = g.latlng[0]-(near_distance/KM_PER_DEGREE_LAT)
+ maxlat = g.latlng[0]+(near_distance/KM_PER_DEGREE_LAT)
+ minlng = g.latlng[1]-(near_distance/(KM_PER_DEGREE_LNG*math.cos(math.radians(g.latlng[0]))))
+ maxlng = g.latlng[1]+(near_distance/(KM_PER_DEGREE_LNG*math.cos(math.radians(g.latlng[0]))))
+
+ near_teams = Team.objects.filter(city__latitude__gte=minlat, city__latitude__lte=maxlat, city__longitude__gte=minlng, city__longitude__lte=maxlng)
+ return near_teams
+ except Exception as e:
+ print("Error looking for local teams: ", e)
+ return Team.objects.none()
+
+def get_client_ip(request):
+ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
+ if x_forwarded_for:
+ ip = x_forwarded_for.split(',')[0]
+ else:
+ ip = request.META.get('REMOTE_ADDR')
+ return ip