Skip to content

Commit

Permalink
Emails and registration complete
Browse files Browse the repository at this point in the history
  • Loading branch information
Casassarnau committed May 20, 2020
1 parent 24bfc15 commit 11bbd0b
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 37 deletions.
45 changes: 35 additions & 10 deletions applications/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,12 +473,43 @@ class Meta(_BaseApplicationForm.Meta):
}


class SponsorForm(_BaseApplicationForm):
class SponsorForm(OverwriteOnlyModelFormMixin, BetterModelForm):
phone_number = forms.CharField(required=False, widget=forms.TextInput(
attrs={'class': 'form-control', 'placeholder': '+#########'}))
code_conduct = forms.BooleanField(required=False,
label='I have read and accept the '
'<a href="%s" target="_blank">%s Code of Conduct</a>' % (
getattr(settings, 'CODE_CONDUCT_LINK', '/code_conduct'),
settings.HACKATHON_NAME), )

def clean_code_conduct(self):
cc = self.cleaned_data.get('code_conduct', False)
# Check that if it's the first submission hackers checks code of conduct checkbox
# self.instance.pk is None if there's no Application existing before
# https://stackoverflow.com/questions/9704067/test-if-django-modelform-has-instance
if not cc and not self.instance.pk:
raise forms.ValidationError(
"To attend %s you must abide by our code of conduct" % settings.HACKATHON_NAME)
return cc

def clean_other_diet(self):
data = self.cleaned_data['other_diet']
diet = self.cleaned_data['diet']
if diet == 'Others' and not data:
raise forms.ValidationError("Please tell us your specific dietary requirements")
return data

def clean_other_gender(self):
data = self.cleaned_data['other_gender']
gender = self.cleaned_data['gender']
if gender == models.GENDER_OTHER and not data:
raise forms.ValidationError("Please enter this field or select 'Prefer not to answer'")
return data

def fieldsets(self):
self._fieldsets = [
('Personal Info',
{'fields': ('phone_number', 'tshirt_size', 'diet', 'other_diet', 'position', 'attendance'),
{'fields': ('name', 'phone_number', 'tshirt_size', 'diet', 'other_diet', 'position', 'attendance'),
'description': 'Hey there, before we begin we would like to know a little more about you.', }),
]
# Fields that we only need the first time the hacker fills the application
Expand All @@ -487,14 +518,7 @@ def fieldsets(self):
self._fieldsets.append(('Code of Conduct', {'fields': ('code_conduct',)}))
return super(SponsorForm, self).fieldsets

def save(self, commit=True):
application = super(SponsorForm, self).save(commit=False)
application.confirm()
if commit:
application.save()
return application

class Meta(_BaseApplicationForm.Meta):
class Meta:
model = models.SponsorApplication
help_texts = {
'other_diet': 'Please fill here in your dietary requirements. We want to make sure we have food for you!',
Expand All @@ -505,3 +529,4 @@ class Meta(_BaseApplicationForm.Meta):
'attendance': 'What availability will you have during the event?',
'position': 'What is your job position?',
}
exclude = ['user', 'uuid', 'submission_date', 'status_update_date', 'status', ]
28 changes: 28 additions & 0 deletions applications/migrations/0028_auto_20200520_0335.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.28 on 2020-05-20 10:35
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('applications', '0027_auto_20200519_1110'),
]

operations = [
migrations.AddField(
model_name='sponsorapplication',
name='id',
field=models.AutoField(auto_created=True, default=1, primary_key=True, serialize=False, verbose_name='ID'),
preserve_default=False,
),
migrations.AlterField(
model_name='sponsorapplication',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sponsorapplication_application', to=settings.AUTH_USER_MODEL),
),
]
20 changes: 20 additions & 0 deletions applications/migrations/0029_auto_20200520_0408.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.28 on 2020-05-20 11:08
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('applications', '0028_auto_20200520_0335'),
]

