Convert all spaces to tabs
authorMagnus Hagander <magnus@hagander.net>
Tue, 5 Feb 2019 22:01:05 +0000 (23:01 +0100)
committerMagnus Hagander <magnus@hagander.net>
Tue, 5 Feb 2019 22:01:05 +0000 (23:01 +0100)
22 files changed:
pgcommitfest/auth.py
pgcommitfest/commitfest/admin.py
pgcommitfest/commitfest/ajax.py
pgcommitfest/commitfest/feeds.py
pgcommitfest/commitfest/forms.py
pgcommitfest/commitfest/lookups.py
pgcommitfest/commitfest/management/commands/send_notifications.py
pgcommitfest/commitfest/migrations/0003_withdrawn_status.py
pgcommitfest/commitfest/models.py
pgcommitfest/commitfest/reports.py
pgcommitfest/commitfest/templatetags/commitfest.py
pgcommitfest/commitfest/util.py
pgcommitfest/commitfest/views.py
pgcommitfest/commitfest/widgets.py
pgcommitfest/mailqueue/models.py
pgcommitfest/mailqueue/util.py
pgcommitfest/settings.py
pgcommitfest/userprofile/admin.py
pgcommitfest/userprofile/forms.py
pgcommitfest/userprofile/models.py
pgcommitfest/userprofile/util.py
pgcommitfest/userprofile/views.py

index 13ada6a0cc36933efc6773c8d9e602606a631525..1073b5e323695435d6ce5b8f53489cd787115673 100644 (file)
@@ -36,10 +36,10 @@ from Crypto import Random
 import time
 
 class AuthBackend(ModelBackend):
-       # We declare a fake backend that always fails direct authentication -
-       # since we should never be using direct authentication in the first place!
-       def authenticate(self, username=None, password=None):
-               raise Exception("Direct authentication not supported")
+    # We declare a fake backend that always fails direct authentication -
+    # since we should never be using direct authentication in the first place!
+    def authenticate(self, username=None, password=None):
+        raise Exception("Direct authentication not supported")
 
 
 ####
@@ -48,85 +48,85 @@ class AuthBackend(ModelBackend):
 
 # Handle login requests by sending them off to the main site
 def login(request):
-       if request.GET.has_key('next'):
-               # Put together an url-encoded dict of parameters we're getting back,
-               # including a small nonce at the beginning to make sure it doesn't
-               # encrypt the same way every time.
-               s = "t=%s&%s" % (int(time.time()), urllib.urlencode({'r': request.GET['next']}))
-               # Now encrypt it
-               r = Random.new()
-               iv = r.read(16)
-               encryptor = AES.new(SHA.new(settings.SECRET_KEY).digest()[:16], AES.MODE_CBC, iv)
-               cipher = encryptor.encrypt(s + ' ' * (16-(len(s) % 16))) # pad to 16 bytes
-
-               return HttpResponseRedirect("%s?d=%s$%s" % (
-                               settings.PGAUTH_REDIRECT,
-                           base64.b64encode(iv, "-_"),
-                           base64.b64encode(cipher, "-_"),
-                               ))
-       else:
-               return HttpResponseRedirect(settings.PGAUTH_REDIRECT)
+    if request.GET.has_key('next'):
+        # Put together an url-encoded dict of parameters we're getting back,
+        # including a small nonce at the beginning to make sure it doesn't
+        # encrypt the same way every time.
+        s = "t=%s&%s" % (int(time.time()), urllib.urlencode({'r': request.GET['next']}))
+        # Now encrypt it
+        r = Random.new()
+        iv = r.read(16)
+        encryptor = AES.new(SHA.new(settings.SECRET_KEY).digest()[:16], AES.MODE_CBC, iv)
+        cipher = encryptor.encrypt(s + ' ' * (16-(len(s) % 16))) # pad to 16 bytes
+
+        return HttpResponseRedirect("%s?d=%s$%s" % (
+                settings.PGAUTH_REDIRECT,
+                base64.b64encode(iv, "-_"),
+                base64.b64encode(cipher, "-_"),
+                ))
+    else:
+        return HttpResponseRedirect(settings.PGAUTH_REDIRECT)
 
 # Handle logout requests by logging out of this site and then
 # redirecting to log out from the main site as well.
 def logout(request):
-       if request.user.is_authenticated():
-               django_logout(request)
-       return HttpResponseRedirect("%slogout/" % settings.PGAUTH_REDIRECT)
+    if request.user.is_authenticated():
+        django_logout(request)
+    return HttpResponseRedirect("%slogout/" % settings.PGAUTH_REDIRECT)
 
 # Receive an authentication response from the main website and try
 # to log the user in.
 def auth_receive(request):
