from django import forms
from django.forms import ValidationError
+from django.forms.widgets import HiddenInput
from django.db.models import Q
from django.contrib.auth.models import User
if '1' in self.cleaned_data[fn] and not '0' in self.cleaned_data[fn]:
self.errors[fn] = (('Cannot pass a test without performing it!'),)
return self.cleaned_data
+
+class BulkEmailForm(forms.Form):
+ reviewers = forms.CharField(required=False, widget=HiddenInput())
+ authors = forms.CharField(required=False, widget=HiddenInput())
+ subject = forms.CharField(required=True)
+ body = forms.CharField(required=True, widget=forms.Textarea)
+ confirm = forms.BooleanField(required=True, label='Check to confirm sending')
+
+ def __init__(self, *args, **kwargs):
+ super(BulkEmailForm, self).__init__(*args, **kwargs)
<th>Reviewers</th>
<th>{%if p.is_open%}<a href="#" style="color:#333333;" onclick="return sortpatches(1);">Latest activity</a>{%if sortkey = 1%}<div style="float:right;"><i class="icon-arrow-down"></i></div>{%endif%}{%else%}Latest activity{%endif%}</th>
<th>{%if p.is_open%}<a href="#" style="color:#333333;" onclick="return sortpatches(2);">Latest mail</a>{%if sortkey = 2%}<div style="float:right;"><i class="icon-arrow-down"></i></div>{%endif%}{%else%}Latest mail{%endif%}</th>
+{%if user.is_staff%}
+ <th>Select</th>
+{%endif%}
</tr>
</thead>
<tbody>
{%if grouping%}
{%ifchanged p.topic%}
- <tr><th colspan="6">{{p.topic}}</th></tr>
+ <tr><th colspan="{%if user.is_staff%}7{%else%}6{%endif%}">{{p.topic}}</th></tr>
{%endifchanged%}
{%endif%}
<tr>
<td>{{p.reviewer_names|default:''}}</td>
<td style="white-space: nowrap;">{{p.modified|date:"Y-m-d"}}<br/>{{p.modified|date:"H:i"}}</td>
<td style="white-space: nowrap;">{{p.lastmail|date:"Y-m-d"}}<br/>{{p.lastmail|date:"H:i"}}</td>
+{%if user.is_staff%}
+ <td style="white-space: nowrap;"><input type="checkbox" class="sender_checkbox" id="send_authors_{{p.id}}">Author<br/><input type="checkbox" class="sender_checkbox" id="send_reviewers_{{p.id}}">Reviewer</td>
+{%endif%}
</tr>
{%endfor%}
</tbody>
</table>
+<div>
{%if cf.isopen or user.is_staff %}
-<p>
<a class="btn btn-default" href="new/">New patch</a>
-</p>
{%endif%}
+{%if user.is_staff%}
+ <div class="btn-group dropup">
+ <button type="button" class="btn btn-default dropdown-toggle " data-toggle="dropdown" href="#">Send mail <span class="caret"></span></button>
+ <ul class="dropdown-menu">
+ <li><a href="javascript:send_selected()">Selected</a></li>
+ <li><a href="send_email/?reviewers={{openpatchids|join:","}}">All reviewers (open patches)</a></li>
+ <li><a href="send_email/?authors={{openpatchids|join:","}}">All authors (open patches)</a></li>
+ <li><a href="send_email/?authors={{openpatchids|join:","}}&reviewers={{openpatchids|join:","}}">All authors and reviewers (open patches)</a></li>
+ </ul>
+ </div>
+{%endif%}
+</div>
+{%endblock%}
+
+{%block morescript%}
+<script language="javascript">
+{%if user.is_staff%}
+ function send_selected() {
+ var authors = [];
+ var reviewers = [];
+ $('input.sender_checkbox').each(function(index, el) {
+ if (el.checked) {
+ if (el.id.indexOf('send_authors_') == 0) {
+ authors.push(el.id.substring(13));
+ } else {
+ reviewers.push(el.id.substring(15));
+ }
+ }
+ });
+ if (authors.length==0 && reviewers.length==0) {
+ alert('Nothing to send.');
+ return;
+ }
+ document.location.href = 'send_email/?authors=' + authors.join(',') + '&reviewers=' + reviewers.join(',');
+ }
+{%endif%}
+</script>
{%endblock%}
from django.db.models import Q
from django.contrib import messages
from django.contrib.auth.decorators import login_required
+from django.contrib.auth.models import User
import settings
from email.mime.text import MIMEText
from email.utils import formatdate, make_msgid
-from mailqueue.util import send_mail
+from mailqueue.util import send_mail, send_simple_mail
from models import CommitFest, Patch, PatchOnCommitFest, PatchHistory, Committer
from forms import PatchForm, NewPatchForm, CommentForm, CommitFestFilterForm
+from forms import BulkEmailForm
from ajax import doAttachThread
def home(request):
# Redirect to get rid of the ugly url
return HttpResponseRedirect('/%s/' % cf.id)
- patches = cf.patch_set.filter(q).select_related().extra(select={
+ patches = list(cf.patch_set.filter(q).select_related().extra(select={
'status':'commitfest_patchoncommitfest.status',
'author_names':"SELECT string_agg(first_name || ' ' || last_name || ' (' || username || ')', ', ') FROM auth_user INNER JOIN commitfest_patch_authors cpa ON cpa.user_id=auth_user.id WHERE cpa.patch_id=commitfest_patch.id",
'reviewer_names':"SELECT string_agg(first_name || ' ' || last_name || ' (' || username || ')', ', ') FROM auth_user INNER JOIN commitfest_patch_reviewers cpr ON cpr.user_id=auth_user.id WHERE cpr.patch_id=commitfest_patch.id",
'is_open':'commitfest_patchoncommitfest.status IN (%s)' % ','.join([str(x) for x in PatchOnCommitFest.OPEN_STATUSES]),
- }).order_by(*ordering)
+ }).order_by(*ordering))
# Generates a fairly expensive query, which we shouldn't do unless
# the user is logged in. XXX: Figure out how to avoid doing that..
'title': cf.title,
'grouping': sortkey==0,
'sortkey': sortkey,
+ 'openpatchids': [p.id for p in patches if p.is_open],
}, context_instance=RequestContext(request))
def patch(request, cfid, patchid):
PatchHistory(patch=patch, by=request.user, what='Removed self from committers').save()
patch.save()
return HttpResponseRedirect('../../')
+
+@login_required
+@transaction.commit_on_success
+def send_email(request, cfid):
+ cf = get_object_or_404(CommitFest, pk=cfid)
+ if not request.user.is_staff:
+ raise Http404("Only CF managers can do that.")
+
+ if request.method == 'POST':
+ authoridstring = request.POST['authors']
+ revieweridstring = request.POST['reviewers']
+ form = BulkEmailForm(data=request.POST)
+ if form.is_valid():
+ q = Q()
+ if authoridstring:
+ q = q | Q(patch_author__in=[int(x) for x in authoridstring.split(',')])
+ if revieweridstring:
+ q = q | Q(patch_reviewer__in=[int(x) for x in revieweridstring.split(',')])
+
+ 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'])
+ messages.add_message(request, messages.INFO, "Sent email to %s" % r.email)
+ return HttpResponseRedirect('..')
+ else:
+ authoridstring = request.GET.get('authors', None)
+ revieweridstring = request.GET.get('reviewers', None)
+ form = BulkEmailForm(initial={'authors': authoridstring, 'reviewers': revieweridstring})
+
+ if authoridstring:
+ authors = list(User.objects.filter(patch_author__in=[int(x) for x in authoridstring.split(',')]).distinct())
+ else:
+ authors = []
+ if revieweridstring:
+ reviewers = list(User.objects.filter(patch_reviewer__in=[int(x) for x in revieweridstring.split(',')]).distinct())
+ else:
+ reviewers = []
+
+ messages.add_message(request, messages.INFO, "Email will be sent from: %s" % request.user.email)
+ def _user_and_mail(u):
+ return "%s %s (%s)" % (u.first_name, u.last_name, u.email)
+
+ if len(authors):
+ messages.add_message(request, messages.INFO, "The email will be sent to the following authors: %s" % ", ".join([_user_and_mail(u) for u in authors]))
+ if len(reviewers):
+ messages.add_message(request, messages.INFO, "The email will be sent to the following reviewers: %s" % ", ".join([_user_and_mail(u) for u in reviewers]))
+
+ return render_to_response('base_form.html', {
+ 'cf': cf,
+ 'form': form,
+ 'title': 'Send email',
+ 'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},],
+ }, context_instance=RequestContext(request))