operations = [
migrations.AlterField(
model_name='sponsorapplication',
name='status',
field=models.CharField(choices=[('P', 'Under review'), ('R', 'Wait listed'), ('I', 'Invited'), ('LR', 'Last reminder'), ('C', 'Confirmed'), ('X', 'Cancelled'), ('A', 'Attended'), ('E', 'Expired'), ('D', 'Dubious'), ('IV', 'Invalid')], default='C', max_length=2),
),
]
16 changes: 10 additions & 6 deletions applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,10 @@ class SponsorApplication(
# When was the last status update
status_update_date = models.DateTimeField(blank=True, null=True)
uuid = models.UUIDField(default=uuid.uuid4, editable=False)
user = models.OneToOneField(User, related_name='%(class)s_application', primary_key=True)
user = models.ForeignKey(User, related_name='%(class)s_application')
# Application status
status = models.CharField(choices=STATUS,
default=APP_PENDING,
default=APP_CONFIRMED,
max_length=2)
phone_number = models.CharField(blank=True, null=True, max_length=16,
validators=[RegexValidator(regex=r'^\+?1?\d{9,15}$',
Expand All @@ -463,11 +463,15 @@ class SponsorApplication(
tshirt_size = models.CharField(max_length=5, default=DEFAULT_TSHIRT_SIZE, choices=TSHIRT_SIZES)
position = models.CharField(max_length=50, null=False)

def confirm(self):
self.status = APP_CONFIRMED
def __str__(self):
return self.name + ' from ' + self.user.name

def can_be_edit(self):
return True
def save(self, **kwargs):
self.status_update_date = timezone.now()
super(SponsorApplication, self).save(**kwargs)

class META:
unique_together = [['name', 'user']]


class DraftApplication(models.Model):
Expand Down
13 changes: 13 additions & 0 deletions applications/templates/sponsor_submited.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends 'base_tabs.html' %}

{% load bootstrap3 %}
{% block head_title %}Application{% endblock %}

{% block panel %}

<div>
<h1 style="text-align: center">Thank you!</h1>
<p style="text-align: center">You have registered to {{ h_name }}.</p>
</div>

{% endblock %}
3 changes: 2 additions & 1 deletion applications/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
url(r'^dashboard/$', views.HackerDashboard.as_view(), name='dashboard'),
url(r'^application/$', views.HackerApplication.as_view(), name='application'),
url(r'^application/draft/$', views.save_draft, name='save_draft'),
url(r'^sponsor/$', views.SponsorApplicationView.as_view(), name='sponsor_app'),
url(r'^sponsor/(?P<uid>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
views.SponsorApplicationView.as_view(), name='sponsor_app'),
]
48 changes: 35 additions & 13 deletions applications/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.exceptions import ValidationError
from django.http import Http404, HttpResponseRedirect, JsonResponse
from django.shortcuts import render, get_object_or_404
from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from django.utils.encoding import force_text
from django.utils.http import urlsafe_base64_decode
from django.views import View

from app import slack
Expand All @@ -19,8 +21,7 @@
from app.views import TabsView
from applications import models, emails, forms
from user.mixins import IsHackerMixin, is_hacker
from user import models as userModels

from user import models as userModels, tokens

VIEW_APPLICATION_TYPE = {
userModels.USR_HACKER: models.HackerApplication,
Expand Down Expand Up @@ -230,18 +231,39 @@ def get_context_data(self, **kwargs):

return context

def get(self, request, *args, **kwargs):
try:
uid = force_text(urlsafe_base64_decode(self.kwargs.get('uid', None)))
user = userModels.User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, userModels.User.DoesNotExist):
raise Http404('Invalid url')

token = self.kwargs.get('token', None)
if not tokens.password_reset_token.check_token(user, token) and user.is_sponsor():
raise Http404('Invalid url')
return super(SponsorApplicationView, self).get(request, *args, **kwargs)

def post(self, request, *args, **kwargs):
form = forms.SponsorForm(request.POST, request.FILES)
if form.is_valid():
application = form.save(commit=False)
application.user = request.user
application.save()
messages.success(request, 'We have now received your application. ')
return HttpResponseRedirect(reverse('root'))
else:
c = self.get_context_data()
c.update({'form': form})
return render(request, self.template_name, c)
try:
uid = force_text(urlsafe_base64_decode(self.kwargs.get('uid', None)))
user = userModels.User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, userModels.User.DoesNotExist):
return Http404('How did you get here?')
if form.is_valid() and user.is_sponsor():
name = form.cleaned_data['name']
app = models.SponsorApplication.objects.filter(user=user, name=name).first()
if app:
form.add_error('name', 'This name is already taken. Have you applied?')
else:
application = form.save(commit=False)
application.user = user
application.save()
messages.success(request, 'We have now received your application. ')
return render(request, 'sponsor_submited.html')
c = self.get_context_data()
c.update({'form': form})
return render(request, self.template_name, c)


@is_hacker
Expand Down
11 changes: 11 additions & 0 deletions user/emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,14 @@ def create_password_reset_email(user, reset_url):
}
return emails.render_mail('mails/password_reset',
user.email, c)


def create_sponsor_link_email(user, user_sponsor_url, app_sponsor_url, sponsor_name):
c = {
'user': user,
'user_sponsor_url': user_sponsor_url,
'app_sponsor_url': app_sponsor_url,
'sponsor_name': sponsor_name,
}
return emails.render_mail('mails/sponsor_link',
user.email, c)
3 changes: 2 additions & 1 deletion user/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def create_sponsor(self, email, name, password=None, n_max=1):
email=email,
name=name,
type=USR_SPONSOR,
max_applications=n_max
max_applications=n_max,
email_verified=True,
)