-       if request.GET.has_key('s') and request.GET['s'] == "logout":
-               # This was a logout request
-               return HttpResponseRedirect('/')
-
-       if not request.GET.has_key('i'):
-               return HttpResponse("Missing IV in url!", status=400)
-       if not request.GET.has_key('d'):
-               return HttpResponse("Missing data in url!", status=400)
-
-       # Set up an AES object and decrypt the data we received
-       decryptor = AES.new(base64.b64decode(settings.PGAUTH_KEY),
-                                               AES.MODE_CBC,
-                                               base64.b64decode(str(request.GET['i']), "-_"))
-       s = decryptor.decrypt(base64.b64decode(str(request.GET['d']), "-_")).rstrip(' ')
-
-       # Now un-urlencode it
-       try:
-               data = urlparse.parse_qs(s, strict_parsing=True)
-       except ValueError:
-               return HttpResponse("Invalid encrypted data received.", status=400)
-
-       # Check the timestamp in the authentication
-       if (int(data['t'][0]) < time.time() - 10):
-               return HttpResponse("Authentication token too old.", status=400)
-
-       # Update the user record (if any)
-       try:
-               user = User.objects.get(username=data['u'][0])
-               # User found, let's see if any important fields have changed
-               changed = False
-               if user.first_name != data['f'][0]:
-                       user.first_name = data['f'][0]
-                       changed = True
-               if user.last_name != data['l'][0]:
-                       user.last_name = data['l'][0]
-                       changed = True
-               if user.email != data['e'][0]:
-                       user.email = data['e'][0]
-                       changed= True
-               if changed:
-                       user.save()
-       except User.DoesNotExist:
-               # User not found, create it!
-
-               # NOTE! We have some legacy users where there is a user in
-               # the database with a different userid. Instead of trying to
-               # somehow fix that live, give a proper error message and
-               # have somebody look at it manually.
-               if User.objects.filter(email=data['e'][0]).exists():
-                       return HttpResponse("""A user with email %s already exists, but with
+    if request.GET.has_key('s') and request.GET['s'] == "logout":
+        # This was a logout request
+        return HttpResponseRedirect('/')
+
+    if not request.GET.has_key('i'):
+        return HttpResponse("Missing IV in url!", status=400)
+    if not request.GET.has_key('d'):
+        return HttpResponse("Missing data in url!", status=400)
+
+    # Set up an AES object and decrypt the data we received
+    decryptor = AES.new(base64.b64decode(settings.PGAUTH_KEY),
+                        AES.MODE_CBC,
+                        base64.b64decode(str(request.GET['i']), "-_"))
+    s = decryptor.decrypt(base64.b64decode(str(request.GET['d']), "-_")).rstrip(' ')
+
+    # Now un-urlencode it
+    try:
+        data = urlparse.parse_qs(s, strict_parsing=True)
+    except ValueError:
+        return HttpResponse("Invalid encrypted data received.", status=400)
+
+    # Check the timestamp in the authentication
+    if (int(data['t'][0]) < time.time() - 10):
+        return HttpResponse("Authentication token too old.", status=400)
+
+    # Update the user record (if any)
+    try:
+        user = User.objects.get(username=data['u'][0])
+        # User found, let's see if any important fields have changed
+        changed = False
+        if user.first_name != data['f'][0]:
+            user.first_name = data['f'][0]
+            changed = True
+        if user.last_name != data['l'][0]:
+            user.last_name = data['l'][0]
+            changed = True
+        if user.email != data['e'][0]:
+            user.email = data['e'][0]
+            changed= True
+        if changed:
+            user.save()
+    except User.DoesNotExist:
+        # User not found, create it!
+
+        # NOTE! We have some legacy users where there is a user in
+        # the database with a different userid. Instead of trying to
+        # somehow fix that live, give a proper error message and
+        # have somebody look at it manually.
+        if User.objects.filter(email=data['e'][0]).exists():
+            return HttpResponse("""A user with email %s already exists, but with
 a different username than %s.
 
 This is almost certainly caused by some legacy data in our database.
@@ -137,39 +137,39 @@ for you.
 We apologize for the inconvenience.
 """ % (data['e'][0], data['u'][0]), content_type='text/plain')
 
-               user = User(username=data['u'][0],
-                                       first_name=data['f'][0],
-                                       last_name=data['l'][0],
-                                       email=data['e'][0],
-                                       password='setbypluginnotasha1',
-                                       )
-               user.save()
-
-       # Ok, we have a proper user record. Now tell django that
-       # we're authenticated so it persists it in the session. Before
-       # we do that, we have to annotate it with the backend information.
-       user.backend = "%s.%s" % (AuthBackend.__module__, AuthBackend.__name__)
-       django_login(request, user)
-
-       # Finally, check of we have a data package that tells us where to
-       # redirect the user.
-       if data.has_key('d'):
-               (ivs, datas) = data['d'][0].split('$')
-               decryptor = AES.new(SHA.new(settings.SECRET_KEY).digest()[:16],
-                                                       AES.MODE_CBC,
-                                                       base64.b64decode(ivs, "-_"))
-               s = decryptor.decrypt(base64.b64decode(datas, "-_")).rstrip(' ')
-               try:
-                       rdata = urlparse.parse_qs(s, strict_parsing=True)
-               except ValueError:
-                       return HttpResponse("Invalid encrypted data received.", status=400)
-               if rdata.has_key('r'):
-                       # Redirect address
-                       return HttpResponseRedirect(rdata['r'][0])
-       # No redirect specified, see if we have it in our settings
-       if hasattr(settings, 'PGAUTH_REDIRECT_SUCCESS'):
-               return HttpResponseRedirect(settings.PGAUTH_REDIRECT_SUCCESS)
-       return HttpResponse("Authentication successful, but don't know where to redirect!", status=500)
+        user = User(username=data['u'][0],
+                    first_name=data['f'][0],
+                    last_name=data['l'][0],
+                    email=data['e'][0],
+                    password='setbypluginnotasha1',
+                    )
+        user.save()
+
+    # Ok, we have a proper user record. Now tell django that
+    # we're authenticated so it persists it in the session. Before
+    # we do that, we have to annotate it with the backend information.
+    user.backend = "%s.%s" % (AuthBackend.__module__, AuthBackend.__name__)
+    django_login(request, user)
+
+    # Finally, check of we have a data package that tells us where to
+    # redirect the user.
+    if data.has_key('d'):
+        (ivs, datas) = data['d'][0].split('$')
+        decryptor = AES.new(SHA.new(settings.SECRET_KEY).digest()[:16],
+                            AES.MODE_CBC,
+                            base64.b64decode(ivs, "-_"))
+        s = decryptor.decrypt(base64.b64decode(datas, "-_")).rstrip(' ')
+        try:
+            rdata = urlparse.parse_qs(s, strict_parsing=True)
+        except ValueError:
+            return HttpResponse("Invalid encrypted data received.", status=400)
+        if rdata.has_key('r'):
+            # Redirect address
+            return HttpResponseRedirect(rdata['r'][0])
+    # No redirect specified, see if we have it in our settings
+    if hasattr(settings, 'PGAUTH_REDIRECT_SUCCESS'):
+        return HttpResponseRedirect(settings.PGAUTH_REDIRECT_SUCCESS)
+    return HttpResponse("Authentication successful, but don't know where to redirect!", status=500)
 
 
 # Perform a search in the central system. Note that the results are returned as an
@@ -180,26 +180,26 @@ We apologize for the inconvenience.
 # Unlike the authentication, searching does not involve the browser - we just make
 # a direct http call.
 def user_search(searchterm=None, userid=None):
-       # If upsteam isn't responding quickly, it's not going to respond at all, and
-       # 10 seconds is already quite long.
-       socket.setdefaulttimeout(10)
-       if userid:
-               q = {'u': userid}
-       else:
-               q = {'s': searchterm}
-
-       u = urllib.urlopen('%ssearch/?%s' % (
-               settings.PGAUTH_REDIRECT,
-               urllib.urlencode(q),
-               ))
-       (ivs, datas) = u.read().split('&')
-       u.close()
-
-       # Decryption time
-       decryptor = AES.new(base64.b64decode(settings.PGAUTH_KEY),
-                                               AES.MODE_CBC,
-                                               base64.b64decode(ivs, "-_"))
-       s = decryptor.decrypt(base64.b64decode(datas, "-_")).rstrip(' ')
-       j = json.loads(s)
-
-       return j
+    # If upsteam isn't responding quickly, it's not going to respond at all, and
+    # 10 seconds is already quite long.
+    socket.setdefaulttimeout(10)
+    if userid:
+        q = {'u': userid}
+    else:
+        q = {'s': searchterm}
+
+    u = urllib.urlopen('%ssearch/?%s' % (
+        settings.PGAUTH_REDIRECT,
+        urllib.urlencode(q),
+        ))
+    (ivs, datas) = u.read().split('&')
+    u.close()
+
+    # Decryption time
+    decryptor = AES.new(base64.b64decode(settings.PGAUTH_KEY),
+                        AES.MODE_CBC,
+                        base64.b64decode(ivs, "-_"))
+    s = decryptor.decrypt(base64.b64decode(datas, "-_")).rstrip(' ')
+    j = json.loads(s)
+
+    return j
index 05fe3eda8b3758ca7f7c798007913c2fd110b382..232ba30b7f5bbd0f7ea5ea155910387fe8f8b5c4 100644 (file)
@@ -3,19 +3,19 @@ from django.contrib import admin
 from models import *
 
 class CommitterAdmin(admin.ModelAdmin):
-       list_display = ('user', 'active')
+    list_display = ('user', 'active')
 
 class PatchOnCommitFestInline(admin.TabularInline):
-       model = PatchOnCommitFest
-       extra=1
+    model = PatchOnCommitFest
+    extra=1
 
 class PatchAdmin(admin.ModelAdmin):
-       inlines = (PatchOnCommitFestInline,)
-       list_display = ('name', )
-#      list_filter = ('commitfests_set__commitfest__name',)
+    inlines = (PatchOnCommitFestInline,)
+    list_display = ('name', )
+#    list_filter = ('commitfests_set__commitfest__name',)
 
 class MailThreadAttachmentAdmin(admin.ModelAdmin):
-       list_display = ('date', 'author', 'messageid', 'mailthread',)
+    list_display = ('date', 'author', 'messageid', 'mailthread',)
 
 admin.site.register(Committer, CommitterAdmin)
 admin.site.register(CommitFest)
index 3ec76eabcdf7eb9baa455dbc49938d5aa575f4e2..b8317f5087b0d92e2d806cfae8d68c0450d06889 100644 (file)
@@ -12,249 +12,249 @@ import json
 from pgcommitfest.auth import user_search
 
 class HttpResponseServiceUnavailable(HttpResponse): 
-       status_code = 503
+    status_code = 503
 
 class Http503(Exception):
-       pass
+    pass
 
 from models import CommitFest, Patch, MailThread, MailThreadAttachment
 from models import MailThreadAnnotation, PatchHistory
 
 def _archivesAPI(suburl, params=None):
-       try:
-               resp = requests.get("http{0}://{1}:{2}{3}".format(settings.ARCHIVES_PORT == 443 and 's' or '',
-                                                                                                                 settings.ARCHIVES_SERVER,
-                                                                                                                 settings.ARCHIVES_PORT,
-                                                                                                                 suburl),
-                                                       params=params,
-                                                       headers={
-                                                               'Host': settings.ARCHIVES_HOST,
-                                                       },
-                                                       timeout=settings.ARCHIVES_TIMEOUT,
-               )
-               if resp.status_code != 200:
-                       if resp.status_code == 404:
-                               raise Http404()
-                       raise Exception("JSON call failed: %s" % resp.status_code)
-
-               return resp.json()
-       except Http404:
-               raise
-       except Exception as e:
-               raise Http503("Failed to communicate with archives backend: %s" % e)
+    try:
+        resp = requests.get("http{0}://{1}:{2}{3}".format(settings.ARCHIVES_PORT == 443 and 's' or '',
+                                                          settings.ARCHIVES_SERVER,
+                                                          settings.ARCHIVES_PORT,
+                                                          suburl),
+                            params=params,
+                            headers={
+                                'Host': settings.ARCHIVES_HOST,
+                            },
+                            timeout=settings.ARCHIVES_TIMEOUT,
+        )
+        if resp.status_code != 200:
+            if resp.status_code == 404:
+                raise Http404()
+            raise Exception("JSON call failed: %s" % resp.status_code)
+
+        return resp.json()
+    except Http404:
+        raise
+    except Exception as e:
+        raise Http503("Failed to communicate with archives backend: %s" % e)
 
 def getThreads(request):
-       search = request.GET.has_key('s') and request.GET['s'] or None
-       if request.GET.has_key('a') and request.GET['a'] == "1":
-               attachonly = 1
-       else:
-               attachonly = 0
+    search = request.GET.has_key('s') and request.GET['s'] or None
+    if request.GET.has_key('a') and request.GET['a'] == "1":
+        attachonly = 1
+    else:
+        attachonly = 0
 
-       # Make a JSON api call to the archives server
-       params = {'n': 100, 'a': attachonly}
-       if search:
-               params['s'] = search
+    # Make a JSON api call to the archives server
+    params = {'n': 100, 'a': attachonly}
+    if search:
+        params['s'] = search
 
-       r = _archivesAPI('/list/pgsql-hackers/latest.json', params)
-       return sorted(r, key=lambda x: x['date'], reverse=True)
+    r = _archivesAPI('/list/pgsql-hackers/latest.json', params)
+    return sorted(r, key=lambda x: x['date'], reverse=True)
 
 def getMessages(request):
-       threadid = request.GET['t']
+    threadid = request.GET['t']
 
-       thread = MailThread.objects.get(pk=threadid)
+    thread = MailThread.objects.get(pk=threadid)
 
-       # Always make a call over to the archives api
-       r = _archivesAPI('/message-id.json/%s' % thread.messageid)
-       return sorted(r, key=lambda x: x['date'], reverse=True)
+    # Always make a call over to the archives api
+    r = _archivesAPI('/message-id.json/%s' % thread.messageid)
+    return sorted(r, key=lambda x: x['date'], reverse=True)
 
 def refresh_single_thread(thread):
-       r = sorted(_archivesAPI('/message-id.json/%s' % thread.messageid), key=lambda x: x['date'])
-       if thread.latestmsgid != r[-1]['msgid']:
-               # There is now a newer mail in the thread!
-               thread.latestmsgid = r[-1]['msgid']
-               thread.latestmessage = r[-1]['date']
-               thread.latestauthor = r[-1]['from']
-               thread.latestsubject = r[-1]['subj']
-               thread.save()
-               parse_and_add_attachments(r, thread)
-               # Potentially update the last mail date - if there wasn't already a mail on each patch
-               # from a *different* thread that had an earlier date.
-               for p in thread.patches.filter(lastmail__lt=thread.latestmessage):
-                       p.lastmail = thread.latestmessage
-                       p.save()
+    r = sorted(_archivesAPI('/message-id.json/%s' % thread.messageid), key=lambda x: x['date'])
+    if thread.latestmsgid != r[-1]['msgid']:
+        # There is now a newer mail in the thread!
+        thread.latestmsgid = r[-1]['msgid']
+        thread.latestmessage = r[-1]['date']
+        thread.latestauthor = r[-1]['from']
+        thread.latestsubject = r[-1]['subj']
+        thread.save()
+        parse_and_add_attachments(r, thread)
+        # Potentially update the last mail date - if there wasn't already a mail on each patch
+        # from a *different* thread that had an earlier date.
+        for p in thread.patches.filter(lastmail__lt=thread.latestmessage):
+            p.lastmail = thread.latestmessage
+            p.save()
 
 @transaction.atomic
 def annotateMessage(request):
-       thread = get_object_or_404(MailThread, pk=int(request.POST['t']))
-       msgid = request.POST['msgid']
-       msg = request.POST['msg']
-
-       # Get the subject, author and date from the archives
-       # We only have an API call to get the whole thread right now, so
-       # do that, and then find our entry in it.
-       r = _archivesAPI('/message-id.json/%s' % thread.messageid)
-       for m in r:
-               if m['msgid'] == msgid:
-                       annotation = MailThreadAnnotation(mailthread=thread,
-                                                                                         user=request.user,
-                                                                                         msgid=msgid,
-                                                                                         annotationtext=msg,
-                                                                                         mailsubject=m['subj'],
-                                                                                         maildate=m['date'],
-                                                                                         mailauthor=m['from'])
-                       annotation.save()
-
-                       for p in thread.patches.all():
-                               PatchHistory(patch=p, by=request.user, what='Added annotation "%s" to %s' % (msg, msgid)).save_and_notify()
-                               p.set_modified()
-                               p.save()
-
-                       return 'OK'
-       return 'Message not found in thread!'
+    thread = get_object_or_404(MailThread, pk=int(request.POST['t']))
+    msgid = request.POST['msgid']
+    msg = request.POST['msg']
+
+    # Get the subject, author and date from the archives
+    # We only have an API call to get the whole thread right now, so
+    # do that, and then find our entry in it.
+    r = _archivesAPI('/message-id.json/%s' % thread.messageid)
+    for m in r:
+        if m['msgid'] == msgid:
+            annotation = MailThreadAnnotation(mailthread=thread,
+                                              user=request.user,
+                                              msgid=msgid,
+                                              annotationtext=msg,
+                                              mailsubject=m['subj'],
+                                              maildate=m['date'],
+                                              mailauthor=m['from'])
+            annotation.save()
+
+            for p in thread.patches.all():
+                PatchHistory(patch=p, by=request.user, what='Added annotation "%s" to %s' % (msg, msgid)).save_and_notify()
+                p.set_modified()
+                p.save()
+
+            return 'OK'
+    return 'Message not found in thread!'
 
 @transaction.atomic
 def deleteAnnotation(request):
-       annotation = get_object_or_404(MailThreadAnnotation, pk=request.POST['id'])
+    annotation = get_object_or_404(MailThreadAnnotation, pk=request.POST['id'])
 
-       for p in annotation.mailthread.patches.all():
-               PatchHistory(patch=p, by=request.user, what='Deleted annotation "%s" from %s' % (annotation.annotationtext, annotation.msgid)).save_and_notify()
-               p.set_modified()
-               p.save()
+    for p in annotation.mailthread.patches.all():
+        PatchHistory(patch=p, by=request.user, what='Deleted annotation "%s" from %s' % (annotation.annotationtext, annotation.msgid)).save_and_notify()
+        p.set_modified()
+        p.save()
 
-       annotation.delete()
+    annotation.delete()
 
-       return 'OK'
+    return 'OK'
 
 def parse_and_add_attachments(threadinfo, mailthread):
-       for t in threadinfo:
-               if len(t['atts']):
-                       # One or more attachments. For now, we're only actually going
-                       # to store and process the first one, even though the API gets
-                       # us all of them.
-                       MailThreadAttachment.objects.get_or_create(mailthread=mailthread,
-                                                                                                          messageid=t['msgid'],
-                                                                                                          defaults={
-                                                                                                                  'date': t['date'],
-                                                                                                                  'author': t['from'],
-                                                                                                                  'attachmentid': t['atts'][0]['id'],
-                                                                                                                  'filename': t['atts'][0]['name'],
-                                                                                                          })
-               # In theory we should remove objects if they don't have an
-               # attachment, but how could that ever happen? Ignore for now.
+    for t in threadinfo:
+        if len(t['atts']):
+            # One or more attachments. For now, we're only actually going
+            # to store and process the first one, even though the API gets
+            # us all of them.
+            MailThreadAttachment.objects.get_or_create(mailthread=mailthread,
+                                                       messageid=t['msgid'],
+                                                       defaults={
+                                                           'date': t['date'],
+                                                           'author': t['from'],
+                                                           'attachmentid': t['atts'][0]['id'],
+                                                           'filename': t['atts'][0]['name'],
+                                                       })
+        # In theory we should remove objects if they don't have an
+        # attachment, but how could that ever happen? Ignore for now.
 
 @transaction.atomic
 def attachThread(request):
-       cf = get_object_or_404(CommitFest, pk=int(request.POST['cf']))
-       patch = get_object_or_404(Patch, pk=int(request.POST['p']), commitfests=cf)
-       msgid = request.POST['msg']
+    cf = get_object_or_404(CommitFest, pk=int(request.POST['cf']))
+    patch = get_object_or_404(Patch, pk=int(request.POST['p']), commitfests=cf)
+    msgid = request.POST['msg']
 
-       return doAttachThread(cf, patch, msgid, request.user)
+    return doAttachThread(cf, patch, msgid, request.user)
 
 def doAttachThread(cf, patch, msgid, user):
-       # Note! Must be called in an open transaction!
-       r = sorted(_archivesAPI('/message-id.json/%s' % msgid), key=lambda x: x['date'])
-       # We have the full thread metadata - using the first and last entry,
-       # construct a new mailthread in our own model.
-       # First, though, check if it's already there.
-       threads = MailThread.objects.filter(messageid=r[0]['msgid'])
-       if len(threads):
-               thread = threads[0]
-               if thread.patches.filter(id=patch.id).exists():
-                       return 'This thread is already added to this email'
-
-               # We did not exist, so we'd better add ourselves.
-               # While at it, we update the thread entry with the latest data from the
-               # archives.
-               thread.patches.add(patch)
-               thread.latestmessage=r[-1]['date']
-               thread.latestauthor=r[-1]['from']
-               thread.latestsubject=r[-1]['subj']
-               thread.latestmsgid=r[-1]['msgid']
-               thread.save()
-       else:
-               # No existing thread existed, so create it
-               # Now create a new mailthread entry
-               m = MailThread(messageid=r[0]['msgid'],
-                                          subject=r[0]['subj'],
-                                          firstmessage=r[0]['date'],
-                                          firstauthor=r[0]['from'],
-                                          latestmessage=r[-1]['date'],
-                                          latestauthor=r[-1]['from'],
-                                          latestsubject=r[-1]['subj'],
-                                          latestmsgid=r[-1]['msgid'],
-                                          )
-               m.save()
-               m.patches.add(patch)
-               m.save()
-               parse_and_add_attachments(r, m)
-
-       PatchHistory(patch=patch, by=user, what='Attached mail thread %s' % r[0]['msgid']).save_and_notify()
-       patch.update_lastmail()
-       patch.set_modified()
-       patch.save()
-
-       return 'OK'
+    # Note! Must be called in an open transaction!
+    r = sorted(_archivesAPI('/message-id.json/%s' % msgid), key=lambda x: x['date'])
+    # We have the full thread metadata - using the first and last entry,
+    # construct a new mailthread in our own model.
+    # First, though, check if it's already there.
+    threads = MailThread.objects.filter(messageid=r[0]['msgid'])
+    if len(threads):
+        thread = threads[0]
+        if thread.patches.filter(id=patch.id).exists():
+            return 'This thread is already added to this email'
+
+        # We did not exist, so we'd better add ourselves.
+        # While at it, we update the thread entry with the latest data from the
+        # archives.
+        thread.patches.add(patch)
+        thread.latestmessage=r[-1]['date']
+        thread.latestauthor=r[-1]['from']
+        thread.latestsubject=r[-1]['subj']
+        thread.latestmsgid=r[-1]['msgid']
+        thread.save()
+    else:
+        # No existing thread existed, so create it
+        # Now create a new mailthread entry
+        m = MailThread(messageid=r[0]['msgid'],
+                       subject=r[0]['subj'],
+                       firstmessage=r[0]['date'],
+                       firstauthor=r[0]['from'],
+                       latestmessage=r[-1]['date'],
+                       latestauthor=r[-1]['from'],
+                       latestsubject=r[-1]['subj'],
+                       latestmsgid=r[-1]['msgid'],
+                       )
+        m.save()
+        m.patches.add(patch)
+        m.save()
+        parse_and_add_attachments(r, m)
+
+    PatchHistory(patch=patch, by=user, what='Attached mail thread %s' % r[0]['msgid']).save_and_notify()
+    patch.update_lastmail()
+    patch.set_modified()
+    patch.save()
+
+    return 'OK'
 
 @transaction.atomic
 def detachThread(request):
-       cf = get_object_or_404(CommitFest, pk=int(request.POST['cf']))
-       patch = get_object_or_404(Patch, pk=int(request.POST['p']), commitfests=cf)
-       thread = get_object_or_404(MailThread, messageid=request.POST['msg'])
+    cf = get_object_or_404(CommitFest, pk=int(request.POST['cf']))
+    patch = get_object_or_404(Patch, pk=int(request.POST['p']), commitfests=cf)
+    thread = get_object_or_404(MailThread, messageid=request.POST['msg'])
 
-       patch.mailthread_set.remove(thread)
-       PatchHistory(patch=patch, by=request.user, what='Detached mail thread %s' % request.POST['msg']).save_and_notify()
-       patch.update_lastmail()
-       patch.set_modified()
-       patch.save()
+    patch.mailthread_set.remove(thread)
+    PatchHistory(patch=patch, by=request.user, what='Detached mail thread %s' % request.POST['msg']).save_and_notify()
+    patch.update_lastmail()
+    patch.set_modified()
+    patch.save()
 
-       return 'OK'
+    return 'OK'
 
 def searchUsers(request):
-       if request.GET.has_key('s') and request.GET['s']:
-               return user_search(request.GET['s'])
-       else:
-               return []
+    if request.GET.has_key('s') and request.GET['s']:
+        return user_search(request.GET['s'])
+    else:
+        return []
 
 def importUser(request):
-       if request.GET.has_key('u') and request.GET['u']:
-               u = user_search(userid=request.GET['u'])
-               if len (u) != 1:
-                       return "Internal error, duplicate user found"
-
-               u = u[0]
-
-               if User.objects.filter(username=u['u']).exists():
-                       return "User already exists"
-               User(username=u['u'],
-                        first_name=u['f'],
-                        last_name=u['l'],
-                        email=u['e'],
-                        password='setbypluginnotsha1',
-                        ).save()
-               return 'OK'
-       else:
-               raise Http404()
+    if request.GET.has_key('u') and request.GET['u']:
+        u = user_search(userid=request.GET['u'])
+        if len (u) != 1:
+            return "Internal error, duplicate user found"
+
+        u = u[0]
+
+        if User.objects.filter(username=u['u']).exists():
+            return "User already exists"
+        User(username=u['u'],
+             first_name=u['f'],
+             last_name=u['l'],
+             email=u['e'],
+             password='setbypluginnotsha1',
+             ).save()
+        return 'OK'
+    else:
+        raise Http404()
 
 _ajax_map={
-       'getThreads': getThreads,
-       'getMessages': getMessages,
-       'attachThread': attachThread,
-       'detachThread': detachThread,
-       'annotateMessage': annotateMessage,
-       'deleteAnnotation': deleteAnnotation,
-       'searchUsers': searchUsers,
-       'importUser': importUser,
+    'getThreads': getThreads,
+    'getMessages': getMessages,
+    'attachThread': attachThread,
+    'detachThread': detachThread,
+    'annotateMessage': annotateMessage,
+    'deleteAnnotation': deleteAnnotation,
+    'searchUsers': searchUsers,
+    'importUser': importUser,
 }
 
 # Main entrypoint for /ajax/<command>/
 @csrf_exempt
 @login_required
 def main(request, command):
-       if not _ajax_map.has_key(command):
-               raise Http404
-       try:
-               resp = HttpResponse(content_type='application/json')
-               json.dump(_ajax_map[command](request), resp)
-               return resp
-       except Http503, e:
-               return HttpResponseServiceUnavailable(e, content_type='text/plain')
+    if not _ajax_map.has_key(command):
+        raise Http404
+    try:
+        resp = HttpResponse(content_type='application/json')
+        json.dump(_ajax_map[command](request), resp)
+        return resp
+    except Http503, e:
+        return HttpResponseServiceUnavailable(e, content_type='text/plain')
index 025ddef60722ae1f75d1ac448f8fdcb127770a70..d858703e8341d079b76d955734359065c519b6f7 100644 (file)
@@ -1,38 +1,38 @@
 from django.contrib.syndication.views import Feed
 
 class ActivityFeed(Feed):
-       title = description = 'Commitfest Activity Log'
-       link = 'https://commitfest.postgresql.org/'
+    title = description = 'Commitfest Activity Log'
+    link = 'https://commitfest.postgresql.org/'
 
-       def __init__(self, activity, cf, *args, **kwargs):
-               super(ActivityFeed, self).__init__(*args, **kwargs)
-               self.activity = activity
-               if cf:
-                       self.cfid = cf.id
-                       self.title = self.description = 'PostgreSQL Commitfest {0} Activity Log'.format(cf.name)
-               else:
-                       self.cfid = None
+    def __init__(self, activity, cf, *args, **kwargs):
+        super(ActivityFeed, self).__init__(*args, **kwargs)
+        self.activity = activity
+        if cf:
+            self.cfid = cf.id
+            self.title = self.description = 'PostgreSQL Commitfest {0} Activity Log'.format(cf.name)
+        else:
+            self.cfid = None
 
-       def items(self):
-               return self.activity
+    def items(self):
+        return self.activity
 
-       def item_title(self, item):
-               if self.cfid:
-                       return item['name']
-               else:
-                       return u'{cfname}: {name}'.format(**item)
+    def item_title(self, item):
+        if self.cfid:
+            return item['name']
+        else:
+            return u'{cfname}: {name}'.format(**item)
 
-       def item_description(self, item):
-               if self.cfid:
-                       return u"<div>Patch: {name}</div><div>User: {by}</div>\n<div>{what}</div>".format(**item)
-               else:
-                       return u"<div>Commitfest: {cfname}</div><div>Patch: {name}</div><div>User: {by}</div><div>{what}</div>".format(**item)
+    def item_description(self, item):
+        if self.cfid:
+            return u"<div>Patch: {name}</div><div>User: {by}</div>\n<div>{what}</div>".format(**item)
+        else:
+            return u"<div>Commitfest: {cfname}</div><div>Patch: {name}</div><div>User: {by}</div><div>{what}</div>".format(**item)
 
-       def item_link(self, item):
-               if self.cfid:
-                       return 'https://commitfest.postgresql.org/{cfid}/{patchid}/'.format(cfid=self.cfid,**item)
-               else:
-                       return 'https://commitfest.postgresql.org/{cfid}/{patchid}/'.format(**item)
+    def item_link(self, item):
+        if self.cfid:
+            return 'https://commitfest.postgresql.org/{cfid}/{patchid}/'.format(cfid=self.cfid,**item)
+        else:
+            return 'https://commitfest.postgresql.org/{cfid}/{patchid}/'.format(**item)
 
-       def item_pubdate(self, item):
-               return item['date']
+    def item_pubdate(self, item):
+        return item['date']
index 568af642e723274f40c4f29030a61c255ce9a083..a341f99ca06b61d872dde78876bf7b9bdba51674 100644 (file)
@@ -13,125 +13,125 @@ from widgets import ThreadPickWidget
 from ajax import _archivesAPI
 
 class CommitFestFilterForm(forms.Form):
-       text = forms.CharField(max_length=50, required=False)
-       status = forms.ChoiceField(required=False)
-       author = forms.ChoiceField(required=False)
-       reviewer = forms.ChoiceField(required=False)
-       sortkey = forms.IntegerField(required=False)
+    text = forms.CharField(max_length=50, required=False)
+    status = forms.ChoiceField(required=False)
+    author = forms.ChoiceField(required=False)
+    reviewer = forms.ChoiceField(required=False)
+    sortkey = forms.IntegerField(required=False)
 
-       def __init__(self, cf, *args, **kwargs):
-               super(CommitFestFilterForm, self).__init__(*args, **kwargs)
+    def __init__(self, cf, *args, **kwargs):
+        super(CommitFestFilterForm, self).__init__(*args, **kwargs)
 
-               self.fields['sortkey'].widget = forms.HiddenInput()
+        self.fields['sortkey'].widget = forms.HiddenInput()
 
-               c = [(-1, '* All')] + list(PatchOnCommitFest._STATUS_CHOICES)
-               self.fields['status'] = forms.ChoiceField(choices=c, required=False)
+        c = [(-1, '* All')] + list(PatchOnCommitFest._STATUS_CHOICES)
+        self.fields['status'] = forms.ChoiceField(choices=c, required=False)
 
-               q = Q(patch_author__commitfests=cf) | Q(patch_reviewer__commitfests=cf)
-               userchoices = [(-1, '* All'), (-2, '* None'), (-3, '* Yourself') ] + [(u.id, '%s %s (%s)' % (u.first_name, u.last_name, u.username)) for u in User.objects.filter(q).distinct().order_by('first_name', 'last_name')]
-               self.fields['author'] = forms.ChoiceField(choices=userchoices, required=False)
-               self.fields['reviewer'] = forms.ChoiceField(choices=userchoices, required=False)
+        q = Q(patch_author__commitfests=cf) | Q(patch_reviewer__commitfests=cf)
+        userchoices = [(-1, '* All'), (-2, '* None'), (-3, '* Yourself') ] + [(u.id, '%s %s (%s)' % (u.first_name, u.last_name, u.username)) for u in User.objects.filter(q).distinct().order_by('first_name', 'last_name')]
+        self.fields['author'] = forms.ChoiceField(choices=userchoices, required=False)
+        self.fields['reviewer'] = forms.ChoiceField(choices=userchoices, required=False)
 
-               for f in ('status', 'author', 'reviewer',):
-                       self.fields[f].widget.attrs = {'class': 'input-medium'}
+        for f in ('status', 'author', 'reviewer',):
+            self.fields[f].widget.attrs = {'class': 'input-medium'}
 
 class PatchForm(forms.ModelForm):
-       class Meta:
-               model = Patch
-               exclude = ('commitfests', 'mailthreads', 'modified', 'lastmail', 'subscribers', )
-               widgets = {
-                       'authors': AutoCompleteSelectMultipleWidget(lookup_class=UserLookup, position='top'),
-                       'reviewers': AutoCompleteSelectMultipleWidget(lookup_class=UserLookup, position='top'),
-               }
+    class Meta:
+        model = Patch
+        exclude = ('commitfests', 'mailthreads', 'modified', 'lastmail', 'subscribers', )
+        widgets = {
+            'authors': AutoCompleteSelectMultipleWidget(lookup_class=UserLookup, position='top'),
+            'reviewers': AutoCompleteSelectMultipleWidget(lookup_class=UserLookup, position='top'),
+        }
 
-       def __init__(self, *args, **kwargs):
-               super(PatchForm, self).__init__(*args, **kwargs)
-               self.fields['authors'].help_text = 'Enter part of name to see list'
-               self.fields['reviewers'].help_text = 'Enter part of name to see list'
-               self.fields['committer'].label_from_instance = lambda x: '%s %s (%s)' % (x.user.first_name, x.user.last_name, x.user.username)
+    def __init__(self, *args, **kwargs):
+        super(PatchForm, self).__init__(*args, **kwargs)
+        self.fields['authors'].help_text = 'Enter part of name to see list'
+        self.fields['reviewers'].help_text = 'Enter part of name to see list'
+        self.fields['committer'].label_from_instance = lambda x: '%s %s (%s)' % (x.user.first_name, x.user.last_name, x.user.username)
 
 
 class NewPatchForm(forms.ModelForm):
-       threadmsgid = forms.CharField(max_length=200, required=True, label='Specify thread msgid', widget=ThreadPickWidget)
-#      patchfile = forms.FileField(allow_empty_file=False, max_length=50000, label='or upload patch file', required=False, help_text='This may be supported sometime in the future, and would then autogenerate a mail to the hackers list. At such a time, the threadmsgid would no longer be required.')
-
-       class Meta:
-               model = Patch
-               exclude = ('commitfests', 'mailthreads', 'modified', 'authors', 'reviewers', 'committer', 'wikilink', 'gitlink', 'lastmail', 'subscribers', )
-
-       def clean_threadmsgid(self):
-               try:
-                       _archivesAPI('/message-id.json/%s' % self.cleaned_data['threadmsgid'])
-               except Http404:
-                       raise ValidationError("Message not found in archives")
-               except:
-                       raise ValidationError("Error in API call to validate thread")
-               return self.cleaned_data['threadmsgid']
+    threadmsgid = forms.CharField(max_length=200, required=True, label='Specify thread msgid', widget=ThreadPickWidget)
+#    patchfile = forms.FileField(allow_empty_file=False, max_length=50000, label='or upload patch file', required=False, help_text='This may be supported sometime in the future, and would then autogenerate a mail to the hackers list. At such a time, the threadmsgid would no longer be required.')
+
+    class Meta:
+        model = Patch
+        exclude = ('commitfests', 'mailthreads', 'modified', 'authors', 'reviewers', 'committer', 'wikilink', 'gitlink', 'lastmail', 'subscribers', )
+
+    def clean_threadmsgid(self):
+        try:
+            _archivesAPI('/message-id.json/%s' % self.cleaned_data['threadmsgid'])
+        except Http404:
+            raise ValidationError("Message not found in archives")
+        except:
+            raise ValidationError("Error in API call to validate thread")
+        return self.cleaned_data['threadmsgid']
 
 def _fetch_thread_choices(patch):
-       for mt in patch.mailthread_set.order_by('-latestmessage'):
-               ti = sorted(_archivesAPI('/message-id.json/%s' % mt.messageid), key=lambda x: x['date'], reverse=True)
-               yield [mt.subject,
-                          [('%s,%s' % (mt.messageid, t['msgid']),'From %s at %s' % (t['from'], t['date'])) for t in ti]]
+    for mt in patch.mailthread_set.order_by('-latestmessage'):
+        ti = sorted(_archivesAPI('/message-id.json/%s' % mt.messageid), key=lambda x: x['date'], reverse=True)
+        yield [mt.subject,
+               [('%s,%s' % (mt.messageid, t['msgid']),'From %s at %s' % (t['from'], t['date'])) for t in ti]]
 
 
 review_state_choices = (
-       (0, 'Tested'),
-       (1, 'Passed'),
+    (0, 'Tested'),
+    (1, 'Passed'),
 )
 
 def reviewfield(label):
-       return forms.MultipleChoiceField(choices=review_state_choices, label=label, widget=forms.CheckboxSelectMultiple, required=False)
+    return forms.MultipleChoiceField(choices=review_state_choices, label=label, widget=forms.CheckboxSelectMultiple, required=False)
 
 class CommentForm(forms.Form):
-       responseto = forms.ChoiceField(choices=[], required=True, label='In response to')
-
-       # Specific checkbox fields for reviews
-       review_installcheck = reviewfield('make installcheck-world')
-       review_implements = reviewfield('Implements feature')
-       review_spec = reviewfield('Spec compliant')
-       review_doc = reviewfield('Documentation')
-
-       message = forms.CharField(required=True, widget=forms.Textarea)
-       newstatus = forms.ChoiceField(choices=PatchOnCommitFest.OPEN_STATUS_CHOICES, label='New status')
-
-       def __init__(self, patch, poc, is_review, *args, **kwargs):
-               super(CommentForm, self).__init__(*args, **kwargs)
-               self.is_review = is_review
-
-               self.fields['responseto'].choices = _fetch_thread_choices(patch)
-               self.fields['newstatus'].initial = poc.status
-               if not is_review:
-                       del self.fields['review_installcheck']
-                       del self.fields['review_implements']
-                       del self.fields['review_spec']
-                       del self.fields['review_doc']
-
-       def clean_responseto(self):
-               try:
-                       (threadid, respid) = self.cleaned_data['responseto'].split(',')
-                       self.thread = MailThread.objects.get(messageid=threadid)
-                       self.respid = respid
-               except MailThread.DoesNotExist:
-                       raise ValidationError('Selected thread appears to no longer exist')
-               except:
-                       raise ValidationError('Invalid message selected')
-               return self.cleaned_data['responseto']
-
-       def clean(self):
-               if self.is_review:
-                       for fn,f in self.fields.items():
-                               if fn.startswith('review_') and self.cleaned_data.has_key(fn):
-                                       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
+    responseto = forms.ChoiceField(choices=[], required=True, label='In response to')
+
+    # Specific checkbox fields for reviews
+    review_installcheck = reviewfield('make installcheck-world')
+    review_implements = reviewfield('Implements feature')
+    review_spec = reviewfield('Spec compliant')
+    review_doc = reviewfield('Documentation')
+
+    message = forms.CharField(required=True, widget=forms.Textarea)
+    newstatus = forms.ChoiceField(choices=PatchOnCommitFest.OPEN_STATUS_CHOICES, label='New status')
+
+    def __init__(self, patch, poc, is_review, *args, **kwargs):
+        super(CommentForm, self).__init__(*args, **kwargs)
+        self.is_review = is_review
+
+        self.fields['responseto'].choices = _fetch_thread_choices(patch)
+        self.fields['newstatus'].initial = poc.status
+        if not is_review:
+            del self.fields['review_installcheck']
+            del self.fields['review_implements']
+            del self.fields['review_spec']
+            del self.fields['review_doc']
+
+    def clean_responseto(self):
+        try:
+            (threadid, respid) = self.cleaned_data['responseto'].split(',')
+            self.thread = MailThread.objects.get(messageid=threadid)
+            self.respid = respid
+        except MailThread.DoesNotExist:
+            raise ValidationError('Selected thread appears to no longer exist')
+        except:
+            raise ValidationError('Invalid message selected')
+        return self.cleaned_data['responseto']
+
+    def clean(self):
+        if self.is_review:
+            for fn,f in self.fields.items():
+                if fn.startswith('review_') and self.cleaned_data.has_key(fn):
+                    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)
+    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)
index 74c08cc4674905815b89a0a176b49a8897b9988a..1cb567f5eef61cc729d75ad7eedae37a64b8ed1c 100644 (file)
@@ -6,20 +6,20 @@ from selectable.decorators import login_required
 
 @login_required
 class UserLookup(ModelLookup):
-       model = User
-       search_fields = (
-               'username__icontains',
-               'first_name__icontains',
-               'last_name__icontains',
-       )
-       filters = {'is_active': True, }
+    model = User
+    search_fields = (
+        'username__icontains',
+        'first_name__icontains',
+        'last_name__icontains',
+    )
+    filters = {'is_active': True, }
 
-       def get_item_value(self, item):
-               # Display for currently selected item
-               return u"%s (%s)" % (item.username, item.get_full_name())
+    def get_item_value(self, item):
+        # Display for currently selected item
+        return u"%s (%s)" % (item.username, item.get_full_name())
 
-       def get_item_label(self, item):
-               # Display for choice listings
-               return u"%s (%s)" % (item.username, item.get_full_name())
+    def get_item_label(self, item):
+        # Display for choice listings
+        return u"%s (%s)" % (item.username, item.get_full_name())
 
 registry.register(UserLookup)
index 6a8fe42ab49ca9b5f872c0f52e13c7e2879c6144..be8cd90b207da515d2217b7b909d1a54a389ca61 100644 (file)
@@ -9,38 +9,38 @@ from pgcommitfest.userprofile.models import UserProfile
 from pgcommitfest.mailqueue.util import send_template_mail
 
 class Command(BaseCommand):
-       help = "Send queued notifications"
+    help = "Send queued notifications"
 
-       def handle(self, *args, **options):
-               with transaction.atomic():
-                       # Django doesn't do proper group by in the ORM, so we have to
-                       # build our own.
-                       matches = {}
-                       for n in PendingNotification.objects.all().order_by('user', 'history__patch__id', 'history__id'):
-                               if not matches.has_key(n.user.id):
-                                       matches[n.user.id] = {'user': n.user, 'patches': {}}
-                               if not matches[n.user.id]['patches'].has_key(n.history.patch.id):
-                                       matches[n.user.id]['patches'][n.history.patch.id] = {'patch': n.history.patch, 'entries': []}
-                               matches[n.user.id]['patches'][n.history.patch.id]['entries'].append(n.history)
-                               n.delete()
+    def handle(self, *args, **options):
+        with transaction.atomic():
+            # Django doesn't do proper group by in the ORM, so we have to
+            # build our own.
+            matches = {}
+            for n in PendingNotification.objects.all().order_by('user', 'history__patch__id', 'history__id'):
+                if not matches.has_key(n.user.id):
+                    matches[n.user.id] = {'user': n.user, 'patches': {}}
+                if not matches[n.user.id]['patches'].has_key(n.history.patch.id):
+                    matches[n.user.id]['patches'][n.history.patch.id] = {'patch': n.history.patch, 'entries': []}
+                matches[n.user.id]['patches'][n.history.patch.id]['entries'].append(n.history)
+                n.delete()
 
-                       # Ok, now let's build emails from this
-                       for v in matches.values():
-                               user = v['user']
-                               email = user.email
-                               try:
-                                       if user.userprofile and user.userprofile.notifyemail:
-                                               email = user.userprofile.notifyemail.email
-                               except UserProfile.DoesNotExist:
-                                       pass
+            # Ok, now let's build emails from this
+            for v in matches.values():
+                user = v['user']
+                email = user.email
+                try:
+                    if user.userprofile and user.userprofile.notifyemail:
+                        email = user.userprofile.notifyemail.email
+                except UserProfile.DoesNotExist:
+                    pass
 
-                               send_template_mail(settings.NOTIFICATION_FROM,
-                                                                  None,
-                                                                  email,
-                                                                  "PostgreSQL commitfest updates",
-                                                                  'mail/patch_notify.txt',
-                                                                  {
-                                                                          'user': user,
-                                                                          'patches': v['patches'],
-                                                                  },
-                                                                  )
+                send_template_mail(settings.NOTIFICATION_FROM,
+                                   None,
+                                   email,
+                                   "PostgreSQL commitfest updates",
+                                   'mail/patch_notify.txt',
+                                   {
+                                       'user': user,
+                                       'patches': v['patches'],
+                                   },
+                                   )
index 346adf92a78a2ee57d07ee068703f0a0680623f4..fa61fe3b05924cae444613740f32ddcace7c9e03 100644 (file)
@@ -16,7 +16,7 @@ class Migration(migrations.Migration):
             name='status',
             field=models.IntegerField(default=1, choices=[(1, b'Needs review'), (2, b'Waiting on Author'), (3, b'Ready for Committer'), (4, b'Committed'), (5, b'Moved to next CF'), (6, b'Rejected'), (7, b'Returned with feedback'), (8, b'Withdrawn')]),
         ),
-               migrations.RunSQL("""
+        migrations.RunSQL("""
 INSERT INTO commitfest_patchstatus (status, statusstring, sortkey) VALUES
 (1,'Needs review',10),
 (2,'Waiting on Author',15),
@@ -28,5 +28,5 @@ INSERT INTO commitfest_patchstatus (status, statusstring, sortkey) VALUES
 (8,'Withdrawn', 50)
 ON CONFLICT (status) DO UPDATE SET statusstring=excluded.statusstring, sortkey=excluded.sortkey;
 """),
-               migrations.RunSQL("DELETE FROM commitfest_patchstatus WHERE status < 1 OR status > 8"),
+        migrations.RunSQL("DELETE FROM commitfest_patchstatus WHERE status < 1 OR status > 8"),
     ]
