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")
####
# 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.
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
# 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
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)
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')
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']
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)
@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)
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'],
+ },
+ )
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),
(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"),
]
# 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)
@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
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},],
+ })
@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 ')
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)
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,
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)
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 + ' <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 + ' <button class="btn btn-default attachThreadButton" id="btn_%s">Find thread</button>' % name
+ return mark_safe(html)
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)
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)
ALLOWED_HOSTS = ['*']
ADMINS = (
- ('webmaster@postgresql.org', 'webmaster@postgresql.org'),
+ ('webmaster@postgresql.org', 'webmaster@postgresql.org'),
)
MANAGERS = ADMINS
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 = (
'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 = (
from models import UserProfile
class UserProfileAdmin(admin.ModelAdmin):
- list_display = ('user', )
+ list_display = ('user', )
admin.site.register(UserProfile, UserProfileAdmin)
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
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)
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))
@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("../../")