mirror of
https://github.com/postgres/pgweb.git
synced 2025-08-10 00:42:06 +00:00
Teach pgweb to handle secondary email addresses
This allows each account to have more than one email address, of which one is primary. Adding more addresses will trigger an email with a verification link (of course). The field previously known as "email" is now changed to be "primary email". Change the profile form to allow freely changing between the added addresses which one is the primary. Remove the functionality to directly change the primary email -- instead one has to add a new address first and then change to that one, which simplifies several things in the handling.
This commit is contained in:
@ -48,7 +48,9 @@ The flow of an authentication in the 2.0 system is fairly simple:
|
||||
l
|
||||
The last name of the user logged in
|
||||
e
|
||||
The email address of the user logged in
|
||||
The primary email address of the user logged in
|
||||
se
|
||||
A comma separated list of secondary email addresses for the user logged in
|
||||
d
|
||||
base64 encoded data block to be passed along in confirmation (optional)
|
||||
su
|
||||
@ -148,8 +150,10 @@ The flow for search is:
|
||||
u
|
||||
Username
|
||||
e
|
||||
Email
|
||||
Primary email
|
||||
f
|
||||
First name
|
||||
l
|
||||
Last name
|
||||
se
|
||||
Array of secondary email addresses
|
||||
|
@ -6,6 +6,7 @@ import re
|
||||
from django.contrib.auth.models import User
|
||||
from pgweb.core.models import UserProfile
|
||||
from pgweb.contributors.models import Contributor
|
||||
from .models import SecondaryEmail
|
||||
|
||||
from .recaptcha import ReCaptchaField
|
||||
|
||||
@ -121,14 +122,25 @@ class UserProfileForm(forms.ModelForm):
|
||||
|
||||
|
||||
class UserForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
primaryemail = forms.ChoiceField(choices=[], required=True, label='Primary email address')
|
||||
|
||||
def __init__(self, can_change_email, secondaryaddresses, *args, **kwargs):
|
||||
super(UserForm, self).__init__(*args, **kwargs)
|
||||
self.fields['first_name'].required = True
|
||||
self.fields['last_name'].required = True
|
||||
if can_change_email:
|
||||
self.fields['primaryemail'].choices = [(self.instance.email, self.instance.email), ] + [(a.email, a.email) for a in secondaryaddresses if a.confirmed]
|
||||
if not secondaryaddresses:
|
||||
self.fields['primaryemail'].help_text = "To change the primary email address, first add it as a secondary address below"
|
||||
else:
|
||||
self.fields['primaryemail'].choices = [(self.instance.email, self.instance.email), ]
|
||||
self.fields['primaryemail'].help_text = "You cannot change the primary email of this account since it is connected to an external authentication system"
|
||||
self.fields['primaryemail'].widget.attrs['disabled'] = True
|
||||
self.fields['primaryemail'].required = False
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('first_name', 'last_name', )
|
||||
fields = ('primaryemail', 'first_name', 'last_name', )
|
||||
|
||||
|
||||
class ContributorForm(forms.ModelForm):
|
||||
@ -137,16 +149,16 @@ class ContributorForm(forms.ModelForm):
|
||||
exclude = ('ctype', 'lastname', 'firstname', 'user', )
|
||||
|
||||
|
||||
class ChangeEmailForm(forms.Form):
|
||||
email = forms.EmailField()
|
||||
email2 = forms.EmailField(label="Repeat email")
|
||||
class AddEmailForm(forms.Form):
|
||||
email1 = forms.EmailField(label="New email", required=False)
|
||||
email2 = forms.EmailField(label="Repeat email", required=False)
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super(ChangeEmailForm, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = user
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data['email'].lower()
|
||||
def clean_email1(self):
|
||||
email = self.cleaned_data['email1'].lower()
|
||||
|
||||
if email == self.user.email:
|
||||
raise forms.ValidationError("This is your existing email address!")
|
||||
@ -154,14 +166,23 @@ class ChangeEmailForm(forms.Form):
|
||||
if User.objects.filter(email=email).exists():
|
||||
raise forms.ValidationError("A user with this email address is already registered")
|
||||
|
||||
try:
|
||||
s = SecondaryEmail.objects.get(email=email)
|
||||
if s.user == self.user:
|
||||
raise forms.ValidationError("This email address is already connected to your account")
|
||||
else:
|
||||
raise forms.ValidationError("A user with this email address is already registered")
|
||||
except SecondaryEmail.DoesNotExist:
|
||||
pass
|
||||
|
||||
return email
|
||||
|
||||
def clean_email2(self):
|
||||
# If the primary email checker had an exception, the data will be gone
|
||||
# from the cleaned_data structure
|
||||
if 'email' not in self.cleaned_data:
|
||||
if 'email1' not in self.cleaned_data:
|
||||
return self.cleaned_data['email2'].lower()
|
||||
email1 = self.cleaned_data['email'].lower()
|
||||
email1 = self.cleaned_data['email1'].lower()
|
||||
email2 = self.cleaned_data['email2'].lower()
|
||||
|
||||
if email1 != email2:
|
||||
|
35
pgweb/account/migrations/0005_secondaryemail.py
Normal file
35
pgweb/account/migrations/0005_secondaryemail.py
Normal file
@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.27 on 2020-08-06 16:12
|
||||
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 = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('account', '0004_cauth_last_login'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SecondaryEmail',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('email', models.EmailField(max_length=75, unique=True)),
|
||||
('confirmed', models.BooleanField(default=False)),
|
||||
('token', models.CharField(max_length=100)),
|
||||
('sentat', models.DateTimeField(auto_now=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('email', ),
|
||||
},
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='EmailChangeToken',
|
||||
),
|
||||
]
|
@ -35,8 +35,12 @@ class CommunityAuthConsent(models.Model):
|
||||
unique_together = (('user', 'org'), )
|
||||
|
||||
|
||||
class EmailChangeToken(models.Model):
|
||||
user = models.OneToOneField(User, null=False, blank=False, on_delete=models.CASCADE)
|
||||
email = models.EmailField(max_length=75, null=False, blank=False)
|
||||
class SecondaryEmail(models.Model):
|
||||
user = models.ForeignKey(User, null=False, blank=False, on_delete=models.CASCADE)
|
||||
email = models.EmailField(max_length=75, null=False, blank=False, unique=True)
|
||||
confirmed = models.BooleanField(null=False, blank=False, default=False)
|
||||
token = models.CharField(max_length=100, null=False, blank=False)
|
||||
sentat = models.DateTimeField(null=False, blank=False, auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ('email', )
|
||||
|
@ -18,8 +18,7 @@ urlpatterns = [
|
||||
|
||||
# Profile
|
||||
url(r'^profile/$', pgweb.account.views.profile),
|
||||
url(r'^profile/change_email/$', pgweb.account.views.change_email),
|
||||
url(r'^profile/change_email/([0-9a-f]+)/$', pgweb.account.views.confirm_change_email),
|
||||
url(r'^profile/add_email/([0-9a-f]+)/$', pgweb.account.views.confirm_add_email),
|
||||
|
||||
# List of items to edit
|
||||
url(r'^edit/(.*)/$', pgweb.account.views.listobjects),
|
||||
|
@ -10,7 +10,7 @@ from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.auth import logout as django_logout
|
||||
from django.conf import settings
|
||||
from django.db import transaction, connection
|
||||
from django.db.models import Q
|
||||
from django.db.models import Q, Prefetch
|
||||
|
||||
import base64
|
||||
import urllib.parse
|
||||
@ -32,12 +32,12 @@ from pgweb.contributors.models import Contributor
|
||||
from pgweb.downloads.models import Product
|
||||
from pgweb.profserv.models import ProfessionalService
|
||||
|
||||
from .models import CommunityAuthSite, CommunityAuthConsent, EmailChangeToken
|
||||
from .models import CommunityAuthSite, CommunityAuthConsent, SecondaryEmail
|
||||
from .forms import PgwebAuthenticationForm
|
||||
from .forms import CommunityAuthConsentForm
|
||||
from .forms import SignupForm, SignupOauthForm
|
||||
from .forms import UserForm, UserProfileForm, ContributorForm
|
||||
from .forms import ChangeEmailForm, PgwebPasswordResetForm
|
||||
from .forms import AddEmailForm, PgwebPasswordResetForm
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@ -110,96 +110,81 @@ def profile(request):
|
||||
|
||||
contribform = None
|
||||
|
||||
secondaryaddresses = SecondaryEmail.objects.filter(user=request.user)
|
||||
|
||||
if request.method == 'POST':
|
||||
# Process this form
|
||||
userform = UserForm(data=request.POST, instance=request.user)
|
||||
userform = UserForm(can_change_email, secondaryaddresses, data=request.POST, instance=request.user)
|
||||
profileform = UserProfileForm(data=request.POST, instance=profile)
|
||||
secondaryemailform = AddEmailForm(request.user, data=request.POST)
|
||||
if contrib:
|
||||
contribform = ContributorForm(data=request.POST, instance=contrib)
|
||||
|
||||
if userform.is_valid() and profileform.is_valid() and (not contrib or contribform.is_valid()):
|
||||
userform.save()
|
||||
if userform.is_valid() and profileform.is_valid() and secondaryemailform.is_valid() and (not contrib or contribform.is_valid()):
|
||||
user = userform.save()
|
||||
|
||||
# Email takes some magic special handling, since we only allow picking of existing secondary emails, but it's
|
||||
# not a foreign key (due to how the django auth model works).
|
||||
if can_change_email and userform.cleaned_data['primaryemail'] != user.email:
|
||||
# Changed it!
|
||||
oldemail = user.email
|
||||
# Create a secondary email for the old primary one
|
||||
SecondaryEmail(user=user, email=oldemail, confirmed=True, token='').save()
|
||||
# Flip the main email
|
||||
user.email = userform.cleaned_data['primaryemail']
|
||||
user.save(update_fields=['email', ])
|
||||
# Finally remove the old secondary address, since it can`'t be both primary and secondary at the same time
|
||||
SecondaryEmail.objects.filter(user=user, email=user.email).delete()
|
||||
log.info("User {} changed primary email from {} to {}".format(user.username, oldemail, user.email))
|
||||
|
||||
profileform.save()
|
||||
if contrib:
|
||||
contribform.save()
|
||||
return HttpResponseRedirect("/account/")
|
||||
if secondaryemailform.cleaned_data.get('email1', ''):
|
||||
sa = SecondaryEmail(user=request.user, email=secondaryemailform.cleaned_data['email1'], token=generate_random_token())
|
||||
sa.save()
|
||||
send_template_mail(
|
||||
settings.ACCOUNTS_NOREPLY_FROM,
|
||||
sa.email,
|
||||
'Your postgresql.org community account',
|
||||
'account/email_add_email.txt',
|
||||
{'secondaryemail': sa, 'user': request.user, }
|
||||
)
|
||||
|
||||
for k, v in request.POST.items():
|
||||
if k.startswith('deladdr_') and v == '1':
|
||||
ii = int(k[len('deladdr_'):])
|
||||
SecondaryEmail.objects.filter(user=request.user, id=ii).delete()
|
||||
|
||||
return HttpResponseRedirect(".")
|
||||
else:
|
||||
# Generate form
|
||||
userform = UserForm(instance=request.user)
|
||||
userform = UserForm(can_change_email, secondaryaddresses, instance=request.user)
|
||||
profileform = UserProfileForm(instance=profile)
|
||||
secondaryemailform = AddEmailForm(request.user)
|
||||
if contrib:
|
||||
contribform = ContributorForm(instance=contrib)
|
||||
|
||||
return render_pgweb(request, 'account', 'account/userprofileform.html', {
|
||||
'userform': userform,
|
||||
'profileform': profileform,
|
||||
'secondaryemailform': secondaryemailform,
|
||||
'secondaryaddresses': secondaryaddresses,
|
||||
'secondarypending': any(not a.confirmed for a in secondaryaddresses),
|
||||
'contribform': contribform,
|
||||
'can_change_email': can_change_email,
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@transaction.atomic
|
||||
def change_email(request):
|
||||
tokens = EmailChangeToken.objects.filter(user=request.user)
|
||||
token = len(tokens) and tokens[0] or None
|
||||
def confirm_add_email(request, tokenhash):
|
||||
addr = get_object_or_404(SecondaryEmail, user=request.user, token=tokenhash)
|
||||
|
||||
if request.user.password == OAUTH_PASSWORD_STORE:
|
||||
# Link shouldn't exist in this case, so just throw an unfriendly
|
||||
# error message.
|
||||
return HttpSimpleResponse(request, "Account error", "This account cannot change email address as it's connected to a third party login site.")
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ChangeEmailForm(request.user, data=request.POST)
|
||||
if form.is_valid():
|
||||
# If there is an existing token, delete it
|
||||
if token:
|
||||
token.delete()
|
||||
|
||||
# Create a new token
|
||||
token = EmailChangeToken(user=request.user,
|
||||
email=form.cleaned_data['email'].lower(),
|
||||
token=generate_random_token())
|
||||
token.save()
|
||||
|
||||
send_template_mail(
|
||||
settings.ACCOUNTS_NOREPLY_FROM,
|
||||
form.cleaned_data['email'],
|
||||
'Your postgresql.org community account',
|
||||
'account/email_change_email.txt',
|
||||
{'token': token, 'user': request.user, }
|
||||
)
|
||||
return HttpResponseRedirect('done/')
|
||||
else:
|
||||
form = ChangeEmailForm(request.user)
|
||||
|
||||
return render_pgweb(request, 'account', 'account/emailchangeform.html', {
|
||||
'form': form,
|
||||
'token': token,
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@transaction.atomic
|
||||
def confirm_change_email(request, tokenhash):
|
||||
tokens = EmailChangeToken.objects.filter(user=request.user, token=tokenhash)
|
||||
token = len(tokens) and tokens[0] or None
|
||||
|
||||
if request.user.password == OAUTH_PASSWORD_STORE:
|
||||
# Link shouldn't exist in this case, so just throw an unfriendly
|
||||
# error message.
|
||||
return HttpSimpleResponse(request, "Account error", "This account cannot change email address as it's connected to a third party login site.")
|
||||
|
||||
if token:
|
||||
# Valid token find, so change the email address
|
||||
request.user.email = token.email.lower()
|
||||
request.user.save()
|
||||
token.delete()
|
||||
|
||||
return render_pgweb(request, 'account', 'account/emailchangecompleted.html', {
|
||||
'token': tokenhash,
|
||||
'success': token and True or False,
|
||||
})
|
||||
# Valid token found, so mark the address as confirmed.
|
||||
addr.confirmed = True
|
||||
addr.token = ''
|
||||
addr.save()
|
||||
return HttpResponseRedirect('/account/profile/')
|
||||
|
||||
|
||||
@login_required
|
||||
@ -538,6 +523,7 @@ def communityauth(request, siteid):
|
||||
'f': request.user.first_name.encode('utf-8'),
|
||||
'l': request.user.last_name.encode('utf-8'),
|
||||
'e': request.user.email.encode('utf-8'),
|
||||
'se': ','.join([a.email for a in SecondaryEmail.objects.filter(user=request.user, confirmed=True).order_by('email')]).encode('utf8'),
|
||||
}
|
||||
if d:
|
||||
info['d'] = d.encode('utf-8')
|
||||
@ -626,9 +612,15 @@ def communityauth_search(request, siteid):
|
||||
else:
|
||||
raise Http404('No search term specified')
|
||||
|
||||
users = User.objects.filter(q)
|
||||
users = User.objects.prefetch_related(Prefetch('secondaryemail_set', queryset=SecondaryEmail.objects.filter(confirmed=True))).filter(q)
|
||||
|
||||
j = json.dumps([{'u': u.username, 'e': u.email, 'f': u.first_name, 'l': u.last_name} for u in users])
|
||||
j = json.dumps([{
|
||||
'u': u.username,
|
||||
'e': u.email,
|
||||
'f': u.first_name,
|
||||
'l': u.last_name,
|
||||
'se': [a.email for a in u.secondaryemail_set.all()],
|
||||
} for u in users])
|
||||
|
||||
return HttpResponse(_encrypt_site_response(site, j))
|
||||
|
||||
|
@ -15,7 +15,7 @@ from django.db import connection, transaction
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from pgweb.account.models import EmailChangeToken
|
||||
from pgweb.account.models import SecondaryEmail
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@ -33,4 +33,4 @@ class Command(BaseCommand):
|
||||
|
||||
# Clean up old email change tokens
|
||||
with transaction.atomic():
|
||||
EmailChangeToken.objects.filter(sentat__lt=datetime.now() - timedelta(hours=24)).delete()
|
||||
SecondaryEmail.objects.filter(confirmed=False, sentat__lt=datetime.now() - timedelta(hours=24)).delete()
|
||||
|
9
templates/account/email_add_email.txt
Normal file
9
templates/account/email_add_email.txt
Normal file
@ -0,0 +1,9 @@
|
||||
Somebody, probably you, attempted to add this email address to
|
||||
the PostgreSQL community account {{user.username}}.
|
||||
|
||||
To confirm the addition of this email address, please click
|
||||
the following link:
|
||||
|
||||
{{link_root}}/account/profile/add_email/{{secondaryemail.token}}/
|
||||
|
||||
If you do not approve of this, you can ignore this email.
|
@ -1,8 +0,0 @@
|
||||
Somebody, probably you, attempted to change the email of the
|
||||
PostgreSQL community account {{user.username}} to this email address.
|
||||
|
||||
To confirm this change of email address, please click the following
|
||||
link:
|
||||
|
||||
{{link_root}}/account/profile/change_email/{{token.token}}/
|
||||
|
@ -1,34 +0,0 @@
|
||||
{%extends "base/page.html"%}
|
||||
{%block title%}{%if success%}Email changed{%else%}Change email{%endif%}{%endblock%}
|
||||
{%block contents%}
|
||||
|
||||
{%if success%}
|
||||
<h1>Email changed <i class="fas fa-envelope"></i></h1>
|
||||
<p>
|
||||
Your email has successfully been changed to {{user.email}}.
|
||||
</p>
|
||||
<p>
|
||||
Please note that if you are using your account from a different
|
||||
community site than <em>www.postgresql.org</em>, you may need to log
|
||||
out and back in again for the email to be updated on that site.
|
||||
</p>
|
||||
{%else%}
|
||||
<h1>Change email <i class="fas fa-envelope"></i></h1>
|
||||
<p>
|
||||
The token <code>{{token}}</code> was not found.
|
||||
</p>
|
||||
<p>
|
||||
This can be because it expired (tokens are valid for approximately
|
||||
24 hours), or because you did not paste the complete URL without any
|
||||
spaces.
|
||||
</p>
|
||||
<p>
|
||||
Double check the URL, and if it is correct, restart the process by
|
||||
clicking "change" in your profile to generate a new token and try again.
|
||||
</p>
|
||||
{%endif%}
|
||||
|
||||
<p>
|
||||
<a href="/account/profile/">Return</a> to your profile.
|
||||
</p>
|
||||
{%endblock%}
|
@ -1,60 +0,0 @@
|
||||
{%extends "base/page.html"%}
|
||||
{% load pgfilters %}
|
||||
{%block title%}Change email{%endblock%}
|
||||
{%block contents%}
|
||||
<h1>Change email <i class="fas fa-envelope"></i></h1>
|
||||
{%if token%}
|
||||
<h2>Awaiting confirmation</h2>
|
||||
<p>
|
||||
A confirmation token was sent to {{token.email}} on {{token.sentat|date:"Y-m-d H:i"}}.
|
||||
Wait for this token to arrive, and then click the link that is sent
|
||||
in the email to confirm the change of email.
|
||||
</p>
|
||||
<p>
|
||||
The token will be valid for approximately 24 hours, after which it will
|
||||
be automatically deleted.
|
||||
</p>
|
||||
<p>
|
||||
To create a new token (and a new email), fill out the form below again.
|
||||
Note that once a new token is created, the old token will no longer be
|
||||
valid for use.
|
||||
</p>
|
||||
|
||||
<h2>Change email</h2>
|
||||
{%else%}
|
||||
<p>
|
||||
To change your email address, input the new address below. Once you
|
||||
click "Change email", a verification token will be sent to the new email address,
|
||||
and once you click the link in that email, your email will be changed.
|
||||
</p>
|
||||
{%endif%}
|
||||
|
||||
<form method="post" action=".">{% csrf_token %}
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
Please correct the errors below, and re-submit the form.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for field in form %}
|
||||
<div class="form-group row">
|
||||
{% if field.errors %}
|
||||
{% for e in field.errors %}
|
||||
<div class="col-lg-12 alert alert-danger">{{e}}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<label class="col-form-label col-sm-3" for="{{ field.id }}">
|
||||
{{ field.label|title }}
|
||||
{% if field.help_text %}
|
||||
<p><small>{{ field.help_text }}</small></p>
|
||||
{% endif %}
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
{{ field|field_class:"form-control" }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="submit-row">
|
||||
<input class="btn btn-primary" type="submit" value="Change Email" />
|
||||
</div>
|
||||
</form>
|
||||
{%endblock%}
|
@ -17,19 +17,6 @@
|
||||
{{ user.username }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-form-label col-sm-3">Email:</label>
|
||||
<div class="col-sm-9">
|
||||
{{ user.email }}
|
||||
{% if can_change_email %}
|
||||
(<em><a href="change_email/">change</a></em>)
|
||||
{% else %}
|
||||
<p><em>The email address of this account cannot be changed, because the account does
|
||||
not have a local password, most likely because it's connected to a third
|
||||
party system (such as Google or Facebook).</em></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% for field in userform %}
|
||||
<div class="form-group row">
|
||||
{% if field.errors %}
|
||||
@ -66,6 +53,47 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<h2>Secondary email addresses</h2>
|
||||
<p>You can add one or more secondary email addresses to your account, which can be used for example to subscribe to mailing lists.</p>
|
||||
{%if secondaryaddresses%}
|
||||
<p>Note that deleting any address here will cascade to connected system and can for example lead to being unsubscribed from mailing lists automatically.</p>
|
||||
<p></p>
|
||||
<p>The following secondary addresses are currently registered with your account:</p>
|
||||
<ul>
|
||||
{% for a in secondaryaddresses %}
|
||||
<li>{{a.email}}{%if not a.confirmed%} <em>(awaiting confirmation since {{a.sentat}})</em>{%endif%} (<input type="checkbox" name="deladdr_{{a.id}}" value="1"> Delete)</li>
|
||||
{%endfor%}
|
||||
</ul>
|
||||
{%if secondarypending %}
|
||||
<p>
|
||||
One or more of the secondary addresses on your account are listed as pending. An email has been sent to the address to confirm that
|
||||
you are in control of the address. Open the link in this email (while logged in to this account) to confirm the account. If an email
|
||||
address is not confirmed within approximately 24 hours, it will be deleted. If you have not received the confirmation token, you
|
||||
can delete the address and re-add it, to have the system re-send the verification email.
|
||||
</p>
|
||||
{%endif%}
|
||||
{%endif%}
|
||||
<h3>Add new email address</h3>
|
||||
{%for field in secondaryemailform%}
|
||||
<div class="form-group row">
|
||||
{% if field.errors %}
|
||||
{% for e in field.errors %}
|
||||
<div class="col-lg-12 alert alert-danger">{{e}}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<label class="col-form-label col-sm-3" for="{{ field.id }}">
|
||||
{{ field.label }}
|
||||
{% if field.help_text %}
|
||||
<p><small>{{ field.help_text }}</small></p>
|
||||
{% endif %}
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
{{ field|field_class:"form-control" }}
|
||||
</div>
|
||||
</div>
|
||||
{%endfor%}
|
||||
|
||||
{% if contribform %}
|
||||
<h2>Edit contributor information</h2>
|
||||
<p>You can edit the information that's shown on the <a href="/community/contributors/" target="_blank">contributors</a> page. Please be careful as your changes will take effect immediately!
|
||||
@ -89,6 +117,7 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="submit-row">
|
||||
<input class="btn btn-primary" type="submit" value="Save" />
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user