index 60dcaf4cc6b59c3f81ad9fa431f237af364fd187..1cc694e552a3f1eda0a57b91264bde18653ea8dd 100644 (file)
@@ -11,310 +11,310 @@ from pgcommitfest.userprofile.models import UserProfile
 # need to extend from the user model, so just create a separate
 # class.
 class Committer(models.Model):
-       user = models.OneToOneField(User, null=False, blank=False, primary_key=True)
-       active = models.BooleanField(null=False, blank=False, default=True)
+    user = models.OneToOneField(User, null=False, blank=False, primary_key=True)
+    active = models.BooleanField(null=False, blank=False, default=True)
 
-       def __unicode__(self):
-               return unicode(self.user)
+    def __unicode__(self):
+        return unicode(self.user)
 
-       @property
-       def fullname(self):
-               return "%s %s (%s)" % (self.user.first_name, self.user.last_name, self.user.username)
+    @property
+    def fullname(self):
+        return "%s %s (%s)" % (self.user.first_name, self.user.last_name, self.user.username)
 
-       class Meta:
-               ordering = ('user__last_name', 'user__first_name')
+    class Meta:
+        ordering = ('user__last_name', 'user__first_name')
 
 class CommitFest(models.Model):
-       STATUS_FUTURE=1
-       STATUS_OPEN=2
-       STATUS_INPROGRESS=3
-       STATUS_CLOSED=4
-       _STATUS_CHOICES = (
-               (STATUS_FUTURE, 'Future'),
-               (STATUS_OPEN, 'Open'),
-               (STATUS_INPROGRESS, 'In Progress'),
-               (STATUS_CLOSED, 'Closed'),
-               )
-       name = models.CharField(max_length=100, blank=False, null=False, unique=True)
-       status = models.IntegerField(null=False, blank=False, default=1, choices=_STATUS_CHOICES)
-       startdate = models.DateField(blank=True, null=True)
-       enddate = models.DateField(blank=True, null=True)
-
-       @property
-       def statusstring(self):
-               return [v for k,v in self._STATUS_CHOICES if k==self.status][0]
-
-       @property
-       def periodstring(self):
-               if self.startdate and self.enddate:
-                       return "{0} - {1}".format(self.startdate, self.enddate)
-               return ""
-
-       @property
-       def title(self):
-               return "Commitfest %s" % self.name
-
-       @property
-       def isopen(self):
-               return self.status == self.STATUS_OPEN
-
-       def __unicode__(self):
-               return self.name
-
-       class Meta:
-               verbose_name_plural='Commitfests'
-               ordering = ('-startdate',)
+    STATUS_FUTURE=1
+    STATUS_OPEN=2
+    STATUS_INPROGRESS=3
+    STATUS_CLOSED=4
+    _STATUS_CHOICES = (
+        (STATUS_FUTURE, 'Future'),
+        (STATUS_OPEN, 'Open'),
+        (STATUS_INPROGRESS, 'In Progress'),
+        (STATUS_CLOSED, 'Closed'),
+        )
+    name = models.CharField(max_length=100, blank=False, null=False, unique=True)
+    status = models.IntegerField(null=False, blank=False, default=1, choices=_STATUS_CHOICES)
+    startdate = models.DateField(blank=True, null=True)
+    enddate = models.DateField(blank=True, null=True)
+
+    @property
+    def statusstring(self):
+        return [v for k,v in self._STATUS_CHOICES if k==self.status][0]
+
+    @property
+    def periodstring(self):
+        if self.startdate and self.enddate:
+            return "{0} - {1}".format(self.startdate, self.enddate)
+        return ""
+
+    @property
+    def title(self):
+        return "Commitfest %s" % self.name
+
+    @property
+    def isopen(self):
+        return self.status == self.STATUS_OPEN
+
+    def __unicode__(self):
+        return self.name
+
+    class Meta:
+        verbose_name_plural='Commitfests'
+        ordering = ('-startdate',)
 
 class Topic(models.Model):
-       topic = models.CharField(max_length=100, blank=False, null=False)
+    topic = models.CharField(max_length=100, blank=False, null=False)
 
-       def __unicode__(self):
-               return self.topic
+    def __unicode__(self):
+        return self.topic
 
 
 class Patch(models.Model, DiffableModel):
-       name = models.CharField(max_length=500, blank=False, null=False, verbose_name='Description')
-       topic = models.ForeignKey(Topic, blank=False, null=False)
-
-       # One patch can be in multiple commitfests, if it has history
-       commitfests = models.ManyToManyField(CommitFest, through='PatchOnCommitFest')
-
-       # If there is a wiki page discussing this patch
-       wikilink = models.URLField(blank=True, null=False, default='')
-
-       # If there is a git repo about this patch
-       gitlink = models.URLField(blank=True, null=False, default='')
-
-       # Mailthreads are ManyToMany in the other direction
-       #mailthreads_set = ...
-
-       authors = models.ManyToManyField(User, related_name='patch_author', blank=True)
-       reviewers = models.ManyToManyField(User, related_name='patch_reviewer', blank=True)
-
-       committer = models.ForeignKey(Committer, blank=True, null=True)
-
-       # Users to be notified when something happens
-       subscribers = models.ManyToManyField(User, related_name='patch_subscriber', blank=True)
-
-       # Datestamps for tracking activity
-       created = models.DateTimeField(blank=False, null=False, auto_now_add=True)
-       modified = models.DateTimeField(blank=False, null=False)
-
-       # Materialize the last time an email was sent on any of the threads
-       # that's attached to this message.
-       lastmail = models.DateTimeField(blank=True, null=True)
-
-       map_manytomany_for_diff = {
-               'authors': 'authors_string',
-               'reviewers': 'reviewers_string',
-               }
-       # Some accessors
-       @property
-       def authors_string(self):
-               return ", ".join(["%s %s (%s)" % (a.first_name, a.last_name, a.username) for a in self.authors.all()])
-
-       @property
-       def reviewers_string(self):
-               return ", ".join(["%s %s (%s)" % (a.first_name, a.last_name, a.username) for a in self.reviewers.all()])
-
-       @property
-       def history(self):
-               # Need to wrap this in a function to make sure it calls
-               # select_related() and doesn't generate a bazillion queries
-               return self.patchhistory_set.select_related('by').all()
-
-       def set_modified(self, newmod=None):
-               # Set the modified date to newmod, but only if that's newer than
-               # what's currently set. If newmod is not specified, use the
-               # current timestamp.
-               if not newmod:
-                       newmod = datetime.now()
-               if not self.modified or newmod > self.modified:
-                       self.modified = newmod
-
-       def update_lastmail(self):
-               # Update the lastmail field, based on the newest email in any of
-               # the threads attached to it.
-               threads = list(self.mailthread_set.all())
-               if len(threads) == 0:
-                       self.lastmail = None
-               else:
-                       self.lastmail = max(threads, key=lambda t:t.latestmessage).latestmessage
-
-       def __unicode__(self):
-               return self.name
-
-       class Meta:
-               verbose_name_plural = 'patches'
+    name = models.CharField(max_length=500, blank=False, null=False, verbose_name='Description')
+    topic = models.ForeignKey(Topic, blank=False, null=False)
+
+    # One patch can be in multiple commitfests, if it has history
+    commitfests = models.ManyToManyField(CommitFest, through='PatchOnCommitFest')
+
+    # If there is a wiki page discussing this patch
+    wikilink = models.URLField(blank=True, null=False, default='')
+
+    # If there is a git repo about this patch
+    gitlink = models.URLField(blank=True, null=False, default='')
+
+    # Mailthreads are ManyToMany in the other direction
+    #mailthreads_set = ...
+
+    authors = models.ManyToManyField(User, related_name='patch_author', blank=True)
+    reviewers = models.ManyToManyField(User, related_name='patch_reviewer', blank=True)
+
+    committer = models.ForeignKey(Committer, blank=True, null=True)
+
+    # Users to be notified when something happens
+    subscribers = models.ManyToManyField(User, related_name='patch_subscriber', blank=True)
+
+    # Datestamps for tracking activity
+    created = models.DateTimeField(blank=False, null=False, auto_now_add=True)
+    modified = models.DateTimeField(blank=False, null=False)
+
+    # Materialize the last time an email was sent on any of the threads
+    # that's attached to this message.
+    lastmail = models.DateTimeField(blank=True, null=True)
+
+    map_manytomany_for_diff = {
+        'authors': 'authors_string',
+        'reviewers': 'reviewers_string',
+        }
+    # Some accessors
+    @property
+    def authors_string(self):
+        return ", ".join(["%s %s (%s)" % (a.first_name, a.last_name, a.username) for a in self.authors.all()])
+
+    @property
+    def reviewers_string(self):
+        return ", ".join(["%s %s (%s)" % (a.first_name, a.last_name, a.username) for a in self.reviewers.all()])
+
+    @property
+    def history(self):
+        # Need to wrap this in a function to make sure it calls
+        # select_related() and doesn't generate a bazillion queries
+        return self.patchhistory_set.select_related('by').all()
+
+    def set_modified(self, newmod=None):
+        # Set the modified date to newmod, but only if that's newer than
+        # what's currently set. If newmod is not specified, use the
+        # current timestamp.
+        if not newmod:
+            newmod = datetime.now()
+        if not self.modified or newmod > self.modified:
+            self.modified = newmod
+
+    def update_lastmail(self):
+        # Update the lastmail field, based on the newest email in any of
+        # the threads attached to it.
+        threads = list(self.mailthread_set.all())
+        if len(threads) == 0:
+            self.lastmail = None
+        else:
+            self.lastmail = max(threads, key=lambda t:t.latestmessage).latestmessage
+
+    def __unicode__(self):
+        return self.name
+
+    class Meta:
+        verbose_name_plural = 'patches'
 
 class PatchOnCommitFest(models.Model):
-       # NOTE! This is also matched by the commitfest_patchstatus table,
-       # but we hardcoded it in here simply for performance reasons since
-       # the data should be entirely static. (Yes, that's something we
-       # might re-evaluate in the future)
-       STATUS_REVIEW=1
-       STATUS_AUTHOR=2
-       STATUS_COMMITTER=3
-       STATUS_COMMITTED=4
-       STATUS_NEXT=5
-       STATUS_REJECTED=6
-       STATUS_RETURNED=7
-       STATUS_WITHDRAWN=8
-       _STATUS_CHOICES=(
-               (STATUS_REVIEW, 'Needs review'),
-               (STATUS_AUTHOR, 'Waiting on Author'),
-               (STATUS_COMMITTER, 'Ready for Committer'),
-               (STATUS_COMMITTED, 'Committed'),
-               (STATUS_NEXT, 'Moved to next CF'),
-               (STATUS_REJECTED, 'Rejected'),
-               (STATUS_RETURNED, 'Returned with feedback'),
-               (STATUS_WITHDRAWN, 'Withdrawn'),
-       )
-       _STATUS_LABELS=(
-               (STATUS_REVIEW, 'default'),
-               (STATUS_AUTHOR, 'primary'),
-               (STATUS_COMMITTER, 'info'),
-               (STATUS_COMMITTED, 'success'),
-               (STATUS_NEXT, 'warning'),
-               (STATUS_REJECTED, 'danger'),
-               (STATUS_RETURNED, 'danger'),
-               (STATUS_WITHDRAWN, 'danger'),
-       )
-       OPEN_STATUSES=[STATUS_REVIEW, STATUS_AUTHOR, STATUS_COMMITTER]
-       OPEN_STATUS_CHOICES=[x for x in _STATUS_CHOICES if x[0] in OPEN_STATUSES]
-
-       patch = models.ForeignKey(Patch, blank=False, null=False)
-       commitfest = models.ForeignKey(CommitFest, blank=False, null=False)
-       enterdate = models.DateTimeField(blank=False, null=False)
-       leavedate = models.DateTimeField(blank=True, null=True)
-
-       status = models.IntegerField(blank=False, null=False, default=STATUS_REVIEW, choices=_STATUS_CHOICES)
-
-       @property
-       def is_closed(self):
-               return self.status not in self.OPEN_STATUSES
-
-       @property
-       def statusstring(self):
-               return [v for k,v in self._STATUS_CHOICES if k==self.status][0]
-
-       class Meta:
-               unique_together = (('patch', 'commitfest',),)
-               ordering = ('-commitfest__startdate', )
+    # NOTE! This is also matched by the commitfest_patchstatus table,
+    # but we hardcoded it in here simply for performance reasons since
+    # the data should be entirely static. (Yes, that's something we
+    # might re-evaluate in the future)
+    STATUS_REVIEW=1
+    STATUS_AUTHOR=2
+    STATUS_COMMITTER=3
+    STATUS_COMMITTED=4
+    STATUS_NEXT=5
+    STATUS_REJECTED=6
+    STATUS_RETURNED=7
+    STATUS_WITHDRAWN=8
+    _STATUS_CHOICES=(
+        (STATUS_REVIEW, 'Needs review'),
+        (STATUS_AUTHOR, 'Waiting on Author'),
+        (STATUS_COMMITTER, 'Ready for Committer'),
+        (STATUS_COMMITTED, 'Committed'),
+        (STATUS_NEXT, 'Moved to next CF'),
+        (STATUS_REJECTED, 'Rejected'),
+        (STATUS_RETURNED, 'Returned with feedback'),
+        (STATUS_WITHDRAWN, 'Withdrawn'),
+    )
+    _STATUS_LABELS=(
+        (STATUS_REVIEW, 'default'),
+        (STATUS_AUTHOR, 'primary'),
+        (STATUS_COMMITTER, 'info'),
+        (STATUS_COMMITTED, 'success'),
+        (STATUS_NEXT, 'warning'),
+        (STATUS_REJECTED, 'danger'),
+        (STATUS_RETURNED, 'danger'),
+        (STATUS_WITHDRAWN, 'danger'),
+    )
+    OPEN_STATUSES=[STATUS_REVIEW, STATUS_AUTHOR, STATUS_COMMITTER]
+    OPEN_STATUS_CHOICES=[x for x in _STATUS_CHOICES if x[0] in OPEN_STATUSES]
+
+    patch = models.ForeignKey(Patch, blank=False, null=False)
+    commitfest = models.ForeignKey(CommitFest, blank=False, null=False)
+    enterdate = models.DateTimeField(blank=False, null=False)
+    leavedate = models.DateTimeField(blank=True, null=True)
+
+    status = models.IntegerField(blank=False, null=False, default=STATUS_REVIEW, choices=_STATUS_CHOICES)
+
+    @property
+    def is_closed(self):
+        return self.status not in self.OPEN_STATUSES
+
+    @property
+    def statusstring(self):
+        return [v for k,v in self._STATUS_CHOICES if k==self.status][0]
+
+    class Meta:
+        unique_together = (('patch', 'commitfest',),)
+        ordering = ('-commitfest__startdate', )
 
 class PatchHistory(models.Model):
-       patch = models.ForeignKey(Patch, blank=False, null=False)
-       date = models.DateTimeField(blank=False, null=False, auto_now_add=True)
-       by = models.ForeignKey(User, blank=False, null=False)
-       what = models.CharField(max_length=500, null=False, blank=False)
-
-       @property
-       def by_string(self):
-               return "%s %s (%s)" % (self.by.first_name, self.by.last_name, self.by.username)
-
-       def __unicode__(self):
-               return "%s - %s" % (self.patch.name, self.date)
-
-       class Meta:
-               ordering = ('-date', )
-
-       def save_and_notify(self, prevcommitter=None,
-                                               prevreviewers=None, prevauthors=None):
-               # Save this model, and then trigger notifications if there are any. There are
-               # many different things that can trigger notifications, so try them all.
-               self.save()
-
-               recipients = []
-               recipients.extend(self.patch.subscribers.all())
-
-               # Current or previous committer wants all notifications
-               try:
-                       if self.patch.committer and self.patch.committer.user.userprofile.notify_all_committer:
-                               recipients.append(self.patch.committer.user)
-               except UserProfile.DoesNotExist:
-                       pass
-
-               try:
-                       if prevcommitter and prevcommitter.user.userprofile.notify_all_committer:
-                               recipients.append(prevcommitter.user)
-               except UserProfile.DoesNotExist:
-                       pass
-
-               # Current or previous reviewers wants all notifications
-               recipients.extend(self.patch.reviewers.filter(userprofile__notify_all_reviewer=True))
-               if prevreviewers:
-                       # prevreviewers is a list
-                       recipients.extend(User.objects.filter(id__in=[p.id for p in prevreviewers], userprofile__notify_all_reviewer=True))
-
-               # Current or previous authors wants all notifications
-               recipients.extend(self.patch.authors.filter(userprofile__notify_all_author=True))
-
-               for u in set(recipients):
-                       if u != self.by: # Don't notify for changes we make ourselves
-                               PendingNotification(history=self, user=u).save()
+    patch = models.ForeignKey(Patch, blank=False, null=False)
+    date = models.DateTimeField(blank=False, null=False, auto_now_add=True)
+    by = models.ForeignKey(User, blank=False, null=False)
+    what = models.CharField(max_length=500, null=False, blank=False)
+
+    @property
+    def by_string(self):
+        return "%s %s (%s)" % (self.by.first_name, self.by.last_name, self.by.username)
+
+    def __unicode__(self):
+        return "%s - %s" % (self.patch.name, self.date)
+
+    class Meta:
+        ordering = ('-date', )
+
+    def save_and_notify(self, prevcommitter=None,
+                        prevreviewers=None, prevauthors=None):
+        # Save this model, and then trigger notifications if there are any. There are
+        # many different things that can trigger notifications, so try them all.
+        self.save()
+
+        recipients = []
+        recipients.extend(self.patch.subscribers.all())
+
+        # Current or previous committer wants all notifications
+        try:
+            if self.patch.committer and self.patch.committer.user.userprofile.notify_all_committer:
+                recipients.append(self.patch.committer.user)
+        except UserProfile.DoesNotExist:
+            pass
+
+        try:
+            if prevcommitter and prevcommitter.user.userprofile.notify_all_committer:
+                recipients.append(prevcommitter.user)
+        except UserProfile.DoesNotExist:
+            pass
+
+        # Current or previous reviewers wants all notifications
+        recipients.extend(self.patch.reviewers.filter(userprofile__notify_all_reviewer=True))
+        if prevreviewers:
+            # prevreviewers is a list
+            recipients.extend(User.objects.filter(id__in=[p.id for p in prevreviewers], userprofile__notify_all_reviewer=True))
+
+        # Current or previous authors wants all notifications
+        recipients.extend(self.patch.authors.filter(userprofile__notify_all_author=True))
+
+        for u in set(recipients):
+            if u != self.by: # Don't notify for changes we make ourselves
+                PendingNotification(history=self, user=u).save()
 
 class MailThread(models.Model):
-       # This class tracks mail threads from the main postgresql.org
-       # mailinglist archives. For each thread, we store *one* messageid.
-       # Using this messageid we can always query the archives for more
-       # detailed information, which is done dynamically as the page
-       # is loaded.
-       # For threads in an active or future commitfest, we also poll
-       # the archives to fetch "updated entries" at (ir)regular intervals
-       # so we can keep track of when there was last a change on the
-       # thread in question.
-       messageid = models.CharField(max_length=1000, null=False, blank=False, unique=True)
-       patches = models.ManyToManyField(Patch, blank=False)
-       subject = models.CharField(max_length=500, null=False, blank=False)
-       firstmessage = models.DateTimeField(null=False, blank=False)
-       firstauthor = models.CharField(max_length=500, null=False, blank=False)
-       latestmessage = models.DateTimeField(null=False, blank=False)
-       latestauthor = models.CharField(max_length=500, null=False, blank=False)
-       latestsubject = models.CharField(max_length=500, null=False, blank=False)
-       latestmsgid = models.CharField(max_length=1000, null=False, blank=False)
-
-       def __unicode__(self):
-               return self.subject
-
-       class Meta:
-               ordering = ('firstmessage', )
+    # This class tracks mail threads from the main postgresql.org
+    # mailinglist archives. For each thread, we store *one* messageid.
+    # Using this messageid we can always query the archives for more
+    # detailed information, which is done dynamically as the page
+    # is loaded.
+    # For threads in an active or future commitfest, we also poll
+    # the archives to fetch "updated entries" at (ir)regular intervals
+    # so we can keep track of when there was last a change on the
+    # thread in question.
+    messageid = models.CharField(max_length=1000, null=False, blank=False, unique=True)
+    patches = models.ManyToManyField(Patch, blank=False)
+    subject = models.CharField(max_length=500, null=False, blank=False)
+    firstmessage = models.DateTimeField(null=False, blank=False)
+    firstauthor = models.CharField(max_length=500, null=False, blank=False)
+    latestmessage = models.DateTimeField(null=False, blank=False)
+    latestauthor = models.CharField(max_length=500, null=False, blank=False)
+    latestsubject = models.CharField(max_length=500, null=False, blank=False)
+    latestmsgid = models.CharField(max_length=1000, null=False, blank=False)
+
+    def __unicode__(self):
+        return self.subject
+
+    class Meta:
+        ordering = ('firstmessage', )
 
 class MailThreadAttachment(models.Model):
-       mailthread = models.ForeignKey(MailThread, null=False, blank=False)
-       messageid = models.CharField(max_length=1000, null=False, blank=False)
-       attachmentid = models.IntegerField(null=False, blank=False)
-       filename = models.CharField(max_length=1000, null=False, blank=True)
-       date = models.DateTimeField(null=False, blank=False)
-       author = models.CharField(max_length=500, null=False, blank=False)
-       ispatch = models.NullBooleanField()
-
-       class Meta:
-               ordering = ('-date',)
-               unique_together = (('mailthread', 'messageid',), )
+    mailthread = models.ForeignKey(MailThread, null=False, blank=False)
+    messageid = models.CharField(max_length=1000, null=False, blank=False)
+    attachmentid = models.IntegerField(null=False, blank=False)
+    filename = models.CharField(max_length=1000, null=False, blank=True)
+    date = models.DateTimeField(null=False, blank=False)
+    author = models.CharField(max_length=500, null=False, blank=False)
+    ispatch = models.NullBooleanField()
+
+    class Meta:
+        ordering = ('-date',)
+        unique_together = (('mailthread', 'messageid',), )
 
 class MailThreadAnnotation(models.Model):
-       mailthread = models.ForeignKey(MailThread, null=False, blank=False)
-       date = models.DateTimeField(null=False, blank=False, auto_now_add=True)
-       user = models.ForeignKey(User, null=False, blank=False)
-       msgid = models.CharField(max_length=1000, null=False, blank=False)
-       annotationtext = models.TextField(null=False, blank=False, max_length=2000)
-       mailsubject = models.CharField(max_length=500, null=False, blank=False)
-       maildate = models.DateTimeField(null=False, blank=False)
-       mailauthor = models.CharField(max_length=500, null=False, blank=False)
-
-       @property
-       def user_string(self):
-               return "%s %s (%s)" % (self.user.first_name, self.user.last_name, self.user.username)
-
-       class Meta:
-               ordering = ('date', )
+    mailthread = models.ForeignKey(MailThread, null=False, blank=False)
+    date = models.DateTimeField(null=False, blank=False, auto_now_add=True)
+    user = models.ForeignKey(User, null=False, blank=False)
+    msgid = models.CharField(max_length=1000, null=False, blank=False)
+    annotationtext = models.TextField(null=False, blank=False, max_length=2000)
+    mailsubject = models.CharField(max_length=500, null=False, blank=False)
+    maildate = models.DateTimeField(null=False, blank=False)
+    mailauthor = models.CharField(max_length=500, null=False, blank=False)
+
+    @property
+    def user_string(self):
+        return "%s %s (%s)" % (self.user.first_name, self.user.last_name, self.user.username)
+
+    class Meta:
+        ordering = ('date', )
 
 class PatchStatus(models.Model):
-       status = models.IntegerField(null=False, blank=False, primary_key=True)
-       statusstring = models.TextField(max_length=50, null=False, blank=False)
-       sortkey = models.IntegerField(null=False, blank=False, default=10)
+    status = models.IntegerField(null=False, blank=False, primary_key=True)
+    statusstring = models.TextField(max_length=50, null=False, blank=False)
+    sortkey = models.IntegerField(null=False, blank=False, default=10)
 
 
 class PendingNotification(models.Model):
-       history = models.ForeignKey(PatchHistory, blank=False, null=False)
-       user = models.ForeignKey(User, blank=False, null=False)
+    history = models.ForeignKey(PatchHistory, blank=False, null=False)
+    user = models.ForeignKey(User, blank=False, null=False)
index f0d352e3f5807bbfdf2e92f0645992541de942e8..4645856a3b42c87f3a9bdb55a511b981654a9156 100644 (file)
@@ -8,12 +8,12 @@ from models import CommitFest
 
 @login_required
 def authorstats(request, cfid):
-       cf = get_object_or_404(CommitFest, pk=cfid)
-       if not request.user.is_staff:
-               raise Http404("Only CF Managers can do that.")
+    cf = get_object_or_404(CommitFest, pk=cfid)
+    if not request.user.is_staff:
+        raise Http404("Only CF Managers can do that.")
 
-       cursor = connection.cursor()
-       cursor.execute("""WITH patches(id,name) AS (
+    cursor = connection.cursor()
+    cursor.execute("""WITH patches(id,name) AS (
   SELECT p.id, name
    FROM commitfest_patch p
    INNER JOIN commitfest_patchoncommitfest poc ON poc.patch_id=p.id AND poc.commitfest_id=%(cid)s
@@ -35,12 +35,12 @@ FROM (authors FULL OUTER JOIN reviewers ON authors.userid=reviewers.userid)
 INNER JOIN auth_user u ON u.id=COALESCE(authors.userid, reviewers.userid)
 ORDER BY last_name, first_name
 """, {
-       'cid': cf.id,
+    'cid': cf.id,
 })
 
-       return render(request, 'report_authors.html', {
-               'cf': cf,
-               'report': cursor.fetchall(),
-               'title': 'Author stats',
-               'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},],
-       })
+    return render(request, 'report_authors.html', {
+        'cf': cf,
+        'report': cursor.fetchall(),
+        'title': 'Author stats',
+        'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},],
+    })
index b8f68e469445f9b7350b1502a423e5a9d435ac4f..bde7d07accd91dade51ae0a9370438a5c97eb2d6 100644 (file)
@@ -8,36 +8,36 @@ register = template.Library()
 @register.filter(name='patchstatusstring')
 @stringfilter
 def patchstatusstring(value):
-       i = int(value)
-       return [v for k,v in PatchOnCommitFest._STATUS_CHOICES if k==i][0]
+    i = int(value)
+    return [v for k,v in PatchOnCommitFest._STATUS_CHOICES if k==i][0]
 
 @register.filter(name='patchstatuslabel')
 @stringfilter
 def patchstatuslabel(value):
-       i = int(value)
-       return [v for k,v in PatchOnCommitFest._STATUS_LABELS if k==i][0]
+    i = int(value)
+    return [v for k,v in PatchOnCommitFest._STATUS_LABELS if k==i][0]
 
 @register.filter(is_safe=True)
 def label_class(value, arg):
-       return value.label_tag(attrs={'class': arg})
+    return value.label_tag(attrs={'class': arg})
 
 @register.filter(is_safe=True)
 def field_class(value, arg):
-       return value.as_widget(attrs={"class": arg})
+    return value.as_widget(attrs={"class": arg})
 
 @register.filter(name='alertmap')
 @stringfilter
 def alertmap(value):
-       if value == 'error':
-               return 'alert-danger'
-       elif value == 'warning':
-               return 'alert-warning'
-       elif value == 'success':
-               return 'alert-success'
-       else:
-               return 'alert-info'
+    if value == 'error':
+        return 'alert-danger'
+    elif value == 'warning':
+        return 'alert-warning'
+    elif value == 'success':
+        return 'alert-success'
+    else:
+        return 'alert-info'
 
 @register.filter(name='hidemail')
 @stringfilter
 def hidemail(value):
-       return value.replace('@', ' at ')
+    return value.replace('@', ' at ')
index ba09433792de42225491071d98954f8b86e71a69..46988796c7da1a6ec212d00d5277a119d05f99e5 100644 (file)
@@ -3,42 +3,42 @@ import django.db.models.fields.related
 
 
 class DiffableModel(object):
-       """
-       Make it possible to diff a model.
+    """
+    Make it possible to diff a model.
     """
 
-       def __init__(self, *args, **kwargs):
-               super(DiffableModel, self).__init__(*args, **kwargs)
-               self.__initial = self._dict
+    def __init__(self, *args, **kwargs):
+        super(DiffableModel, self).__init__(*args, **kwargs)
+        self.__initial = self._dict
 
-       @property
-       def diff(self):
-               manytomanyfieldnames = [f.name for f in self._meta.many_to_many]
-               d1 = self.__initial
-               d2 = self._dict
-               diffs = dict([(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]])
-               # Foreign key lookups
-               for k,v in diffs.items():
-                       if type(self._meta.get_field(k)) is django.db.models.fields.related.ForeignKey:
-                               # If it's a foreign key, look up the name again on ourselves.
-                               # Since we only care about the *new* value, it's easy enough.
-                               diffs[k] = (v[0], getattr(self, k))
-               # Many to many lookups
-               if hasattr(self, 'map_manytomany_for_diff'):
-                       for k,v in diffs.items():
-                               if k in manytomanyfieldnames and self.map_manytomany_for_diff.has_key(k):
-                                       # Try to show the display name instead here
-                                       newvalue = getattr(self, self.map_manytomany_for_diff[k])
-                                       diffs[k] = (v[0], newvalue)
-               return diffs
+    @property
+    def diff(self):
+        manytomanyfieldnames = [f.name for f in self._meta.many_to_many]
+        d1 = self.__initial
+        d2 = self._dict
+        diffs = dict([(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]])
+        # Foreign key lookups
+        for k,v in diffs.items():
+            if type(self._meta.get_field(k)) is django.db.models.fields.related.ForeignKey:
+                # If it's a foreign key, look up the name again on ourselves.
+                # Since we only care about the *new* value, it's easy enough.
+                diffs[k] = (v[0], getattr(self, k))
+        # Many to many lookups
+        if hasattr(self, 'map_manytomany_for_diff'):
+            for k,v in diffs.items():
+                if k in manytomanyfieldnames and self.map_manytomany_for_diff.has_key(k):
+                    # Try to show the display name instead here
+                    newvalue = getattr(self, self.map_manytomany_for_diff[k])
+                    diffs[k] = (v[0], newvalue)
+        return diffs
 
-       def save(self, *args, **kwargs):
-               super(DiffableModel, self).save(*args, **kwargs)
-               self.__initial = self._dict
+    def save(self, *args, **kwargs):
+        super(DiffableModel, self).save(*args, **kwargs)
+        self.__initial = self._dict
 
-       @property
-       def _dict(self):
-               fields = [field.name for field in self._meta.fields]
-               fields.extend([field.name for field in self._meta.many_to_many])
-               return model_to_dict(self, fields=fields)
+    @property
+    def _dict(self):
+        fields = [field.name for field in self._meta.fields]
+        fields.extend([field.name for field in self._meta.many_to_many])
+        return model_to_dict(self, fields=fields)
 
index 5f6889501e3fc410455318d8660b95d01b125ccc..705b172cb42aa266e7d72279e8bd412fe825f7bd 100644 (file)
@@ -27,170 +27,170 @@ from ajax import doAttachThread, refresh_single_thread
 from feeds import ActivityFeed
 
 def home(request):
-       commitfests = list(CommitFest.objects.all())
-       opencf = next((c for c in commitfests if c.status == CommitFest.STATUS_OPEN), None)
-       inprogresscf = next((c for c in commitfests if c.status == CommitFest.STATUS_INPROGRESS), None)
+    commitfests = list(CommitFest.objects.all())
+    opencf = next((c for c in commitfests if c.status == CommitFest.STATUS_OPEN), None)
+    inprogresscf = next((c for c in commitfests if c.status == CommitFest.STATUS_INPROGRESS), None)
 
-       return render(request, 'home.html', {
-               'commitfests': commitfests,
-               'opencf': opencf,
-               'inprogresscf': inprogresscf,
-               'title': 'Commitfests',
-               'header_activity': 'Activity log',
-               'header_activity_link': '/activity/',
-               })
+    return render(request, 'home.html', {
+        'commitfests': commitfests,
+        'opencf': opencf,
+        'inprogresscf': inprogresscf,
+        'title': 'Commitfests',
+        'header_activity': 'Activity log',
+        'header_activity_link': '/activity/',
+        })
 
 
 def activity(request, cfid=None, rss=None):
-       # Number of notes to fetch
-       if rss:
-               num = 50
-       else:
-               num = 100
-
-       if cfid:
-               cf = get_object_or_404(CommitFest, pk=cfid)
-
-               # Yes, we do string concatenation of the were clause. Because
-               # we're evil.  And also because the number has been verified
-               # when looking up the cf itself, so nothing can be injected
-               # there.
-               extrafields = ''
-               where = 'WHERE poc.commitfest_id={0}'.format(cf.id)
-       else:
-               cf = None
-               extrafields = ',poc.commitfest_id AS cfid,cf.name AS cfname'
-               where = ' INNER JOIN commitfest_commitfest cf ON cf.id=poc.commitfest_id'
-
-       sql = "SELECT ph.date, auth_user.username AS by, ph.what, p.id AS patchid, p.name{0} FROM commitfest_patchhistory ph INNER JOIN commitfest_patch p ON ph.patch_id=p.id INNER JOIN auth_user on auth_user.id=ph.by_id INNER JOIN commitfest_patchoncommitfest poc ON poc.patch_id=p.id {1} ORDER BY ph.date DESC LIMIT {2}".format(extrafields,where, num)
-
-       curs = connection.cursor()
-       curs.execute(sql)
-       activity = [dict(zip([c[0] for c in curs.description],r)) for r in curs.fetchall()]
-
-       if rss:
-               # Return RSS feed with these objects
-               return ActivityFeed(activity, cf)(request)
-       else:
-               # Return regular webpage
-               return render(request, 'activity.html', {
-                       'commitfest': cf,
-                       'activity': activity,
-                       'title': cf and 'Commitfest activity' or 'Global Commitfest activity',
-                       'rss_alternate': cf and '/{0}/activity.rss/'.format(cf.id) or '/activity.rss/',
-                       'rss_alternate_title': 'PostgreSQL Commitfest Activity Log',
-                       'breadcrumbs': cf and [{'title': cf.title, 'href': '/%s/' % cf.pk},] or None,
-               })
+    # Number of notes to fetch
+    if rss:
+        num = 50
+    else:
+        num = 100
+
+    if cfid:
+        cf = get_object_or_404(CommitFest, pk=cfid)
+
+        # Yes, we do string concatenation of the were clause. Because
+        # we're evil.  And also because the number has been verified
+        # when looking up the cf itself, so nothing can be injected
+        # there.
+        extrafields = ''
+        where = 'WHERE poc.commitfest_id={0}'.format(cf.id)
+    else:
+        cf = None
+        extrafields = ',poc.commitfest_id AS cfid,cf.name AS cfname'
+        where = ' INNER JOIN commitfest_commitfest cf ON cf.id=poc.commitfest_id'
+
+    sql = "SELECT ph.date, auth_user.username AS by, ph.what, p.id AS patchid, p.name{0} FROM commitfest_patchhistory ph INNER JOIN commitfest_patch p ON ph.patch_id=p.id INNER JOIN auth_user on auth_user.id=ph.by_id INNER JOIN commitfest_patchoncommitfest poc ON poc.patch_id=p.id {1} ORDER BY ph.date DESC LIMIT {2}".format(extrafields,where, num)
+
+    curs = connection.cursor()
+    curs.execute(sql)
+    activity = [dict(zip([c[0] for c in curs.description],r)) for r in curs.fetchall()]
+
+    if rss:
+        # Return RSS feed with these objects
+        return ActivityFeed(activity, cf)(request)
+    else:
+        # Return regular webpage
+        return render(request, 'activity.html', {
+            'commitfest': cf,
+            'activity': activity,
+            'title': cf and 'Commitfest activity' or 'Global Commitfest activity',
+            'rss_alternate': cf and '/{0}/activity.rss/'.format(cf.id) or '/activity.rss/',
+            'rss_alternate_title': 'PostgreSQL Commitfest Activity Log',
+            'breadcrumbs': cf and [{'title': cf.title, 'href': '/%s/' % cf.pk},] or None,
+        })
 
 def redir(request, what):
-       if what == 'open':
-               cfs = list(CommitFest.objects.filter(status=CommitFest.STATUS_OPEN))
-       elif what == 'inprogress':
-               cfs = list(CommitFest.objects.filter(status=CommitFest.STATUS_INPROGRESS))
-       else:
-               raise Http404()
-
-       if len(cfs) == 0:
-               messages.warning(request, "No {0} commitfests exist, redirecting to startpage.".format(what))
-               return HttpResponseRedirect("/")
-       if len(cfs) != 1:
-               messages.warning(request, "More than one {0} commitfest exists, redirecting to startpage instead.".format(what))
-               return HttpResponseRedirect("/")
-
-       return HttpResponseRedirect("/%s/" % cfs[0].id)
+    if what == 'open':
+        cfs = list(CommitFest.objects.filter(status=CommitFest.STATUS_OPEN))
+    elif what == 'inprogress':
+        cfs = list(CommitFest.objects.filter(status=CommitFest.STATUS_INPROGRESS))
+    else:
+        raise Http404()
+
+    if len(cfs) == 0:
+        messages.warning(request, "No {0} commitfests exist, redirecting to startpage.".format(what))
+        return HttpResponseRedirect("/")
+    if len(cfs) != 1:
+        messages.warning(request, "More than one {0} commitfest exists, redirecting to startpage instead.".format(what))
+        return HttpResponseRedirect("/")
+
+    return HttpResponseRedirect("/%s/" % cfs[0].id)
 
 def commitfest(request, cfid):
-       # Find ourselves
-       cf = get_object_or_404(CommitFest, pk=cfid)
-
-       # Build a dynamic filter based on the filtering options entered
-       whereclauses = []
-       whereparams = {}
-       if request.GET.has_key('status') and request.GET['status'] != "-1":
-               try:
-                       whereparams['status'] = int(request.GET['status'])
-                       whereclauses.append("poc.status=%(status)s")
-               except ValueError:
-                       # int() failed -- so just ignore this filter
-                       pass
-
-       if request.GET.has_key('author') and request.GET['author'] != "-1":
-               if request.GET['author'] == '-2':
-                       whereclauses.append("NOT EXISTS (SELECT 1 FROM commitfest_patch_authors cpa WHERE cpa.patch_id=p.id)")
-               elif request.GET['author'] == '-3':
-                       # Checking for "yourself" requires the user to be logged in!
-                       if not request.user.is_authenticated():
-                               return HttpResponseRedirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
-                       whereclauses.append("EXISTS (SELECT 1 FROM commitfest_patch_authors cpa WHERE cpa.patch_id=p.id AND cpa.user_id=%(self)s)")
-                       whereparams['self'] = request.user.id
-               else:
-                       try:
-                               whereparams['author'] = int(request.GET['author'])
-                               whereclauses.append("EXISTS (SELECT 1 FROM commitfest_patch_authors cpa WHERE cpa.patch_id=p.id AND cpa.user_id=%(author)s)")
-                       except ValueError:
-                               # int() failed -- so just ignore this filter
-                               pass
-
-       if request.GET.has_key('reviewer') and request.GET['reviewer'] != "-1":
-               if request.GET['reviewer'] == '-2':
-                       whereclauses.append("NOT EXISTS (SELECT 1 FROM commitfest_patch_reviewers cpr WHERE cpr.patch_id=p.id)")
-               elif request.GET['reviewer'] == '-3':
-                       # Checking for "yourself" requires the user to be logged in!
-                       if not request.user.is_authenticated():
-                               return HttpResponseRedirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
-                       whereclauses.append("EXISTS (SELECT 1 FROM commitfest_patch_reviewers cpr WHERE cpr.patch_id=p.id AND cpr.user_id=%(self)s)")
-                       whereparams['self'] = request.user.id
-               else:
-                       try:
-                               whereparams['reviewer'] = int(request.GET['reviewer'])
-                               whereclauses.append("EXISTS (SELECT 1 FROM commitfest_patch_reviewers cpr WHERE cpr.patch_id=p.id AND cpr.user_id=%(reviewer)s)")
-                       except ValueError:
-                               # int() failed -- so just ignore this filter
-                               pass
-
-       if request.GET.has_key('text') and request.GET['text'] != '':
-               whereclauses.append("p.name ILIKE '%%' || %(txt)s || '%%'")
-               whereparams['txt'] = request.GET['text']
-
-       has_filter = len(whereclauses) > 0
-
-       # Figure out custom ordering
-       if request.GET.has_key('sortkey') and request.GET['sortkey']!='':
-               try:
-                       sortkey=int(request.GET['sortkey'])
-               except ValueError:
-                       sortkey=0
-
-               if sortkey==1:
-                       orderby_str = 'modified, created'
-               elif sortkey==2:
-                       orderby_str = 'lastmail, created'
-               elif sortkey==3:
-                       orderby_str = 'num_cfs DESC, modified, created'
-               else:
-                       orderby_str = 'p.id'
-                       sortkey=0
-       else:
-               orderby_str = 'topic, created'
-               sortkey = 0
-
-       if not has_filter and sortkey==0 and request.GET:
-               # Redirect to get rid of the ugly url
-               return HttpResponseRedirect('/%s/' % cf.id)
-
-       if whereclauses:
-               where_str = 'AND ({0})'.format(' AND '.join(whereclauses))
-       else:
-               where_str = ''
-       params = {
-               'cid': cf.id,
-               'openstatuses': PatchOnCommitFest.OPEN_STATUSES,
-       }
-       params.update(whereparams)
-
-       # Let's not overload the poor django ORM
-       curs = connection.cursor()
-       curs.execute("""SELECT p.id, p.name, poc.status, p.created, p.modified, p.lastmail, committer.username AS committer, t.topic,
+    # Find ourselves
+    cf = get_object_or_404(CommitFest, pk=cfid)
+
+    # Build a dynamic filter based on the filtering options entered
+    whereclauses = []
+    whereparams = {}
+    if request.GET.has_key('status') and request.GET['status'] != "-1":
+        try:
+            whereparams['status'] = int(request.GET['status'])
+            whereclauses.append("poc.status=%(status)s")
+        except ValueError:
+            # int() failed -- so just ignore this filter
+            pass
+
+    if request.GET.has_key('author') and request.GET['author'] != "-1":
+        if request.GET['author'] == '-2':
+            whereclauses.append("NOT EXISTS (SELECT 1 FROM commitfest_patch_authors cpa WHERE cpa.patch_id=p.id)")
+        elif request.GET['author'] == '-3':
+            # Checking for "yourself" requires the user to be logged in!
+            if not request.user.is_authenticated():
+                return HttpResponseRedirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
+            whereclauses.append("EXISTS (SELECT 1 FROM commitfest_patch_authors cpa WHERE cpa.patch_id=p.id AND cpa.user_id=%(self)s)")
+            whereparams['self'] = request.user.id
+        else:
+            try:
+                whereparams['author'] = int(request.GET['author'])
+                whereclauses.append("EXISTS (SELECT 1 FROM commitfest_patch_authors cpa WHERE cpa.patch_id=p.id AND cpa.user_id=%(author)s)")
+            except ValueError:
+                # int() failed -- so just ignore this filter
+                pass
+
+    if request.GET.has_key('reviewer') and request.GET['reviewer'] != "-1":
+        if request.GET['reviewer'] == '-2':
+            whereclauses.append("NOT EXISTS (SELECT 1 FROM commitfest_patch_reviewers cpr WHERE cpr.patch_id=p.id)")
+        elif request.GET['reviewer'] == '-3':
+            # Checking for "yourself" requires the user to be logged in!
+            if not request.user.is_authenticated():
+                return HttpResponseRedirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
+            whereclauses.append("EXISTS (SELECT 1 FROM commitfest_patch_reviewers cpr WHERE cpr.patch_id=p.id AND cpr.user_id=%(self)s)")
+            whereparams['self'] = request.user.id
+        else:
+            try:
+                whereparams['reviewer'] = int(request.GET['reviewer'])
+                whereclauses.append("EXISTS (SELECT 1 FROM commitfest_patch_reviewers cpr WHERE cpr.patch_id=p.id AND cpr.user_id=%(reviewer)s)")
+            except ValueError:
+                # int() failed -- so just ignore this filter
+                pass
+
+    if request.GET.has_key('text') and request.GET['text'] != '':
+        whereclauses.append("p.name ILIKE '%%' || %(txt)s || '%%'")
+        whereparams['txt'] = request.GET['text']
+
+    has_filter = len(whereclauses) > 0
+
+    # Figure out custom ordering
+    if request.GET.has_key('sortkey') and request.GET['sortkey']!='':
+        try:
+            sortkey=int(request.GET['sortkey'])
+        except ValueError:
+            sortkey=0
+
+        if sortkey==1:
+            orderby_str = 'modified, created'
+        elif sortkey==2:
+            orderby_str = 'lastmail, created'
+        elif sortkey==3:
+            orderby_str = 'num_cfs DESC, modified, created'
+        else:
+            orderby_str = 'p.id'
+            sortkey=0
+    else:
+        orderby_str = 'topic, created'
+        sortkey = 0
+
+    if not has_filter and sortkey==0 and request.GET:
+        # Redirect to get rid of the ugly url
+        return HttpResponseRedirect('/%s/' % cf.id)
+
+    if whereclauses:
+        where_str = 'AND ({0})'.format(' AND '.join(whereclauses))
+    else:
+        where_str = ''
+    params = {
+        'cid': cf.id,
+        'openstatuses': PatchOnCommitFest.OPEN_STATUSES,
+    }
+    params.update(whereparams)
+
+    # Let's not overload the poor django ORM
+    curs = connection.cursor()
+    curs.execute("""SELECT p.id, p.name, poc.status, p.created, p.modified, p.lastmail, committer.username AS committer, t.topic,
 (poc.status=ANY(%(openstatuses)s)) AS is_open,
 (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=p.id) AS author_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=p.id) AS reviewer_names,
@@ -202,533 +202,533 @@ LEFT JOIN auth_user committer ON committer.id=p.committer_id
 WHERE poc.commitfest_id=%(cid)s {0}
 GROUP BY p.id, poc.id, committer.id, t.id
 ORDER BY is_open DESC, {1}""".format(where_str, orderby_str), params)
-       patches = [dict(zip([col[0] for col in curs.description], row)) for row in curs.fetchall()]
-
-       # Generate patch status summary.
-       curs = connection.cursor()
-       curs.execute("SELECT ps.status, ps.statusstring, count(*) FROM commitfest_patchoncommitfest poc INNER JOIN commitfest_patchstatus ps ON ps.status=poc.status WHERE commitfest_id=%(id)s GROUP BY ps.status ORDER BY ps.sortkey", {
-               'id': cf.id,
-               })
-       statussummary = curs.fetchall()
-       statussummary.append([-1, 'Total', sum((r[2] for r in statussummary))])
-
-       # Generates a fairly expensive query, which we shouldn't do unless
-       # the user is logged in. XXX: Figure out how to avoid doing that..
-       form = CommitFestFilterForm(cf, request.GET)
-
-       return render(request, 'commitfest.html', {
-               'cf': cf,
-               'form': form,
-               'patches': patches,
-               'statussummary': statussummary,
-               'has_filter': has_filter,
-               'title': cf.title,
-               'grouping': sortkey==0,
-               'sortkey': sortkey,
-               'openpatchids': [p['id'] for p in patches if p['is_open']],
-               'header_activity': 'Activity log',
-               'header_activity_link': 'activity/',
-               })
+    patches = [dict(zip([col[0] for col in curs.description], row)) for row in curs.fetchall()]
+
+    # Generate patch status summary.
+    curs = connection.cursor()
+    curs.execute("SELECT ps.status, ps.statusstring, count(*) FROM commitfest_patchoncommitfest poc INNER JOIN commitfest_patchstatus ps ON ps.status=poc.status WHERE commitfest_id=%(id)s GROUP BY ps.status ORDER BY ps.sortkey", {
+        'id': cf.id,
+        })
+    statussummary = curs.fetchall()
+    statussummary.append([-1, 'Total', sum((r[2] for r in statussummary))])
+
+    # Generates a fairly expensive query, which we shouldn't do unless
+    # the user is logged in. XXX: Figure out how to avoid doing that..
+    form = CommitFestFilterForm(cf, request.GET)
+
+    return render(request, 'commitfest.html', {
+        'cf': cf,
+        'form': form,
+        'patches': patches,
+        'statussummary': statussummary,
+        'has_filter': has_filter,
+        'title': cf.title,
+        'grouping': sortkey==0,
+        'sortkey': sortkey,
+        'openpatchids': [p['id'] for p in patches if p['is_open']],
+        'header_activity': 'Activity log',
+        'header_activity_link': 'activity/',
+        })
 
 def global_search(request):
-       if not request.GET.has_key('searchterm'):
-               return HttpResponseRedirect('/')
-       searchterm = request.GET['searchterm']
+    if not request.GET.has_key('searchterm'):
+        return HttpResponseRedirect('/')
+    searchterm = request.GET['searchterm']
 
-       patches = Patch.objects.select_related().filter(name__icontains=searchterm).order_by('created',)
+    patches = Patch.objects.select_related().filter(name__icontains=searchterm).order_by('created',)
 
-       return render(request, 'patchsearch.html', {
-               'patches': patches,
-               'title': 'Patch search results',
-               })
+    return render(request, 'patchsearch.html', {
+        'patches': patches,
+        'title': 'Patch search results',
+        })
 
 def patch(request, cfid, patchid):
-       cf = get_object_or_404(CommitFest, pk=cfid)
-       patch = get_object_or_404(Patch.objects.select_related(), pk=patchid, commitfests=cf)
-       patch_commitfests = PatchOnCommitFest.objects.select_related('commitfest').filter(patch=patch).order_by('-commitfest__startdate')
-       committers = Committer.objects.filter(active=True).order_by('user__last_name', 'user__first_name')
-
-       #XXX: this creates a session, so find a smarter way. Probably handle
-       #it in the callback and just ask the user then?
-       if request.user.is_authenticated():
-               committer = [c for c in committers if c.user==request.user]
-               if len(committer) > 0:
-                       is_committer=  True
-                       is_this_committer = committer[0] == patch.committer
-               else:
-                       is_committer = is_this_committer = False
-
-               is_reviewer = request.user in patch.reviewers.all()
-               is_subscribed = patch.subscribers.filter(id=request.user.id).exists()
-       else:
-               is_committer = False
-               is_this_committer = False
-               is_reviewer = False
-               is_subscribed = False
-
-       return render(request, 'patch.html', {
-               'cf': cf,
-               'patch': patch,
-               'patch_commitfests': patch_commitfests,
-               'is_committer': is_committer,
-               'is_this_committer': is_this_committer,
-               'is_reviewer': is_reviewer,
-               'is_subscribed': is_subscribed,
-               'committers': committers,
-               'attachnow': request.GET.has_key('attachthreadnow'),
-               'title': patch.name,
-               'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},],
-               })
+    cf = get_object_or_404(CommitFest, pk=cfid)
+    patch = get_object_or_404(Patch.objects.select_related(), pk=patchid, commitfests=cf)
+    patch_commitfests = PatchOnCommitFest.objects.select_related('commitfest').filter(patch=patch).order_by('-commitfest__startdate')
+    committers = Committer.objects.filter(active=True).order_by('user__last_name', 'user__first_name')
+
+    #XXX: this creates a session, so find a smarter way. Probably handle
+    #it in the callback and just ask the user then?
+    if request.user.is_authenticated():
+        committer = [c for c in committers if c.user==request.user]
+        if len(committer) > 0:
+            is_committer=  True
+            is_this_committer = committer[0] == patch.committer
+        else:
+            is_committer = is_this_committer = False
+
+        is_reviewer = request.user in patch.reviewers.all()
+        is_subscribed = patch.subscribers.filter(id=request.user.id).exists()
+    else:
+        is_committer = False
+        is_this_committer = False
+        is_reviewer = False
+        is_subscribed = False
+
+    return render(request, 'patch.html', {
+        'cf': cf,
+        'patch': patch,
+        'patch_commitfests': patch_commitfests,
+        'is_committer': is_committer,
+        'is_this_committer': is_this_committer,
+        'is_reviewer': is_reviewer,
+        'is_subscribed': is_subscribed,
+        'committers': committers,
+        'attachnow': request.GET.has_key('attachthreadnow'),
+        'title': patch.name,
+        'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},],
+        })
 
 @login_required
 @transaction.atomic
 def patchform(request, cfid, patchid):
-       cf = get_object_or_404(CommitFest, pk=cfid)
-       patch = get_object_or_404(Patch, pk=patchid, commitfests=cf)
-
-       prevreviewers = list(patch.reviewers.all())
-       prevauthors = list(patch.authors.all())
-       prevcommitter = patch.committer
-
-       if request.method == 'POST':
-               form = PatchForm(data=request.POST, instance=patch)
-               if form.is_valid():
-                       # Some fields need to be set when creating a new one
-                       r = form.save(commit=False)
-                       # Fill out any locked fields here
-
-                       form.save_m2m()
-
-                       # Track all changes
-                       for field, values in r.diff.items():
-                               PatchHistory(patch=patch, by=request.user, what='Changed %s to %s' % (field, values[1])).save_and_notify(prevcommitter=prevcommitter, prevreviewers=prevreviewers, prevauthors=prevauthors)
-                       r.set_modified()
-                       r.save()
-                       return HttpResponseRedirect('../../%s/' % r.pk)
-               # Else fall through and render the page again
-       else:
-               form = PatchForm(instance=patch)
-
-       return render(request, 'base_form.html', {
-               'cf': cf,
-               'form': form,
-               'patch': patch,
-               'title': 'Edit patch',
-               'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},
-                                               {'title': 'View patch', 'href': '/%s/%s/' % (cf.pk, patch.pk)}],
-       })
+    cf = get_object_or_404(CommitFest, pk=cfid)
+    patch = get_object_or_404(Patch, pk=patchid, commitfests=cf)
+
+    prevreviewers = list(patch.reviewers.all())
+    prevauthors = list(patch.authors.all())
+    prevcommitter = patch.committer
+
+    if request.method == 'POST':
+        form = PatchForm(data=request.POST, instance=patch)
+        if form.is_valid():
+            # Some fields need to be set when creating a new one
+            r = form.save(commit=False)
+            # Fill out any locked fields here
+
+            form.save_m2m()
+
+            # Track all changes
+            for field, values in r.diff.items():
+                PatchHistory(patch=patch, by=request.user, what='Changed %s to %s' % (field, values[1])).save_and_notify(prevcommitter=prevcommitter, prevreviewers=prevreviewers, prevauthors=prevauthors)
+            r.set_modified()
+            r.save()
+            return HttpResponseRedirect('../../%s/' % r.pk)
+        # Else fall through and render the page again
+    else:
+        form = PatchForm(instance=patch)
+
+    return render(request, 'base_form.html', {
+        'cf': cf,
+        'form': form,
+        'patch': patch,
+        'title': 'Edit patch',
+        'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},
+                        {'title': 'View patch', 'href': '/%s/%s/' % (cf.pk, patch.pk)}],
+    })
 
 @login_required
 @transaction.atomic
 def newpatch(request, cfid):
-       cf = get_object_or_404(CommitFest, pk=cfid)
-       if not cf.status == CommitFest.STATUS_OPEN and not request.user.is_staff:
-               raise Http404("This commitfest is not open!")
-
-       if request.method == 'POST':
-               form = NewPatchForm(data=request.POST)
-               if form.is_valid():
-                       patch = Patch(name=form.cleaned_data['name'],
-                                                 topic=form.cleaned_data['topic'])
-                       patch.set_modified()
-                       patch.save()
-                       poc = PatchOnCommitFest(patch=patch, commitfest=cf, enterdate=datetime.now())
-                       poc.save()
-                       PatchHistory(patch=patch, by=request.user, what='Created patch record').save()
-                       # Now add the thread
-                       try:
-                               doAttachThread(cf, patch, form.cleaned_data['threadmsgid'], request.user)
-                               return HttpResponseRedirect("/%s/%s/edit/" % (cf.id, patch.id))
-                       except Http404:
-                               # Thread not found!
-                               # This is a horrible breakage of API layers
-                               form._errors['threadmsgid'] = form.error_class(('Selected thread did not exist in the archives',))
-                       except Exception:
-                               form._errors['threadmsgid'] = form.error_class(('An error occurred looking up the thread in the archives.',))
-                       # In this case, we have created a patch - delete it. This causes a agp in id's, but it should
-                       # not happen very often. If we successfully attached to it, we will have already returned.
-                       patch.delete()
-       else:
-               form = NewPatchForm()
-
-       return render(request, 'base_form.html', {
-               'form': form,
-               'title': 'New patch',
-               'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},],
-               'savebutton': 'Create patch',
-               'threadbrowse': True,
-       })
+    cf = get_object_or_404(CommitFest, pk=cfid)
+    if not cf.status == CommitFest.STATUS_OPEN and not request.user.is_staff:
+        raise Http404("This commitfest is not open!")
+
+    if request.method == 'POST':
+        form = NewPatchForm(data=request.POST)
+        if form.is_valid():
+            patch = Patch(name=form.cleaned_data['name'],
+                          topic=form.cleaned_data['topic'])
+            patch.set_modified()
+            patch.save()
+            poc = PatchOnCommitFest(patch=patch, commitfest=cf, enterdate=datetime.now())
+            poc.save()
+            PatchHistory(patch=patch, by=request.user, what='Created patch record').save()
+            # Now add the thread
+            try:
+                doAttachThread(cf, patch, form.cleaned_data['threadmsgid'], request.user)
+                return HttpResponseRedirect("/%s/%s/edit/" % (cf.id, patch.id))
+            except Http404:
+                # Thread not found!
+                # This is a horrible breakage of API layers
+                form._errors['threadmsgid'] = form.error_class(('Selected thread did not exist in the archives',))
+            except Exception:
+                form._errors['threadmsgid'] = form.error_class(('An error occurred looking up the thread in the archives.',))
+            # In this case, we have created a patch - delete it. This causes a agp in id's, but it should
+            # not happen very often. If we successfully attached to it, we will have already returned.
+            patch.delete()
+    else:
+        form = NewPatchForm()
+
+    return render(request, 'base_form.html', {
+        'form': form,
+        'title': 'New patch',
+        'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},],
+        'savebutton': 'Create patch',
+        'threadbrowse': True,
+    })
 
 def _review_status_string(reviewstatus):
-       if '0' in reviewstatus:
-               if '1' in reviewstatus:
-                       return "tested, passed"
-               else:
-                       return "tested, failed"
-       else:
-               return "not tested"
+    if '0' in reviewstatus:
+        if '1' in reviewstatus:
+            return "tested, passed"
+        else:
+            return "tested, failed"
+    else:
+        return "not tested"
 
 @login_required
 @transaction.atomic
 def comment(request, cfid, patchid, what):
-       cf = get_object_or_404(CommitFest, pk=cfid)
-       patch = get_object_or_404(Patch, pk=patchid)
-       poc = get_object_or_404(PatchOnCommitFest, patch=patch, commitfest=cf)
-       is_review = (what=='review')
-
-       if poc.is_closed:
-               # We allow modification of patches in closed CFs *only* if it's the
-               # last CF that the patch is part of. If it's part of another CF, that
-               # is later than this one, tell the user to go there instead.
-               lastcf = PatchOnCommitFest.objects.filter(patch=patch).order_by('-commitfest__startdate')[0]
-               if poc != lastcf:
-                       messages.add_message(request, messages.INFO, "The status of this patch cannot be changed in this commitfest. You must modify it in the one where it's open!")
-                       return HttpResponseRedirect('..')
-
-       if request.method == 'POST':
-               try:
-                       form = CommentForm(patch, poc, is_review, data=request.POST)
-               except Exception, e:
-                       messages.add_message(request, messages.ERROR, "Failed to build list of response options from the archives: %s" % e)
-                       return HttpResponseRedirect('/%s/%s/' % (cf.id, patch.id))
-
-               if form.is_valid():
-                       if is_review:
-                               txt = "The following review has been posted through the commitfest application:\n%s\n\n%s" % (
-                                       "\n".join(["%-25s %s" % (f.label + ':', _review_status_string(form.cleaned_data[fn])) for (fn, f) in form.fields.items() if fn.startswith('review_')]),
-                                       form.cleaned_data['message']
-                               )
-                       else:
-                               txt = form.cleaned_data['message']
-
-                       if int(form.cleaned_data['newstatus']) != poc.status:
-                               poc.status = int(form.cleaned_data['newstatus'])
-                               poc.save()
-                               PatchHistory(patch=poc.patch, by=request.user, what='New status: %s' % poc.statusstring).save_and_notify()
-                               txt += "\n\nThe new status of this patch is: %s\n" % poc.statusstring
-
-                       msg = MIMEText(txt, _charset='utf-8')
-
-                       if form.thread.subject.startswith('Re:'):
-                               msg['Subject'] = form.thread.subject
-                       else:
-                               msg['Subject'] = 'Re: %s' % form.thread.subject
-
-                       msg['To'] = settings.HACKERS_EMAIL
-                       msg['From'] = UserWrapper(request.user).encoded_email_header
-
-                       # CC the authors of a patch, if there are any
-                       authors = list(patch.authors.all())
-                       if len(authors):
-                               msg['Cc'] = ", ".join([UserWrapper(a).encoded_email_header for a in authors])
-
-                       msg['Date'] = formatdate(localtime=True)
-                       msg['User-Agent'] = 'pgcommitfest'
-                       msg['X-cfsender'] = request.user.username
-                       msg['In-Reply-To'] = '<%s>' % form.respid
-                       # We just add the "top" messageid and the one we're responding to.
-                       # This along with in-reply-to should indicate clearly enough where
-                       # in the thread the message belongs.
-                       msg['References'] = '<%s> <%s>' % (form.thread.messageid, form.respid)
-                       msg['Message-ID'] = make_msgid('pgcf')
-
-                       uw = UserWrapper(request.user)
-                       msgstring = msg.as_string()
-                       send_mail(uw.email, settings.HACKERS_EMAIL, msgstring)
-                       for a in authors:
-                               # Actually send a copy directly to the author. Just setting the Cc field doesn't
-                               # make it deliver the email...
-                               send_mail(uw.email, UserWrapper(a).email, msgstring)
-
-                       PatchHistory(patch=patch, by=request.user, what='Posted %s with messageid %s' % (what, msg['Message-ID'])).save()
-
-                       messages.add_message(request, messages.INFO, "Your email has been queued for %s, and will be sent within a few minutes." % (settings.HACKERS_EMAIL))
-
-                       return HttpResponseRedirect('/%s/%s/' % (cf.id, patch.id))
-       else:
-               try:
-                       form = CommentForm(patch, poc, is_review)
-               except Exception, e:
-                       messages.add_message(request, messages.ERROR, "Failed to build list of response options from the archives: %s" % e)
-                       return HttpResponseRedirect('/%s/%s/' % (cf.id, patch.id))
-
-       return render(request, 'base_form.html', {
-               'cf': cf,
-               'form': form,
-               'patch': patch,
-               'extraformclass': 'patchcommentform',
-               '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>%s</i>, with sender set to <i>%s</i>!<br/>Please ensure that the email settings for your domain (<a href="https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail" target="_blank">DKIM</a>, <a href="https://en.wikipedia.org/wiki/SPF" target="_blank">SPF</a>) allow emails from external sources.' % (settings.HACKERS_EMAIL, UserWrapper(request.user).email),
-               'savebutton': 'Send %s' % what,
-       })
+    cf = get_object_or_404(CommitFest, pk=cfid)
+    patch = get_object_or_404(Patch, pk=patchid)
+    poc = get_object_or_404(PatchOnCommitFest, patch=patch, commitfest=cf)
+    is_review = (what=='review')
+
+    if poc.is_closed:
+        # We allow modification of patches in closed CFs *only* if it's the
+        # last CF that the patch is part of. If it's part of another CF, that
+        # is later than this one, tell the user to go there instead.
+        lastcf = PatchOnCommitFest.objects.filter(patch=patch).order_by('-commitfest__startdate')[0]
+        if poc != lastcf:
+            messages.add_message(request, messages.INFO, "The status of this patch cannot be changed in this commitfest. You must modify it in the one where it's open!")
+            return HttpResponseRedirect('..')
+
+    if request.method == 'POST':
+        try:
+            form = CommentForm(patch, poc, is_review, data=request.POST)
+        except Exception, e:
+            messages.add_message(request, messages.ERROR, "Failed to build list of response options from the archives: %s" % e)
+            return HttpResponseRedirect('/%s/%s/' % (cf.id, patch.id))
+
+        if form.is_valid():
+            if is_review:
+                txt = "The following review has been posted through the commitfest application:\n%s\n\n%s" % (
+                    "\n".join(["%-25s %s" % (f.label + ':', _review_status_string(form.cleaned_data[fn])) for (fn, f) in form.fields.items() if fn.startswith('review_')]),
+                    form.cleaned_data['message']
+                )
+            else:
+                txt = form.cleaned_data['message']
+
+            if int(form.cleaned_data['newstatus']) != poc.status:
+                poc.status = int(form.cleaned_data['newstatus'])
+                poc.save()
+                PatchHistory(patch=poc.patch, by=request.user, what='New status: %s' % poc.statusstring).save_and_notify()
+                txt += "\n\nThe new status of this patch is: %s\n" % poc.statusstring
+
+            msg = MIMEText(txt, _charset='utf-8')
+
+            if form.thread.subject.startswith('Re:'):
+                msg['Subject'] = form.thread.subject
+            else:
+                msg['Subject'] = 'Re: %s' % form.thread.subject
+
+            msg['To'] = settings.HACKERS_EMAIL
+            msg['From'] = UserWrapper(request.user).encoded_email_header
+
+            # CC the authors of a patch, if there are any
+            authors = list(patch.authors.all())
+            if len(authors):
+                msg['Cc'] = ", ".join([UserWrapper(a).encoded_email_header for a in authors])
+
+            msg['Date'] = formatdate(localtime=True)
+            msg['User-Agent'] = 'pgcommitfest'
+            msg['X-cfsender'] = request.user.username
+            msg['In-Reply-To'] = '<%s>' % form.respid
+            # We just add the "top" messageid and the one we're responding to.
+            # This along with in-reply-to should indicate clearly enough where
+            # in the thread the message belongs.
+            msg['References'] = '<%s> <%s>' % (form.thread.messageid, form.respid)
+            msg['Message-ID'] = make_msgid('pgcf')
+
+            uw = UserWrapper(request.user)
+            msgstring = msg.as_string()
+            send_mail(uw.email, settings.HACKERS_EMAIL, msgstring)
+            for a in authors:
+                # Actually send a copy directly to the author. Just setting the Cc field doesn't
+                # make it deliver the email...
+                send_mail(uw.email, UserWrapper(a).email, msgstring)
+
+            PatchHistory(patch=patch, by=request.user, what='Posted %s with messageid %s' % (what, msg['Message-ID'])).save()
+
+            messages.add_message(request, messages.INFO, "Your email has been queued for %s, and will be sent within a few minutes." % (settings.HACKERS_EMAIL))
+
+            return HttpResponseRedirect('/%s/%s/' % (cf.id, patch.id))
+    else:
+        try:
+            form = CommentForm(patch, poc, is_review)
+        except Exception, e:
+            messages.add_message(request, messages.ERROR, "Failed to build list of response options from the archives: %s" % e)
+            return HttpResponseRedirect('/%s/%s/' % (cf.id, patch.id))
+
+    return render(request, 'base_form.html', {
+        'cf': cf,
+        'form': form,
+        'patch': patch,
+        'extraformclass': 'patchcommentform',
+        '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>%s</i>, with sender set to <i>%s</i>!<br/>Please ensure that the email settings for your domain (<a href="https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail" target="_blank">DKIM</a>, <a href="https://en.wikipedia.org/wiki/SPF" target="_blank">SPF</a>) allow emails from external sources.' % (settings.HACKERS_EMAIL, UserWrapper(request.user).email),
+        'savebutton': 'Send %s' % what,
+    })
 
 @login_required
 @transaction.atomic
 def status(request, cfid, patchid, status):
-       poc = get_object_or_404(PatchOnCommitFest.objects.select_related(), commitfest__id=cfid, patch__id=patchid)
-
-       if poc.is_closed:
-               # We allow modification of patches in closed CFs *only* if it's the
-               # last CF that the patch is part of. If it's part of another CF, that
-               # is later than this one, tell the user to go there instead.
-               lastcf = PatchOnCommitFest.objects.filter(patch__id=patchid).order_by('-commitfest__startdate')[0]
-               if poc != lastcf:
-                       messages.add_message(request, messages.INFO, "The status of this patch cannot be changed in this commitfest. You must modify it in the one where it's open!")
-                       return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
-
-       if status == 'review':
-               newstatus = PatchOnCommitFest.STATUS_REVIEW
-       elif status == 'author':
-               newstatus = PatchOnCommitFest.STATUS_AUTHOR
-       elif status == 'committer':
-               newstatus = PatchOnCommitFest.STATUS_COMMITTER
-       else:
-               raise Exception("Can't happen")
-
-       if newstatus != poc.status:
-               # Only save it if something actually changed
-               poc.status = newstatus
-               poc.patch.set_modified()
-               poc.patch.save()
-               poc.save()
-
-               PatchHistory(patch=poc.patch, by=request.user, what='New status: %s' % poc.statusstring).save_and_notify()
-
-       return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
+    poc = get_object_or_404(PatchOnCommitFest.objects.select_related(), commitfest__id=cfid, patch__id=patchid)
+
+    if poc.is_closed:
+        # We allow modification of patches in closed CFs *only* if it's the
+        # last CF that the patch is part of. If it's part of another CF, that
+        # is later than this one, tell the user to go there instead.
+        lastcf = PatchOnCommitFest.objects.filter(patch__id=patchid).order_by('-commitfest__startdate')[0]
+        if poc != lastcf:
+            messages.add_message(request, messages.INFO, "The status of this patch cannot be changed in this commitfest. You must modify it in the one where it's open!")
+            return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
+
+    if status == 'review':
+        newstatus = PatchOnCommitFest.STATUS_REVIEW
+    elif status == 'author':
+        newstatus = PatchOnCommitFest.STATUS_AUTHOR
+    elif status == 'committer':
+        newstatus = PatchOnCommitFest.STATUS_COMMITTER
+    else:
+        raise Exception("Can't happen")
+
+    if newstatus != poc.status:
+        # Only save it if something actually changed
+        poc.status = newstatus
+        poc.patch.set_modified()
+        poc.patch.save()
+        poc.save()
+
+        PatchHistory(patch=poc.patch, by=request.user, what='New status: %s' % poc.statusstring).save_and_notify()
+
+    return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
 
 
 @login_required
 @transaction.atomic
 def close(request, cfid, patchid, status):
-       poc = get_object_or_404(PatchOnCommitFest.objects.select_related(), commitfest__id=cfid, patch__id=patchid)
-
-       if poc.is_closed:
-               # We allow modification of patches in closed CFs *only* if it's the
-               # last CF that the patch is part of. If it's part of another CF, that
-               # is later than this one, tell the user to go there instead.
-               lastcf = PatchOnCommitFest.objects.filter(patch__id=patchid).order_by('-commitfest__startdate')[0]
-               if poc != lastcf:
-                       messages.add_message(request, messages.INFO, "The status of this patch cannot be changed in this commitfest. You must modify it in the one where it's open!")
-                       return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
-
-       poc.leavedate = datetime.now()
-
-       # We know the status can't be one of the ones below, since we
-       # have checked that we're not closed yet. Therefor, we don't
-       # need to check if the individual status has changed.
-       if status == 'reject':
-               poc.status = PatchOnCommitFest.STATUS_REJECTED
-       elif status == 'withdrawn':
-               poc.status = PatchOnCommitFest.STATUS_WITHDRAWN
-       elif status == 'feedback':
-               poc.status = PatchOnCommitFest.STATUS_RETURNED
-       elif status == 'next':
-               # Only some patch statuses can actually be moved.
-               if poc.status in (PatchOnCommitFest.STATUS_AUTHOR,
-                                                 PatchOnCommitFest.STATUS_COMMITTED,
-                                                 PatchOnCommitFest.STATUS_NEXT,
-                                                 PatchOnCommitFest.STATUS_RETURNED,
-                                                 PatchOnCommitFest.STATUS_REJECTED):
-                       # Can't be moved!
-                       messages.error(request, "A patch in status {0} cannot be moved to next commitfest.".format(poc.statusstring))
-                       return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
-               elif poc.status in (PatchOnCommitFest.STATUS_REVIEW,
-                                                       PatchOnCommitFest.STATUS_COMMITTER):
-                       # This one can be moved
-                       pass
-               else:
-                       messages.error(request, "Invalid existing patch status")
-
-               oldstatus = poc.status
-
-               poc.status = PatchOnCommitFest.STATUS_NEXT
-               # Figure out the commitfest to actually put it on
-               newcf = CommitFest.objects.filter(status=CommitFest.STATUS_OPEN)
-               if len(newcf) == 0:
-                       # Ok, there is no open CF at all. Let's see if there is a
-                       # future one.
-                       newcf = CommitFest.objects.filter(status=CommitFest.STATUS_FUTURE)
-                       if len(newcf) == 0:
-                               messages.error(request,"No open and no future commitfest exists!")
-                               return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
-                       elif len(newcf) != 1:
-                               messages.error(request, "No open and multiple future commitfests exist!")
-                               return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
-               elif len(newcf) != 1:
-                       messages.error(request, "Multiple open commitfests exists!")
-                       return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
-               elif newcf[0] == poc.commitfest:
-                       # The current open CF is the same one that we are already on.
-                       # In this case, try to see if there is a future CF we can
-                       # move it to.
-                       newcf = CommitFest.objects.filter(status=CommitFest.STATUS_FUTURE)
-                       if len(newcf) == 0:
-                               messages.error(request, "Cannot move patch to the same commitfest, and no future commitfests exist!")
-                               return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
-                       elif len(newcf) != 1:
-                               messages.error(request, "Cannot move patch to the same commitfest, and multiple future commitfests exist!")
-                               return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
-               # Create a mapping to the new commitfest that we are bouncing
-               # this patch to.
-               newpoc = PatchOnCommitFest(patch=poc.patch,
-                                                                  commitfest=newcf[0],
-                                                                  status=oldstatus,
-                                                                  enterdate=datetime.now())
-               newpoc.save()
-       elif status == 'committed':
-               committer = get_object_or_404(Committer, user__username=request.GET['c'])
-               if committer != poc.patch.committer:
-                       # Committer changed!
-                       prevcommitter = poc.patch.committer
-                       poc.patch.committer = committer
-                       PatchHistory(patch=poc.patch, by=request.user, what='Changed committer to %s' % committer).save_and_notify(prevcommitter=prevcommitter)
-               poc.status = PatchOnCommitFest.STATUS_COMMITTED
-       else:
-               raise Exception("Can't happen")
-
-       poc.patch.set_modified()
-       poc.patch.save()
-       poc.save()
-
-       PatchHistory(patch=poc.patch, by=request.user, what='Closed in commitfest %s with status: %s' % (poc.commitfest, poc.statusstring)).save_and_notify()
-
-       return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
+    poc = get_object_or_404(PatchOnCommitFest.objects.select_related(), commitfest__id=cfid, patch__id=patchid)
+
+    if poc.is_closed:
+        # We allow modification of patches in closed CFs *only* if it's the
+        # last CF that the patch is part of. If it's part of another CF, that
+        # is later than this one, tell the user to go there instead.
+        lastcf = PatchOnCommitFest.objects.filter(patch__id=patchid).order_by('-commitfest__startdate')[0]
+        if poc != lastcf:
+            messages.add_message(request, messages.INFO, "The status of this patch cannot be changed in this commitfest. You must modify it in the one where it's open!")
+            return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
+
+    poc.leavedate = datetime.now()
+
+    # We know the status can't be one of the ones below, since we
+    # have checked that we're not closed yet. Therefor, we don't
+    # need to check if the individual status has changed.
+    if status == 'reject':
+        poc.status = PatchOnCommitFest.STATUS_REJECTED
+    elif status == 'withdrawn':
+        poc.status = PatchOnCommitFest.STATUS_WITHDRAWN
+    elif status == 'feedback':
+        poc.status = PatchOnCommitFest.STATUS_RETURNED
+    elif status == 'next':
+        # Only some patch statuses can actually be moved.
+        if poc.status in (PatchOnCommitFest.STATUS_AUTHOR,
+                          PatchOnCommitFest.STATUS_COMMITTED,
+                          PatchOnCommitFest.STATUS_NEXT,
+                          PatchOnCommitFest.STATUS_RETURNED,
+                          PatchOnCommitFest.STATUS_REJECTED):
+            # Can't be moved!
+            messages.error(request, "A patch in status {0} cannot be moved to next commitfest.".format(poc.statusstring))
+            return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
+        elif poc.status in (PatchOnCommitFest.STATUS_REVIEW,
+                            PatchOnCommitFest.STATUS_COMMITTER):
+            # This one can be moved
+            pass
+        else:
+            messages.error(request, "Invalid existing patch status")
+
+        oldstatus = poc.status
+
+        poc.status = PatchOnCommitFest.STATUS_NEXT
+        # Figure out the commitfest to actually put it on
+        newcf = CommitFest.objects.filter(status=CommitFest.STATUS_OPEN)
+        if len(newcf) == 0:
+            # Ok, there is no open CF at all. Let's see if there is a
+            # future one.
+            newcf = CommitFest.objects.filter(status=CommitFest.STATUS_FUTURE)
+            if len(newcf) == 0:
+                messages.error(request,"No open and no future commitfest exists!")
+                return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
+            elif len(newcf) != 1:
+                messages.error(request, "No open and multiple future commitfests exist!")
+                return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
+        elif len(newcf) != 1:
+            messages.error(request, "Multiple open commitfests exists!")
+            return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
+        elif newcf[0] == poc.commitfest:
+            # The current open CF is the same one that we are already on.
+            # In this case, try to see if there is a future CF we can
+            # move it to.
+            newcf = CommitFest.objects.filter(status=CommitFest.STATUS_FUTURE)
+            if len(newcf) == 0:
+                messages.error(request, "Cannot move patch to the same commitfest, and no future commitfests exist!")
+                return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
+            elif len(newcf) != 1:
+                messages.error(request, "Cannot move patch to the same commitfest, and multiple future commitfests exist!")
+                return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
+        # Create a mapping to the new commitfest that we are bouncing
+        # this patch to.
+        newpoc = PatchOnCommitFest(patch=poc.patch,
+                                   commitfest=newcf[0],
+                                   status=oldstatus,
+                                   enterdate=datetime.now())
+        newpoc.save()
+    elif status == 'committed':
+        committer = get_object_or_404(Committer, user__username=request.GET['c'])
+        if committer != poc.patch.committer:
+            # Committer changed!
+            prevcommitter = poc.patch.committer
+            poc.patch.committer = committer
+            PatchHistory(patch=poc.patch, by=request.user, what='Changed committer to %s' % committer).save_and_notify(prevcommitter=prevcommitter)
+        poc.status = PatchOnCommitFest.STATUS_COMMITTED
+    else:
+        raise Exception("Can't happen")
+
+    poc.patch.set_modified()
+    poc.patch.save()
+    poc.save()
+
+    PatchHistory(patch=poc.patch, by=request.user, what='Closed in commitfest %s with status: %s' % (poc.commitfest, poc.statusstring)).save_and_notify()
+
+    return HttpResponseRedirect('/%s/%s/' % (poc.commitfest.id, poc.patch.id))
 
 @login_required
 @transaction.atomic
 def reviewer(request, cfid, patchid, status):
-       get_object_or_404(CommitFest, pk=cfid)
-       patch = get_object_or_404(Patch, pk=patchid)
+    get_object_or_404(CommitFest, pk=cfid)
+    patch = get_object_or_404(Patch, pk=patchid)
 
-       is_reviewer = request.user in patch.reviewers.all()
+    is_reviewer = request.user in patch.reviewers.all()
 
-       if status=='become' and not is_reviewer:
-               patch.reviewers.add(request.user)
-               patch.set_modified()
-               PatchHistory(patch=patch, by=request.user, what='Added %s as reviewer' % request.user.username).save_and_notify()
-       elif status=='remove' and is_reviewer:
-               patch.reviewers.remove(request.user)
-               patch.set_modified()
-               PatchHistory(patch=patch, by=request.user, what='Removed %s from reviewers' % request.user.username).save_and_notify()
-       return HttpResponseRedirect('../../')
+    if status=='become' and not is_reviewer:
+        patch.reviewers.add(request.user)
+        patch.set_modified()
+        PatchHistory(patch=patch, by=request.user, what='Added %s as reviewer' % request.user.username).save_and_notify()
+    elif status=='remove' and is_reviewer:
+        patch.reviewers.remove(request.user)
+        patch.set_modified()
+        PatchHistory(patch=patch, by=request.user, what='Removed %s from reviewers' % request.user.username).save_and_notify()
+    return HttpResponseRedirect('../../')
 
 @login_required
 @transaction.atomic
 def committer(request, cfid, patchid, status):
-       get_object_or_404(CommitFest, pk=cfid)
-       patch = get_object_or_404(Patch, pk=patchid)
-
-       committer = list(Committer.objects.filter(user=request.user, active=True))
-       if len(committer) == 0:
-               return HttpResponseForbidden('Only committers can do that!')
-       committer = committer[0]
-
-       is_committer = committer == patch.committer
-
-       prevcommitter = patch.committer
-       if status=='become' and not is_committer:
-               patch.committer = committer
-               patch.set_modified()
-               PatchHistory(patch=patch, by=request.user, what='Added %s as committer' % request.user.username).save_and_notify(prevcommitter=prevcommitter)
-       elif status=='remove' and is_committer:
-               patch.committer = None
-               patch.set_modified()
-               PatchHistory(patch=patch, by=request.user, what='Removed %s from committers' % request.user.username).save_and_notify(prevcommitter=prevcommitter)
-       patch.save()
-       return HttpResponseRedirect('../../')
+    get_object_or_404(CommitFest, pk=cfid)
+    patch = get_object_or_404(Patch, pk=patchid)
+
+    committer = list(Committer.objects.filter(user=request.user, active=True))
+    if len(committer) == 0:
+        return HttpResponseForbidden('Only committers can do that!')
+    committer = committer[0]
+
+    is_committer = committer == patch.committer
+
+    prevcommitter = patch.committer
+    if status=='become' and not is_committer:
+        patch.committer = committer
+        patch.set_modified()
+        PatchHistory(patch=patch, by=request.user, what='Added %s as committer' % request.user.username).save_and_notify(prevcommitter=prevcommitter)
+    elif status=='remove' and is_committer:
+        patch.committer = None
+        patch.set_modified()
+        PatchHistory(patch=patch, by=request.user, what='Removed %s from committers' % request.user.username).save_and_notify(prevcommitter=prevcommitter)
+    patch.save()
+    return HttpResponseRedirect('../../')
 
 @login_required
 @transaction.atomic
 def subscribe(request, cfid, patchid, sub):
-       get_object_or_404(CommitFest, pk=cfid)
-       patch = get_object_or_404(Patch, pk=patchid)
-
-       if sub == 'un':
-               patch.subscribers.remove(request.user)
-               messages.info(request, "You have been unsubscribed from updates on this patch")
-       else:
-               patch.subscribers.add(request.user)
-               messages.info(request, "You have been subscribed to updates on this patch")
-       patch.save()
-       return HttpResponseRedirect("../")
+    get_object_or_404(CommitFest, pk=cfid)
+    patch = get_object_or_404(Patch, pk=patchid)
+
+    if sub == 'un':
+        patch.subscribers.remove(request.user)
+        messages.info(request, "You have been unsubscribed from updates on this patch")
+    else:
+        patch.subscribers.add(request.user)
+        messages.info(request, "You have been subscribed to updates on this patch")
+    patch.save()
+    return HttpResponseRedirect("../")
 
 @login_required
 @transaction.atomic
 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(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:
-               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 = []
-
-       if len(authors)==0 and len(reviewers)==0:
-               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" % UserWrapper(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(request, 'base_form.html', {
-               'cf': cf,
-               'form': form,
-               'title': 'Send email',
-               'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},],
-               'savebutton': 'Send email',
-       })
+    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(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:
+        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 = []
+
+    if len(authors)==0 and len(reviewers)==0:
+        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" % UserWrapper(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(request, 'base_form.html', {
+        'cf': cf,
+        'form': form,
+        'title': 'Send email',
+        'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},],
+        'savebutton': 'Send email',
+    })
 
 
 @csrf_exempt
 def thread_notify(request):
-       if request.method != 'POST':
-               return HttpResponseForbidden("Invalid method")
-
-       j = json.loads(request.body)
-       if j['apikey'] != settings.ARCHIVES_APIKEY:
-               return HttpResponseForbidden("Invalid API key")
-
-       for m in j['messageids']:
-               try:
-                       t = MailThread.objects.get(messageid=m)
-                       refresh_single_thread(t)
-               except Exception, e:
-                       # Just ignore it, we'll check again later
-                       pass
-
-       return HttpResponse(status=200)
+    if request.method != 'POST':
+        return HttpResponseForbidden("Invalid method")
+
+    j = json.loads(request.body)
+    if j['apikey'] != settings.ARCHIVES_APIKEY:
+        return HttpResponseForbidden("Invalid API key")
+
+    for m in j['messageids']:
+        try:
+            t = MailThread.objects.get(messageid=m)
+            refresh_single_thread(t)
+        except Exception, e:
+            # Just ignore it, we'll check again later
+            pass
+
+    return HttpResponse(status=200)
index 0475001bd373c6bdc97c9fab4e9659aacffb4e38..ee2df3de10dfb4c910b8b6bbd80e3c478a607b90 100644 (file)
@@ -2,8 +2,8 @@ from django.forms import TextInput
 from django.utils.safestring import mark_safe
 
 class ThreadPickWidget(TextInput):
-       def render(self, name, value, attrs=None):
-               attrs['class'] += ' threadpick-input'
-               html = super(ThreadPickWidget, self).render(name, value, attrs)
-               html = html + '&nbsp;<button class="btn btn-default attachThreadButton" id="btn_%s">Find thread</button>' % name
-               return mark_safe(html)
+    def render(self, name, value, attrs=None):
+        attrs['class'] += ' threadpick-input'
+        html = super(ThreadPickWidget, self).render(name, value, attrs)
+        html = html + '&nbsp;<button class="btn btn-default attachThreadButton" id="btn_%s">Find thread</button>' % name
+        return mark_safe(html)
index f2e7d198e2bb6dce56eb24d0d7461cec6c3b5ae1..f75ca37c83b9f412711504910a8735a6de58a9f0 100644 (file)
@@ -1,11 +1,11 @@
 from django.db import models
 
 class QueuedMail(models.Model):
-       sender = models.EmailField(max_length=100, null=False, blank=False)
-       receiver = models.EmailField(max_length=100, null=False, blank=False)
-       # We store the raw MIME message, so if there are any attachments or
-       # anything, we just push them right in there!
-       fullmsg = models.TextField(null=False, blank=False)
+    sender = models.EmailField(max_length=100, null=False, blank=False)
+    receiver = models.EmailField(max_length=100, null=False, blank=False)
+    # We store the raw MIME message, so if there are any attachments or
+    # anything, we just push them right in there!
+    fullmsg = models.TextField(null=False, blank=False)
 
-       def __unicode__(self):
-               return "%s: %s -> %s" % (self.pk, self.sender, self.receiver)
+    def __unicode__(self):
+        return "%s: %s -> %s" % (self.pk, self.sender, self.receiver)
index 9d587507e80ff203cff72bc0a069f88991b48ee4..9abea5368321fd09322745443f007ae3e6d5bb34 100644 (file)
@@ -9,37 +9,37 @@ from email import encoders
 from models import QueuedMail
 
 def send_simple_mail(sender, receiver, subject, msgtxt, sending_username, attachments=None):
-       # attachment format, each is a tuple of (name, mimetype,contents)
-       # content should already be base64 encoded
-       msg = MIMEMultipart()
-       msg['Subject'] = subject
-       msg['To'] = receiver
-       msg['From'] = sender
-       msg['Date'] = formatdate(localtime=True)
-       msg['User-Agent'] = 'pgcommitfest'
-       if sending_username:
-               msg['X-cfsender'] = sending_username
-
-       msg.attach(MIMEText(msgtxt, _charset='utf-8'))
-
-       if attachments:
-               for filename, contenttype, content in attachments:
-                       main,sub = contenttype.split('/')
-                       part = MIMENonMultipart(main,sub)
-                       part.set_payload(content)
-                       part.add_header('Content-Disposition', 'attachment; filename="%s"' % filename)
-                       encoders.encode_base64(part)
-                       msg.attach(part)
-
-
-       # Just write it to the queue, so it will be transactionally rolled back
-       QueuedMail(sender=sender, receiver=receiver, fullmsg=msg.as_string()).save()
+    # attachment format, each is a tuple of (name, mimetype,contents)
+    # content should already be base64 encoded
+    msg = MIMEMultipart()
+    msg['Subject'] = subject
+    msg['To'] = receiver
+    msg['From'] = sender
+    msg['Date'] = formatdate(localtime=True)
+    msg['User-Agent'] = 'pgcommitfest'
+    if sending_username:
+        msg['X-cfsender'] = sending_username
+
+    msg.attach(MIMEText(msgtxt, _charset='utf-8'))
+
+    if attachments:
+        for filename, contenttype, content in attachments:
+            main,sub = contenttype.split('/')
+            part = MIMENonMultipart(main,sub)
+            part.set_payload(content)
+            part.add_header('Content-Disposition', 'attachment; filename="%s"' % filename)
+            encoders.encode_base64(part)
+            msg.attach(part)
+
+
+    # Just write it to the queue, so it will be transactionally rolled back
+    QueuedMail(sender=sender, receiver=receiver, fullmsg=msg.as_string()).save()
 
 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()
+    # Send an email, prepared as the full MIME encoded mail already
+    QueuedMail(sender=sender, receiver=receiver, fullmsg=fullmsg).save()
 
 def send_template_mail(sender, senderaccountname, receiver, subject, templatename, templateattr={}, usergenerated=False):
-       send_simple_mail(sender, receiver, subject,
-                                        get_template(templatename).render(templateattr),
-                                        senderaccountname)
+    send_simple_mail(sender, receiver, subject,
+                     get_template(templatename).render(templateattr),
+                     senderaccountname)
index 1c1a860abea2613eb3d07df45cc690122fd5c07c..575d90fb40316ee7059e41dcf81b77afec17f3a9 100644 (file)
@@ -6,7 +6,7 @@ TEMPLATE_DEBUG = DEBUG
 ALLOWED_HOSTS = ['*']
 
 ADMINS = (
-       ('webmaster@postgresql.org', 'webmaster@postgresql.org'),
+    ('webmaster@postgresql.org', 'webmaster@postgresql.org'),
 )
 
 MANAGERS = ADMINS
@@ -98,19 +98,19 @@ MIDDLEWARE_CLASSES = (
 ROOT_URLCONF = 'pgcommitfest.urls'
 
 TEMPLATES = [{
-       'BACKEND': 'django.template.backends.django.DjangoTemplates',
-       'DIRS': ['global_templates'],
-       'OPTIONS': {
-               'context_processors': [
-                       'django.template.context_processors.request',
-                       'django.contrib.auth.context_processors.auth',
-                       'django.contrib.messages.context_processors.messages',
-               ],
-               'loaders': [
-                       'django.template.loaders.filesystem.Loader',
-                       'django.template.loaders.app_directories.Loader',
-               ],
-       },
+    'BACKEND': 'django.template.backends.django.DjangoTemplates',
+    'DIRS': ['global_templates'],
+    'OPTIONS': {
+        'context_processors': [
+            'django.template.context_processors.request',
+            'django.contrib.auth.context_processors.auth',
+            'django.contrib.messages.context_processors.messages',
+        ],
+        'loaders': [
+            'django.template.loaders.filesystem.Loader',
+            'django.template.loaders.app_directories.Loader',
+        ],
+    },
 }]
 
 INSTALLED_APPS = (
@@ -123,10 +123,10 @@ INSTALLED_APPS = (
     'django.contrib.admin',
     # Uncomment the next line to enable admin documentation:
     # 'django.contrib.admindocs',
-       'pgcommitfest.selectable',
-       'pgcommitfest.commitfest',
-       'pgcommitfest.mailqueue',
-       'pgcommitfest.userprofile',
+    'pgcommitfest.selectable',
+    'pgcommitfest.commitfest',
+    'pgcommitfest.mailqueue',
+    'pgcommitfest.userprofile',
 )
 
 AUTHENTICATION_BACKENDS = (
index c99922b661f0107068d768e2726da96a6684c377..4b73d1f4fce95652a01ab0b678b7c677524aaf9c 100644 (file)
@@ -3,6 +3,6 @@ from django.contrib import admin
 from models import UserProfile
 
 class UserProfileAdmin(admin.ModelAdmin):
-       list_display = ('user', )
+    list_display = ('user', )
 
 admin.site.register(UserProfile, UserProfileAdmin)
index b177ef13231227ca5da734a5153534c1dfaea08b..400a06881e4c0ee9f0577d11e999f5a40447dd66 100644 (file)
@@ -4,40 +4,40 @@ from django.contrib.auth.models import User
 from models import UserProfile, UserExtraEmail
 
 class UserProfileForm(forms.ModelForm):
-       class Meta:
-               model = UserProfile
-               exclude = ('user', )
+    class Meta:
+        model = UserProfile
+        exclude = ('user', )
 
-       def __init__(self, user, *args, **kwargs):
-               super(UserProfileForm, self).__init__(*args, **kwargs)
-               self.user = 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)
-               self.fields['notifyemail'].empty_label=self.user.email
-               self.fields['notifyemail'].queryset=UserExtraEmail.objects.filter(user=self.user, confirmed=True)
+        self.fields['selectedemail'].empty_label=self.user.email
+        self.fields['selectedemail'].queryset=UserExtraEmail.objects.filter(user=self.user, confirmed=True)
+        self.fields['notifyemail'].empty_label=self.user.email
+        self.fields['notifyemail'].queryset=UserExtraEmail.objects.filter(user=self.user, confirmed=True)
 
 class MailForm(forms.Form):
-       email = forms.EmailField()
-       email2 = forms.EmailField(label="Repeat email")
+    email = forms.EmailField()
+    email2 = forms.EmailField(label="Repeat email")
 
-       def clean_email(self):
-               email = self.cleaned_data['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")
+        if User.objects.filter(email=email).exists():
+            raise forms.ValidationError("This email is already in use by another account")
 
-               return email
+        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']
+    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")
+        if email1 != email2:
+            raise forms.ValidationError("Email addresses don't match")
 
-               return email2
+        return email2
index f5e58872eb6d742f97853a26f0577e6772aeb8d0..367e0bdc8a0524013ed3ce33fc98e3ed75de6f80 100644 (file)
@@ -2,30 +2,30 @@ 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)
+    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
+    def __unicode__(self):
+        return self.email
 
-       class Meta:
-               ordering = ('user', 'email')
-               unique_together = (('user', 'email'),)
+    class Meta:
+        ordering = ('user', 'email')
+        unique_together = (('user', 'email'),)
 
 
 class UserProfile(models.Model):
-       user = models.OneToOneField(User, null=False, blank=False)
-       selectedemail = models.ForeignKey(UserExtraEmail, null=True, blank=True,
-                                                                         verbose_name='Sender email')
-       notifyemail = models.ForeignKey(UserExtraEmail, null=True, blank=True,
-                                                                       verbose_name='Notifications sent to',
-                                                                       related_name='notifier')
-       notify_all_author = models.BooleanField(null=False, blank=False, default=False, verbose_name="Notify on all where author")
-       notify_all_reviewer = models.BooleanField(null=False, blank=False, default=False, verbose_name="Notify on all where reviewer")
-       notify_all_committer = models.BooleanField(null=False, blank=False, default=False, verbose_name="Notify on all where committer")
+    user = models.OneToOneField(User, null=False, blank=False)
+    selectedemail = models.ForeignKey(UserExtraEmail, null=True, blank=True,
+                                      verbose_name='Sender email')
+    notifyemail = models.ForeignKey(UserExtraEmail, null=True, blank=True,
+                                    verbose_name='Notifications sent to',
+                                    related_name='notifier')
+    notify_all_author = models.BooleanField(null=False, blank=False, default=False, verbose_name="Notify on all where author")
+    notify_all_reviewer = models.BooleanField(null=False, blank=False, default=False, verbose_name="Notify on all where reviewer")
+    notify_all_committer = models.BooleanField(null=False, blank=False, default=False, verbose_name="Notify on all where committer")
 
-       def __unicode__(self):
-               return unicode(self.user)
+    def __unicode__(self):
+        return unicode(self.user)
index 89b4e28cf3061bbb8b1fb5761e7b5fb8c66e9c7f..bd2bcc8268aa80861caf7b5eb42ce28de501e6fd 100644 (file)
@@ -6,34 +6,34 @@ from email.header import Header
 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()
+    """
+    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
+    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
+    @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
 
-       @property
-       def encoded_email_header(self):
-               return formataddr((
-                       str(Header(u"%s %s" % (self.user.first_name, self.user.last_name), 'utf-8')),
-                       self.email))
+    @property
+    def encoded_email_header(self):
+        return formataddr((
+            str(Header(u"%s %s" % (self.user.first_name, self.user.last_name), 'utf-8')),
+            self.email))
index ced36b7589b15d4b2644be501eed73f396f52902..62e09ea2a67aad7c8e65fc5108d5b5ecc2e8d07b 100644 (file)
@@ -17,83 +17,83 @@ from util import generate_random_token
 @login_required
 @transaction.atomic
 def userprofile(request):
-       (profile, created) = UserProfile.objects.get_or_create(user=request.user)
-       form = mailform = None
+    (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,
-                                                                  request.user.username,
-                                                                  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 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,
+                                   request.user.username,
+                                   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()
+    if not form:
+        form = UserProfileForm(request.user, instance=profile)
+    if not mailform:
+        mailform = MailForm()
 
-       extramails = UserExtraEmail.objects.filter(user=request.user)
+    extramails = UserExtraEmail.objects.filter(user=request.user)
 
-       return render(request, 'userprofileform.html', {
-               'form': form,
-               'extramails': extramails,
-               'mailform': mailform,
-               })
+    return render(request, 'userprofileform.html', {
+        'form': form,
+        'extramails': extramails,
+        'mailform': mailform,
+        })
 
 @login_required
 @transaction.atomic
 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:
+        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('../')
+    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('../')
+    messages.info(request, "Email address %s deleted." % e.email)
+    e.delete()
+    return HttpResponseRedirect('../')
 
 @login_required
 @transaction.atomic
 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)
+    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("../../")
+    return HttpResponseRedirect("../../")