user.set_password(password)
Expand Down
27 changes: 27 additions & 0 deletions user/templates/mails/sponsor_link_message.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{% extends 'base_email.html' %}
{% block preheader %}New Sponsor: {{ sponsor_name }}{% endblock %}
{% block content %}
<p>
Hi {{ user.name }},
thanks for creating a new Sponsor User! Here you have a few useful links:
Password reset generator link for {{ sponsor_name }}:
</p>
{% include 'mails/include/email_button.html' with url=user_sponsor_url text='Reset password' %}
<p>
If clicking the link above doesn't work, please copy and paste the URL in a new browser
window instead.
</p>
<p style="text-align: center;">{{ user_sponsor_url|urlize }}</p>
<p>
Application link for {{ sponsor_name }}:
</p>
{% include 'mails/include/email_button.html' with url=app_sponsor_url text='Application' %}
<p>
If clicking the link above doesn't work, please copy and paste the URL in a new browser
window instead.
</p>
<p style="text-align: center;">{{ app_sponsor_url|urlize }}</p>

{% include 'mails/include/closing.html' %}

{% endblock %}
1 change: 1 addition & 0 deletions user/templates/mails/sponsor_link_subject.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
New Sponsor Links
15 changes: 14 additions & 1 deletion user/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.utils.http import urlsafe_base64_encode

from app.utils import reverse as r_reverse
from user.emails import create_verify_email, create_password_reset_email
from user.emails import create_verify_email, create_password_reset_email, create_sponsor_link_email


class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
Expand All @@ -22,6 +22,9 @@ def _make_hash_value(self, user, timestamp):
password_reset_token = PasswordResetTokenGenerator()


sponsor_token = PasswordResetTokenGenerator()


def generate_verify_email(user):
token = account_activation_token.make_token(user)
uuid = urlsafe_base64_encode(force_bytes(user.pk))
Expand All @@ -35,3 +38,13 @@ def generate_pw_reset_email(user, request):
uuid = urlsafe_base64_encode(force_bytes(user.pk))
reset_url = r_reverse('password_reset_confirm', kwargs={'uid': uuid, 'token': token}, request=request)
return create_password_reset_email(user, reset_url)


def generate_sponsor_link_email(user, request):
token = sponsor_token.make_token(user)
uuid = urlsafe_base64_encode(force_bytes(user.pk))
user_sponsor_url = r_reverse('password_reset_confirm', kwargs={'uid': uuid, 'token': token}, request=request)
app_sponsor_url = r_reverse('sponsor_app', kwargs={'uid': uuid, 'token': token}, request=request)
print(user_sponsor_url)
print(app_sponsor_url)
return create_sponsor_link_email(request.user, user_sponsor_url, app_sponsor_url, user.name)
11 changes: 6 additions & 5 deletions user/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from user.forms import SetPasswordForm, PasswordResetForm
from user.mixins import HaveSponsorPermissionMixin
from user.models import User
from user.tokens import account_activation_token, password_reset_token


def login(request):
Expand Down Expand Up @@ -102,7 +101,7 @@ def activate(request, uid, token):
messages.warning(request, "This user no longer exists. Please sign up again!")
return redirect('root')

if account_activation_token.check_token(user, token):
if tokens.account_activation_token.check_token(user, token):
messages.success(request, "Email verified!")

user.email_verified = True
Expand Down Expand Up @@ -144,7 +143,7 @@ def password_reset_confirm(request, uid, token):
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
return TemplateResponse(request, 'password_reset_confirm.html', {'validlink': False})

if password_reset_token.check_token(user, token):
if tokens.password_reset_token.check_token(user, token):
if request.method == 'POST':
form = SetPasswordForm(request.POST)
if form.is_valid():
Expand Down Expand Up @@ -264,9 +263,11 @@ def post(self, request, *args, **kwargs):
name = form.cleaned_data['name']
n_max = form.cleaned_data['n_max']
password = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))

if models.User.objects.filter(email=email).first() is not None:
messages.error(request, 'An account with this email already exists')
else:
models.User.objects.create_sponsor(email=email, password=password, name=name, n_max=n_max)
user = models.User.objects.create_sponsor(email=email, password=password, name=name, n_max=n_max)
msg = tokens.generate_sponsor_link_email(user, request)
msg.send()
messages.success(request, "Sponsor link email successfully sent")
return HttpResponseRedirect(reverse('sponsor_user_list'))

0 comments on commit 11bbd0b

Please sign in to comment.