Skip to content

Commit 952b85e

Browse files
committed
Move handling of secondary email addresses upstream
Since the upstream main website now handles secondary email addresses, centralize the handling to there. This removes the local handling completely, except that we store them in the database. The new push-changes API ensures that they are kept in sync with upstream.
1 parent 86725ca commit 952b85e

File tree

10 files changed

+81
-180
lines changed

10 files changed

+81
-180
lines changed

pgcommitfest/commitfest/apps.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from django.apps import AppConfig
2+
3+
4+
class CFAppConfig(AppConfig):
5+
name = 'pgcommitfest.commitfest'
6+
7+
def ready(self):
8+
from pgcommitfest.auth import auth_user_data_received
9+
from pgcommitfest.userprofile.util import handle_user_data
10+
11+
auth_user_data_received.connect(handle_user_data)

pgcommitfest/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@
123123
# Uncomment the next line to enable admin documentation:
124124
# 'django.contrib.admindocs',
125125
'pgcommitfest.selectable',
126-
'pgcommitfest.commitfest',
126+
'pgcommitfest.commitfest.apps.CFAppConfig',
127127
'pgcommitfest.mailqueue',
128128
'pgcommitfest.userprofile',
129129
)

pgcommitfest/urls.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@
4444

4545
# Account management
4646
url(r'^account/profile/$', pgcommitfest.userprofile.views.userprofile),
47-
url(r'^account/profile/delmail/$', pgcommitfest.userprofile.views.deletemail),
48-
url(r'^account/profile/confirm/([0-9a-f]+)/$', pgcommitfest.userprofile.views.confirmemail),
4947

5048
# Examples:
5149
# url(r'^$', 'pgpgcommitfest.commitfest.views.home', name='home),

pgcommitfest/userprofile/forms.py

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from django import forms
2-
from django.contrib.auth.models import User
32

43
from .models import UserProfile, UserExtraEmail
54

@@ -13,33 +12,11 @@ def __init__(self, user, *args, **kwargs):
1312
super(UserProfileForm, self).__init__(*args, **kwargs)
1413
self.user = user
1514

15+
mailhelp = "To add a new address to choose from, update your user profile on <a href=\"https://www.postgresql.org/account/profile/\">postgresql.org</a>."
16+
1617
self.fields['selectedemail'].empty_label = self.user.email
17-
self.fields['selectedemail'].queryset = UserExtraEmail.objects.filter(user=self.user, confirmed=True)
18+
self.fields['selectedemail'].queryset = UserExtraEmail.objects.filter(user=self.user)
19+
self.fields['selectedemail'].help_text = mailhelp
1820
self.fields['notifyemail'].empty_label = self.user.email
19-
self.fields['notifyemail'].queryset = UserExtraEmail.objects.filter(user=self.user, confirmed=True)
20-
21-
22-
class MailForm(forms.Form):
23-
email = forms.EmailField()
24-
email2 = forms.EmailField(label="Repeat email")
25-
26-
def clean_email(self):
27-
email = self.cleaned_data['email']
28-
29-
if User.objects.filter(email=email).exists():
30-
raise forms.ValidationError("This email is already in use by another account")
31-
32-
return email
33-
34-
def clean_email2(self):
35-
# If the primary email checker had an exception, the data will be gone
36-
# from the cleaned_data structure
37-
if 'email' not in self.cleaned_data:
38-
return self.cleaned_data['email2']
39-
email1 = self.cleaned_data['email']
40-
email2 = self.cleaned_data['email2']
41-
42-
if email1 != email2:
43-
raise forms.ValidationError("Email addresses don't match")
44-
45-
return email2
21+
self.fields['notifyemail'].queryset = UserExtraEmail.objects.filter(user=self.user)
22+
self.fields['notifyemail'].help_text = mailhelp
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Generated by Django 2.2.11 on 2020-08-11 11:09
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('userprofile', '0002_notifications'),
11+
]
12+
13+
operations = [
14+
migrations.RemoveField(
15+
model_name='userextraemail',
16+
name='confirmed',
17+
),
18+
migrations.RemoveField(
19+
model_name='userextraemail',
20+
name='token',
21+
),
22+
migrations.RemoveField(
23+
model_name='userextraemail',
24+
name='tokensent',
25+
),
26+
migrations.AlterField(
27+
model_name='userprofile',
28+
name='notifyemail',
29+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='notifier', to='userprofile.UserExtraEmail', verbose_name='Notifications sent to'),
30+
),
31+
migrations.AlterField(
32+
model_name='userprofile',
33+
name='selectedemail',
34+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='userprofile.UserExtraEmail', verbose_name='Sender email'),
35+
),
36+
]

