Commit 924bb5cf authored by Chris Lamb's avatar Chris Lamb
Browse files

Add email change functionality. (Closes: #27)

parent 3add26ec
from django import forms
from django.contrib.auth import get_user_model
from email_from_template import send_mail
from freenodejobs.utils.tokens import get_token
UserModel = get_user_model()
class ChangeEmailForm(forms.ModelForm):
class Meta:
model = UserModel
fields = (
'email',
)
def clean_email(self):
val = self.cleaned_data['email']
if val == self.initial['email']:
raise forms.ValidationError(
"Please enter a different email address."
)
return val
def save(self):
# Ensure that we generate tokens using their existing email
self.instance.refresh_from_db()
new_email = self.cleaned_data['email']
send_mail((new_email,), 'account/change_email/validate.email', {
'token': get_token(self.instance, new_email),
}, fail_silently=True)
return new_email
from django.contrib.auth import get_user_model
from freenodejobs.utils.test import TestCase
from freenodejobs.utils.tokens import get_token
UserModel = get_user_model()
class ChangeEmailTests(TestCase):
NEW_EMAIL = 'new@example.com'
def test_GET(self):
self.assertGET(200, 'account:change-email:view')
def test_POST(self):
with self.assertSendsMail():
self.assertPOST(
{'email': self.NEW_EMAIL},
'account:change-email:view',
)
self.user.refresh_from_db()
def test_POST_same(self):
with self.assertSendsMail(0):
response = self.assertPOST({
'email': self.user.email,
}, 'account:change-email:view', status_code=200)
self.assertFormError(response, 'form', 'email', [
"Please enter a different email address.",
])
def test_validate(self):
old_email_validated = self.user.email_validated
self.assertGET(
302,
'account:change-email:validate',
get_token(self.user, self.NEW_EMAIL),
login=None,
)
self.user.refresh_from_db()
self.assertEqual(self.user.email, self.NEW_EMAIL)
self.assertNotEqual(self.user.email_validated, old_email_validated)
def test_validate_invalid_token(self):
old_email_validated = self.user.email_validated
self.assertGET(302, 'account:change-email:validate', 'invalid-token')
self.user.refresh_from_db()
self.assertEqual(self.user.email_validated, old_email_validated)
self.assertNotEqual(self.user.email, self.NEW_EMAIL)
from django.urls import path
from . import views
app_name = 'account_change_email'
urlpatterns = (
path('account/change-email', views.view,
name='view'),
path('account/change-email/validate/<token>', views.validate,
name='validate'),
)
from django.conf import settings
from django.utils import timezone
from django.contrib import messages
from django.shortcuts import render, redirect
from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required
from freenodejobs.utils.user import login
from freenodejobs.utils.tokens import get_user_from_token, get_value_from_token
from .forms import ChangeEmailForm
@login_required
def view(request):
if request.method == 'POST':
form = ChangeEmailForm(request.POST, instance=request.user)
if form.is_valid():
email = form.save()
messages.success(
request,
"A validation email has been sent to {}.".format(email)
)
return redirect('profile:view')
else:
form = ChangeEmailForm(instance=request.user)
return render(request, 'account/change_email/view.html', {
'form': form,
})
def validate(request, token):
# Always log the user out, at least to avoid confusion
logout(request)
user = get_user_from_token(token)
new_email = get_value_from_token(token, 0)
if user is None or new_email is None:
messages.error(request,
"The link you followed is invalid or has expired.")
return redirect(settings.LOGOUT_REDIRECT_URL)
user.email = new_email
user.email_validated = timezone.now()
user.save()
login(request, user)
messages.success(
request,
"Your new email address was successfully validated.",
)
return redirect(settings.LOGIN_REDIRECT_URL)
......@@ -5,6 +5,8 @@ from . import views
app_name = 'account'
urlpatterns = (
path('', include('freenodejobs.account.account_change_email.urls',
namespace='change-email')),
path('', include('freenodejobs.account.account_reset_password.urls',
namespace='reset-password')),
path('', include('freenodejobs.account.account_two_factor_auth.urls',
......
{% extends email_from_template %}
{% block subject %}
Freenode Jobs email validation
{% endblock %}
{% block body %}
Hi,
To validate your new email address on Freenode Jobs, please click the following
link:
{{ settings.SITE_URL }}{% url "account:change-email:validate" token %}
If you did not request this email change, please ignore this email.
Regards,
--
Freenode Jobs
{% endblock %}
{% extends "base/base.html" %}
{% block title %}Change email &mdash; {{ block.super }}{% endblock %}
{% block subtitle %}Change email{% endblock %}
{% block container %}
{% if form.non_field_errors %}
<div class="alert alert-danger mb-5">
<h4 class="alert-heading">
Change email error
</h4>
<hr>
{% for x in form.non_field_errors %}
<p class="mb-1">{{ x }}</p>
{% endfor %}
</div>
{% endif %}
<form
method="POST"
class=""
action=""
{% if form.is_multipart %}enctype="multipart/form-data"{% endif %}
>
{% csrf_token %}
<div class="form-group">
<label class="col-form-label" for="{{ form.email.id_for_label }}">
New email address
</label>
<input
id="{{ form.email.id_for_label }}"
type="email"
class="form-control {% if form.errors %}{% if form.errors.email %}is-invalid{% else %}{% if not form.non_field_errors %}is-valid{% endif %}{% endif %}{% endif %}"
name="{{ form.email.html_name }}"
value="{{ form.email.value|default:"" }}"
maxlength="{{ form.email.field.max_length|default:"" }}"
placeholder="Enter a new email (confirm)"
{% if form.email.field.required %}required{% endif %}
>
{% for x in form.errors.email %}
<div class="invalid-feedback">{{ x }}</div>
{% empty %}
<small class="form-text text-muted">
Please enter your updated email address. A validation email will be sent to this address.
</small>
{% endfor %}
</div>
<button type="submit" class="btn btn-primary my-3">
Change email
</button>
</form>
{% endblock %}
......@@ -8,6 +8,7 @@
{% if profile %}
<p class="text-right">
<a href="{% url "account:password-change" %}" class="btn btn-secondary">Change password</a>
<a href="{% url "account:change-email:view" %}" class="btn btn-secondary">Change email</a>
</p>
{% else %}
<p>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment