<li class="active">{{title}}</li>
<li class="pull-right active">
{%if user.is_authenticated%}
- Logged in as {{user}} (<a href="/account/logout/">log out</a>{%if user.is_staff%} or access <a href="/admin/">administration</a>{%endif%})
+ Logged in as {{user}} (<a href="/account/profile/">edit profile</a> | <a href="/account/logout/">log out</a>{%if user.is_staff%} | <a href="/admin/">administration</a>{%endif%})
{%else%}
<a href="/account/login/?next={{request.path}}">Log in</a>
{%endif%}
from email.utils import formatdate, make_msgid
from mailqueue.util import send_mail, send_simple_mail
+from userprofile.util import UserWrapper
from models import CommitFest, Patch, PatchOnCommitFest, PatchHistory, Committer
from forms import PatchForm, NewPatchForm, CommentForm, CommitFestFilterForm
msg['Subject'] = 'Re: %s' % form.thread.subject
msg['To'] = settings.HACKERS_EMAIL
- msg['From'] = "%s %s <%s>" % (request.user.first_name, request.user.last_name, request.user.email)
+ msg['From'] = "%s %s <%s>" % (request.user.first_name, request.user.last_name, UserWrapper(request.user).email)
msg['Date'] = formatdate(localtime=True)
msg['User-Agent'] = 'pgcommitfest'
msg['X-cfsender'] = request.user.username
msg['References'] = '<%s> <%s>' % (form.thread.messageid, form.respid)
msg['Message-ID'] = make_msgid('pgcf')
- send_mail(request.user.email, settings.HACKERS_EMAIL, msg)
+ send_mail(UserWrapper(request.user).email, settings.HACKERS_EMAIL, msg)
PatchHistory(patch=patch, by=request.user, what='Posted %s with messageid %s' % (what, msg['Message-ID'])).save()
'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},
{'title': 'View patch', 'href': '/%s/%s/' % (cf.pk, patch.pk)}],
'title': "Add %s" % what,
- 'note': '<b>Note!</b> This form will generate an email to the public mailinglist <i>pgsql-hackers</i>, with sender set to %s!' % (request.user.email),
+ 'note': '<b>Note!</b> This form will generate an email to the public mailinglist <i>pgsql-hackers</i>, with sender set to %s!' % (UserWrapper(request.user).email),
'savebutton': 'Send %s' % what,
}, context_instance=RequestContext(request))
recipients = User.objects.filter(q).distinct()
for r in recipients:
- send_simple_mail(request.user.email, r.email, form.cleaned_data['subject'], form.cleaned_data['body'], request.user.username)
+ send_simple_mail(UserWrapper(request.user).email, r.email, form.cleaned_data['subject'], form.cleaned_data['body'], request.user.username)
messages.add_message(request, messages.INFO, "Sent email to %s" % r.email)
return HttpResponseRedirect('..')
else:
messages.add_message(request, messages.WARNING, "No recipients specified, cannot send email")
return HttpResponseRedirect('..')
- messages.add_message(request, messages.INFO, "Email will be sent from: %s" % request.user.email)
+ messages.add_message(request, messages.INFO, "Email will be sent from: %s" % UserWrapper(request.user).email)
def _user_and_mail(u):
return "%s %s (%s)" % (u.first_name, u.last_name, u.email)
+from django.template import Context
+from django.template.loader import get_template
+
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.nonmultipart import MIMENonMultipart
def send_mail(sender, receiver, fullmsg):
# Send an email, prepared as the full MIME encoded mail already
QueuedMail(sender=sender, receiver=receiver, fullmsg=fullmsg).save()
+
+def send_template_mail(sender, receiver, subject, templatename, templateattr={}, usergenerated=False):
+ send_simple_mail(sender, receiver, subject,
+ get_template(templatename).render(Context(templateattr)),
+ '__internal')
'selectable',
'commitfest',
'mailqueue',
+ 'userprofile',
)
# A sample logging configuration. The only tangible logging
# Email address to pgsql-hackers. Set to something local to test maybe?
HACKERS_EMAIL="pgsql-hackers-testing@localhost"
+# Email address for outgoing system messages
+NOTIFICATION_FROM="webmaster@postgresql.org"
+
# Load local settings overrides
try:
from local_settings import *
(r'^(?:account/)?logout/?$', 'auth.logout'),
(r'^auth_receive/$', 'auth.auth_receive'),
+ # Account management
+ (r'^account/profile/$', 'userprofile.views.userprofile'),
+ (r'^account/profile/delmail/$', 'userprofile.views.deletemail'),
+ (r'^account/profile/confirm/([0-9a-f]+)/$', 'userprofile.views.confirmemail'),
+
# Examples:
# url(r'^$', 'pgcommitfest.views.home', name='home'),
# url(r'^pgcommitfest/', include('pgcommitfest.foo.urls')),
--- /dev/null
+from django import forms
+from django.contrib.auth.models import User
+
+from models import UserProfile, UserExtraEmail
+
+class UserProfileForm(forms.ModelForm):
+ class Meta:
+ model = UserProfile
+ exclude = ('user', )
+
+ def __init__(self, user, *args, **kwargs):
+ super(UserProfileForm, self).__init__(*args, **kwargs)
+ self.user = user
+
+ self.fields['selectedemail'].empty_label=self.user.email
+ self.fields['selectedemail'].queryset=UserExtraEmail.objects.filter(user=self.user, confirmed=True)
+
+class MailForm(forms.Form):
+ email = forms.EmailField()
+ email2 = forms.EmailField(label="Repeat email")
+
+ def clean_email(self):
+ email = self.cleaned_data['email']
+
+ if User.objects.filter(email=email).exists():
+ raise forms.ValidationError("This email is already in use by another account")
+
+ 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 not self.cleaned_data.has_key('email'):
+ return self.cleaned_data['email2']
+ email1 = self.cleaned_data['email']
+ email2 = self.cleaned_data['email2']
+
+ if email1 != email2:
+ raise forms.ValidationError("Email addresses don't match")
+
+ return email2
--- /dev/null
+from django.db import models
+from django.contrib.auth.models import User
+
+class UserExtraEmail(models.Model):
+ user = models.ForeignKey(User, null=False, blank=False, db_index=True)
+ email = models.EmailField(max_length=100, null=False, blank=False, unique=True)
+ confirmed = models.BooleanField(null=False, blank=False, default=False)
+ token = models.CharField(max_length=100, null=False, blank=True)
+ tokensent = models.DateTimeField(null=False, blank=False)
+
+ def __unicode__(self):
+ return self.email
+
+ class Meta:
+ ordering = ('user', 'email')
+ unique_together = (('user', 'email'),)
+
+
+class UserProfile(models.Model):
+ user = models.ForeignKey(User, null=False, blank=False)
+ selectedemail = models.ForeignKey(UserExtraEmail, null=True, blank=True,
+ verbose_name='Sender email')
+
+ def __unicode__(self):
+ return unicode(self.user)
--- /dev/null
+Somebody, probably you, has registered this email address as a secondary
+address for the account {{user.username}} on commitfest.postgresql.org.
+
+To confirm this addition, please click on the following link:
+
+https://commitfest.postgresql.org/account/profile/confirm/{{token}}/
+
--- /dev/null
+{%extends "base.html"%}
+{%load commitfest%}
+
+{%block contents%}
+<style>
+.form-horizontal div.form-group {
+ margin-bottom: 10px;
+}
+div.form-group div.controls ul {
+ list-style-type: none;
+ margin: 0px;
+ padding: 0px;
+}
+div.form-group div.controls ul li {
+ display: inline;
+}
+div.form-group div.controls ul li label {
+ display: inline;
+ font-weight: normal;
+ vertical-align:middle;
+}
+div.form-group div.controls ul li label input {
+ display: inline;
+ vertical-align:middle;
+}
+div.form-group div.controls input[type='checkbox'] {
+ width: 10px;
+}
+
+div.controls ul.selectable-deck li.selectable-deck-item {
+ display: block;
+}
+
+div.controls ul.selectable-deck li.selectable-deck-item a.selectable-deck-remove {
+ float: none;
+ margin-left: 10px;
+}
+
+div.form-group div.controls input.threadpick-input {
+ width: 80%;
+ display: inline;
+}
+</style>
+<form class="form-horizontal {{extraformclass}}" method="POST" action=".">{%csrf_token%}
+{%if form.errors%}
+ <div class="alert">Please correct the errors below, and re-submit the form.</div>
+{%endif%}
+{%if form.non_field_errors%}
+ <div class="alert alert-danger">{{form.non_field_errors}}</div>
+{%endif%}
+ {%for field in form%}
+ {%if not field.is_hidden%}
+ <div class="form-group">
+ {{field|label_class:"control-label col-lg-1"}}
+ <div class="col-lg-11 controls">
+ {%if field.errors %}
+ {%for e in field.errors%}
+ <div class="alert alert-danger">{{e}}</div>
+ {%endfor%}
+ {%endif%}
+{{field|field_class:"form-control"}}
+{%if field.help_text%}<br/>{{field.help_text|safe}}{%endif%}</div>
+ </div>
+ {%else%}
+{{field}}
+ {%endif%}
+{%endfor%}
+ <div class="form-group">
+ <div class="col-lg-12">
+ <div class="control"><input type="submit" class="btn btn-default" name="submit" value="Save"></div>
+ </div>
+ </div>
+</form>
+
+<h2>Extra email addresses</h2>
+<p>
+The following extra email addresses are registered for your account:
+</p>
+<ul>
+{%for e in extramails%}
+ <li>{{e.email}}{%if not e.confirmed%} (<i>Pending confirmation</i>){%endif%} <a href="delmail/?{{e.id}}">delete</a></li>
+{%endfor%}
+</ul>
+
+<h3>Add email</h3>
+<form class="form-horizontal" method="post" action=".">{%csrf_token%}
+{%if mailform.errors%}
+ <div class="alert">Please correct the errors below, and re-submit the form.</div>
+{%endif%}
+{%if mailform.non_field_errors%}
+ <div class="alert alert-danger">{{mailform.non_field_errors}}</div>
+{%endif%}
+ {%for field in mailform%}
+ <div class="form-group">
+ {{field|label_class:"control-label col-lg-1"}}
+ <div class="col-lg-11 controls">
+ {%if field.errors %}
+ {%for e in field.errors%}
+ <div class="alert alert-danger">{{e}}</div>
+ {%endfor%}
+ {%endif%}
+{{field|field_class:"form-control"}}
+{%if field.help_text%}<br/>{{field.help_text|safe}}{%endif%}</div>
+ </div>
+{%endfor%}
+
+ <div class="form-group">
+ <div class="col-lg-12">
+ <div class="control"><input type="submit" class="btn btn-default" name="submit" value="Add email"></div>
+ </div>
+ </div>
+</form>
+{%endblock%}
--- /dev/null
+from Crypto.Hash import SHA256
+from Crypto import Random
+
+from models import UserProfile
+
+def generate_random_token():
+ """
+ Generate a random token of 64 characters. This token will be
+ generated using a strong random number, and then hex encoded to make
+ sure all characters are safe to put in emails and URLs.
+ """
+ s = SHA256.new()
+ r = Random.new()
+ s.update(r.read(250))
+ return s.hexdigest()
+
+
+class UserWrapper(object):
+ def __init__(self, user):
+ self.user = user
+
+ @property
+ def email(self):
+ try:
+ up = UserProfile.objects.get(user=self.user)
+ if up.selectedemail and up.selectedemail.confirmed:
+ return up.selectedemail.email
+ else:
+ return self.user.email
+ except UserProfile.DoesNotExist:
+ return self.user.email
--- /dev/null
+from django.shortcuts import render_to_response
+from django.http import HttpResponseRedirect
+from django.template import RequestContext
+from django.db import transaction
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required
+from django.conf import settings
+
+from datetime import datetime
+
+from mailqueue.util import send_template_mail
+
+from models import UserProfile, UserExtraEmail
+from forms import UserProfileForm, MailForm
+from util import generate_random_token
+
+@login_required
+@transaction.commit_on_success
+def userprofile(request):
+ (profile, created) = UserProfile.objects.get_or_create(user=request.user)
+ form = mailform = None
+
+ if request.method == 'POST':
+ if request.POST['submit'] == 'Save':
+ form = UserProfileForm(request.user, request.POST, instance=profile)
+ if form.is_valid():
+ form.save()
+ messages.add_message(request, messages.INFO, "User profile saved.")
+ return HttpResponseRedirect('.')
+ elif request.POST['submit'] == 'Add email':
+ mailform = MailForm(request.POST)
+ if mailform.is_valid():
+ m = UserExtraEmail(user=request.user,
+ email=mailform.cleaned_data['email'],
+ confirmed=False,
+ token=generate_random_token(),
+ tokensent=datetime.now())
+ m.save()
+ send_template_mail(settings.NOTIFICATION_FROM,
+ m.email,
+ 'Your email address for commitfest.postgresql.org',
+ 'extra_email_mail.txt',
+ {'token': m.token, 'user': m.user})
+ messages.info(request, "A confirmation token has been sent to %s" % m.email)
+ return HttpResponseRedirect('.')
+ else:
+ messages.error(request, "Invalid submit button pressed! Nothing saved.")
+ return HttpResponseRedirect('.')
+
+ if not form:
+ form = UserProfileForm(request.user, instance=profile)
+ if not mailform:
+ mailform = MailForm()
+
+ extramails = UserExtraEmail.objects.filter(user=request.user)
+
+ return render_to_response('userprofileform.html', {
+ 'form': form,
+ 'extramails': extramails,
+ 'mailform': mailform,
+ }, context_instance=RequestContext(request))
+
+@login_required
+@transaction.commit_on_success
+def deletemail(request):
+ try:
+ id = int(request.META['QUERY_STRING'])
+ except ValueError:
+ messages.error(request, "Invalid format of id in query string")
+ return HttpResponseRedirect('../')
+
+ try:
+ e = UserExtraEmail.objects.get(user=request.user, id=id)
+ except UserExtraEmail.DoesNotExist:
+ messages.error(request, "Specified email address does not exist on this user")
+ return HttpResponseRedirect('../')
+
+ messages.info(request, "Email address %s deleted." % e.email)
+ e.delete()
+ return HttpResponseRedirect('../')
+
+@login_required
+@transaction.commit_on_success
+def confirmemail(request, tokenhash):
+ try:
+ e = UserExtraEmail.objects.get(user=request.user, token=tokenhash)
+ if e.confirmed:
+ messages.warning(request, "This email address has already been confirmed.")
+ else:
+ # Ok, it's not confirmed. So let's do that now
+ e.confirmed = True
+ e.token = ''
+ e.save()
+ messages.info(request, "Email address %s added to profile." % e.email)
+ except UserExtraEmail.DoesNotExist:
+ messages.error(request, "Token %s was not found for your user. It may be because it has already been used?" % tokenhash)
+
+ return HttpResponseRedirect("../../")