pgcommitfest/userprofile/models.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
class UserExtraEmail(models.Model):
66
user = models.ForeignKey(User, null=False, blank=False, db_index=True, on_delete=models.CASCADE)
77
email = models.EmailField(max_length=100, null=False, blank=False, unique=True)
8-
confirmed = models.BooleanField(null=False, blank=False, default=False)
9-
token = models.CharField(max_length=100, null=False, blank=True)
10-
tokensent = models.DateTimeField(null=False, blank=False)
118

129
def __str__(self):
1310
return self.email
@@ -20,10 +17,10 @@ class Meta:
2017
class UserProfile(models.Model):
2118
user = models.OneToOneField(User, null=False, blank=False, on_delete=models.CASCADE)
2219
selectedemail = models.ForeignKey(UserExtraEmail, null=True, blank=True,
23-
verbose_name='Sender email', on_delete=models.CASCADE)
20+
verbose_name='Sender email', on_delete=models.SET_NULL)
2421
notifyemail = models.ForeignKey(UserExtraEmail, null=True, blank=True,
2522
verbose_name='Notifications sent to',
26-
related_name='notifier', on_delete=models.CASCADE)
23+
related_name='notifier', on_delete=models.SET_NULL)
2724
notify_all_author = models.BooleanField(null=False, blank=False, default=False, verbose_name="Notify on all where author")
2825
notify_all_reviewer = models.BooleanField(null=False, blank=False, default=False, verbose_name="Notify on all where reviewer")
2926
notify_all_committer = models.BooleanField(null=False, blank=False, default=False, verbose_name="Notify on all where committer")

pgcommitfest/userprofile/templates/extra_email_mail.txt

Lines changed: 0 additions & 7 deletions
This file was deleted.

pgcommitfest/userprofile/templates/userprofileform.html

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -33,42 +33,4 @@
3333
</div>
3434
</form>
3535

36-
<h2>Extra email addresses</h2>
37-
<p>
38-
The following extra email addresses are registered for your account:
39-
</p>
40-
<ul>
41-
{%for e in extramails%}
42-
<li>{{e.email}}{%if not e.confirmed%} (<i>Pending confirmation</i>){%endif%} <a href="delmail/?{{e.id}}">delete</a></li>
43-
{%endfor%}
44-
</ul>
45-
46-
<h3>Add email</h3>
47-
<form class="form-horizontal" method="post" action=".">{%csrf_token%}
48-
{%if mailform.errors%}
49-
<div class="alert">Please correct the errors below, and re-submit the form.</div>
50-
{%endif%}
51-
{%if mailform.non_field_errors%}
52-
<div class="alert alert-danger">{{mailform.non_field_errors}}</div>
53-
{%endif%}
54-
{%for field in mailform%}
55-
<div class="form-group">
56-
{{field|label_class:"control-label col-lg-1"}}
57-
<div class="col-lg-11 controls">
58-
{%if field.errors %}
59-
{%for e in field.errors%}
60-
<div class="alert alert-danger">{{e}}</div>
61-
{%endfor%}
62-
{%endif%}
63-
{{field|field_class:"form-control"}}
64-
{%if field.help_text%}<br/>{{field.help_text|safe}}{%endif%}</div>
65-
</div>
66-
{%endfor%}
67-
68-
<div class="form-group">
69-
<div class="col-lg-12">
70-
<div class="control"><input type="submit" class="btn btn-default" name="submit" value="Add email"></div>
71-
</div>
72-
</div>
73-
</form>
7436
{%endblock%}

pgcommitfest/userprofile/util.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,7 @@
1-
from Crypto.Hash import SHA256
2-
from Crypto import Random
31
from email.utils import formataddr
42
from email.header import Header
53

6-
from .models import UserProfile
7-
8-
9-
def generate_random_token():
10-
"""
11-
Generate a random token of 64 characters. This token will be
12-
generated using a strong random number, and then hex encoded to make
13-
sure all characters are safe to put in emails and URLs.
14-
"""
15-
s = SHA256.new()
16-
r = Random.new()
17-
s.update(r.read(250))
18-
return s.hexdigest()
4+
from .models import UserProfile, UserExtraEmail
195

206

217
class UserWrapper(object):
@@ -26,7 +12,7 @@ def __init__(self, user):
2612
def email(self):
2713
try:
2814
up = UserProfile.objects.get(user=self.user)
29-
if up.selectedemail and up.selectedemail.confirmed:
15+
if up.selectedemail:
3016
return up.selectedemail.email
3117
else:
3218
return self.user.email
@@ -38,3 +24,19 @@ def encoded_email_header(self):
3824
return formataddr((
3925
str(Header("%s %s" % (self.user.first_name, self.user.last_name), 'utf-8')),
4026
self.email))
27+
28+
29+
def handle_user_data(sender, **kwargs):
30+
user = kwargs.pop('user')
31+
userdata = kwargs.pop('userdata')
32+
33+
secondary = userdata.get('secondaryemails', [])
34+
35+
# Remove any email attached to this user that are not upstream. Since the foreign keys
36+
# are set to SET_NULL, they will all revert to being the users default in this case.
37+
UserExtraEmail.objects.filter(user=user).exclude(email__in=secondary).delete()
38+
39+
# Then add back any of the ones that aren't there
40+
current = set([e.email for e in UserExtraEmail.objects.filter(user=user)])
41+
for e in set(secondary).difference(current):
42+
UserExtraEmail(user=user, email=e).save()

pgcommitfest/userprofile/views.py

Lines changed: 7 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,27 @@
11
from django.shortcuts import render
22
from django.http import HttpResponseRedirect
3-
from django.template import RequestContext
43
from django.db import transaction
54
from django.contrib import messages
65
from django.contrib.auth.decorators import login_required
7-
from django.conf import settings
86

9-
from datetime import datetime
10-
11-
from pgcommitfest.mailqueue.util import send_template_mail
12-
13-
from .models import UserProfile, UserExtraEmail
14-
from .forms import UserProfileForm, MailForm
15-
from .util import generate_random_token
7+
from .models import UserProfile
8+
from .forms import UserProfileForm
169

1710

1811
@login_required
1912
@transaction.atomic
2013
def userprofile(request):
2114
(profile, created) = UserProfile.objects.get_or_create(user=request.user)
22-
form = mailform = None
2315

2416
if request.method == 'POST':
25-
if request.POST['submit'] == 'Save':
26-
form = UserProfileForm(request.user, request.POST, instance=profile)
27-
if form.is_valid():
28-
form.save()
29-
messages.add_message(request, messages.INFO, "User profile saved.")
30-
return HttpResponseRedirect('.')
31-
elif request.POST['submit'] == 'Add email':
32-
mailform = MailForm(request.POST)
33-
if mailform.is_valid():
34-
m = UserExtraEmail(user=request.user,
35-
email=mailform.cleaned_data['email'],
36-
confirmed=False,
37-
token=generate_random_token(),
38-
tokensent=datetime.now())
39-
m.save()
40-
send_template_mail(settings.NOTIFICATION_FROM,
41-
request.user.username,
42-
m.email,
43-
'Your email address for commitfest.postgresql.org',
44-
'extra_email_mail.txt',
45-
{'token': m.token, 'user': m.user})
46-
messages.info(request, "A confirmation token has been sent to %s" % m.email)
47-
return HttpResponseRedirect('.')
48-
else:
49-
messages.error(request, "Invalid submit button pressed! Nothing saved.")
17+
form = UserProfileForm(request.user, request.POST, instance=profile)
18+
if form.is_valid():
19+
form.save()
20+
messages.add_message(request, messages.INFO, "User profile saved.")
5021
return HttpResponseRedirect('.')
51-
52-
if not form:
22+
else:
5323
form = UserProfileForm(request.user, instance=profile)
54-
if not mailform:
55-
mailform = MailForm()
56-
57-
extramails = UserExtraEmail.objects.filter(user=request.user)
5824

5925
return render(request, 'userprofileform.html', {
6026
'form': form,
61-
'extramails': extramails,
62-
'mailform': mailform,
6327
})
64-
65-
66-
@login_required
67-
@transaction.atomic
68-
def deletemail(request):
69-
try:
70-
id = int(request.META['QUERY_STRING'])
71-
except ValueError:
72-
messages.error(request, "Invalid format of id in query string")
73-
return HttpResponseRedirect('../')
74-
75-
try:
76-
e = UserExtraEmail.objects.get(user=request.user, id=id)
77-
except UserExtraEmail.DoesNotExist:
78-
messages.error(request, "Specified email address does not exist on this user")
79-
return HttpResponseRedirect('../')
80-
81-
messages.info(request, "Email address %s deleted." % e.email)
82-
e.delete()
83-
return HttpResponseRedirect('../')
84-
85-
86-
@login_required
87-
@transaction.atomic
88-
def confirmemail(request, tokenhash):
89-
try:
90-
e = UserExtraEmail.objects.get(user=request.user, token=tokenhash)
91-
if e.confirmed:
92-
messages.warning(request, "This email address has already been confirmed.")
93-
else:
94-
# Ok, it's not confirmed. So let's do that now
95-
e.confirmed = True
96-
e.token = ''
97-
e.save()
98-
messages.info(request, "Email address %s added to profile." % e.email)
99-
except UserExtraEmail.DoesNotExist:
100-
messages.error(request, "Token %s was not found for your user. It may be because it has already been used?" % tokenhash)
101-
102-
return HttpResponseRedirect("../../")

0 commit comments

Comments
 (0)