Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(75)

Side by Side Diff: rietveld/views.py

Issue 776: Allow reviewers to change the reviewers list (Closed) SVN Base: http://rietveld.googlecode.com/svn/trunk/
Patch Set: Automatically add current non-owner to reviewer list, remove duplicates in reviewer list Created 3 months, 2 weeks ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2008 Google Inc. 1 # Copyright 2008 Google Inc.
2 # 2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); 3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License. 4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at 5 # You may obtain a copy of the License at
6 # 6 #
7 # http://www.apache.org/licenses/LICENSE-2.0 7 # http://www.apache.org/licenses/LICENSE-2.0
8 # 8 #
9 # Unless required by applicable law or agreed to in writing, software 9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, 10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and 12 # See the License for the specific language governing permissions and
13 # limitations under the License. 13 # limitations under the License.
14 14
15 """Views for Rietveld. 15 """Views for Rietveld.
16 16
17 This requires Django 0.97.pre. 17 This requires Django 0.97.pre.
18 """ 18 """
19 19
20 20
21 ### Imports ### 21 ### Imports ###
22 22
23 23
24 # Python imports 24 # Python imports
25 import os 25 import os
26 import cgi 26 import cgi
27 import random 27 import random
28 import logging 28 import logging
29 import binascii 29 import binascii
30 import sets
GvR 2008/05/11 19:20:49 This is Python 2.5, it has a built-in set type. (E
30 31
31 # AppEngine imports 32 # AppEngine imports
32 from google.appengine.api import mail 33 from google.appengine.api import mail
33 from google.appengine.api import users 34 from google.appengine.api import users
34 from google.appengine.api import urlfetch 35 from google.appengine.api import urlfetch
35 from google.appengine.ext import db 36 from google.appengine.ext import db
36 from google.appengine.ext.db import djangoforms 37 from google.appengine.ext.db import djangoforms
37 38
38 # DeadlineExceededError can live in two different places 39 # DeadlineExceededError can live in two different places
39 # TODO(guido): simplify once this is fixed. 40 # TODO(guido): simplify once this is fixed.
40 try: 41 try:
41 # When deployed 42 # When deployed
42 from google.appengine.runtime import DeadlineExceededError 43 from google.appengine.runtime import DeadlineExceededError
43 except ImportError: 44 except ImportError:
44 # In the development server 45 # In the development server
45 from google.appengine.runtime.apiproxy_errors import DeadlineExceededError 46 from google.appengine.runtime.apiproxy_errors import DeadlineExceededError
46 47
47 # Django imports 48 # Django imports
48 # TODO(guido): Don't import classes/functions directly. 49 # TODO(guido): Don't import classes/functions directly.
49 from django import newforms as forms 50 from django import newforms as forms
50 from django.http import HttpResponse, HttpResponseRedirect 51 from django.http import HttpResponse, HttpResponseRedirect
51 from django.http import HttpResponseForbidden, HttpResponseNotFound 52 from django.http import HttpResponseForbidden, HttpResponseNotFound
52 from django.shortcuts import render_to_response 53 from django.shortcuts import render_to_response
53 import django.template 54 import django.template
54 55
55 # Local imports 56 # Local imports
56 import models 57 import models
57 import engine 58 import engine
58 import library 59 import library
59 import patching 60 import patching
60 61
61 # Add our own template library. 62 # Add our own template library.
62 if not django.template.libraries.get('rietveld.library', None): 63 if not django.template.libraries.get('rietveld.library', None):
63 django.template.add_to_builtins('rietveld.library') 64 django.template.add_to_builtins('rietveld.library')
64 65
65 66
66 ### Constants ### 67 ### Constants ###
67 68
68 69
69 SENDER = 'svncodereview@gmail.com' # An administrator allowed to send mail 70 SENDER = 'svncodereview@gmail.com' # An administrator allowed to send mail
70 71
71 IS_DEV = os.environ['SERVER_SOFTWARE'].startswith('Dev') # Development server 72 IS_DEV = os.environ['SERVER_SOFTWARE'].startswith('Dev') # Development server
72 73
73 74
74 ### Form classes ### 75 ### Form classes ###
75 76
76 77
77 class IssueBaseForm(forms.Form): 78 class IssueBaseForm(forms.Form):
78 79
79 subject = forms.CharField(max_length=100, 80 subject = forms.CharField(max_length=100,
(...skipping 60 matching lines...) Show 10 above Show 10 below
140 max_length=2083, 141 max_length=2083,
141 widget=forms.TextInput(attrs={'size': 60})) 142 widget=forms.TextInput(attrs={'size': 60}))
142 143
143 144
144 class UploadForm(forms.Form): 145 class UploadForm(forms.Form):
145 146
146 subject = forms.CharField(max_length=100) 147 subject = forms.CharField(max_length=100)
147 description = forms.CharField(max_length=10000, required=False) 148 description = forms.CharField(max_length=10000, required=False)
148 base = forms.CharField(max_length=2000) 149 base = forms.CharField(max_length=2000)
149 data = forms.FileField() 150 data = forms.FileField()
150 issue = forms.IntegerField(required=False) 151 issue = forms.IntegerField(required=False)
151 152
152 def get_base(self): 153 def get_base(self):
153 return self.cleaned_data.get('base') 154 return self.cleaned_data.get('base')
154 155
155 156
156 class EditForm(IssueBaseForm): 157 class EditForm(IssueBaseForm):
157 pass 158 pass
158 159
159 160
160 class RepoForm(djangoforms.ModelForm): 161 class RepoForm(djangoforms.ModelForm):
161 162
162 class Meta: 163 class Meta:
163 model = models.Repository 164 model = models.Repository
164 exclude = ['owner'] 165 exclude = ['owner']
165 166
166 167
167 class BranchForm(djangoforms.ModelForm): 168 class BranchForm(djangoforms.ModelForm):
168 169
169 class Meta: 170 class Meta:
170 model = models.Branch 171 model = models.Branch
171 exclude = ['owner'] 172 exclude = ['owner']
172 173
173 174
174 class PublishForm(forms.Form): 175 class PublishForm(forms.Form):
175 176
176 subject = forms.CharField(max_length=100, 177 subject = forms.CharField(max_length=100,
177 widget=forms.TextInput(attrs={'size': 60})) 178 widget=forms.TextInput(attrs={'size': 60}))
178 reviewers = forms.CharField(required=False, 179 reviewers = forms.CharField(required=False,
179 max_length=1000, 180 max_length=1000,
180 widget=forms.TextInput(attrs={'size': 60})) 181 widget=forms.TextInput(attrs={'size': 60}))
181 send_mail = forms.BooleanField() 182 send_mail = forms.BooleanField()
182 message = forms.CharField(required=False, 183 message = forms.CharField(required=False,
183 max_length=10000, 184 max_length=10000,
184 widget=forms.Textarea(attrs={'cols': 60})) 185 widget=forms.Textarea(attrs={'cols': 60}))
185 186
186 187
187 class MiniPublishForm(forms.Form): 188 class MiniPublishForm(forms.Form):
188 189
189 send_mail = forms.BooleanField() 190 send_mail = forms.BooleanField()
191 reviewers = forms.CharField(required=False,
192 max_length=1000,
193 widget=forms.TextInput(attrs={'size': 60}))
190 message = forms.CharField(required=False, 194 message = forms.CharField(required=False,
191 max_length=10000, 195 max_length=10000,
192 widget=forms.Textarea(attrs={'cols': 60})) 196 widget=forms.Textarea(attrs={'cols': 60}))
193 197
194 198
195 class SettingsForm(forms.Form): 199 class SettingsForm(forms.Form):
196 200
197 nickname = forms.CharField(max_length=30) 201 nickname = forms.CharField(max_length=30)
198 202
199 203
200 ### Helper functions ### 204 ### Helper functions ###
201 205
202 206
203 # Counter displayed (by respond()) below) on every page showing how 207 # Counter displayed (by respond()) below) on every page showing how
204 # many requests the current incarnation has handled, not counting 208 # many requests the current incarnation has handled, not counting
205 # redirects. Rendered by templates/base.html. 209 # redirects. Rendered by templates/base.html.
206 counter = 0 210 counter = 0
207 211
208 def respond(request, template, params=None): 212 def respond(request, template, params=None):
209 """Helper to render a response, passing standard stuff to the response. 213 """Helper to render a response, passing standard stuff to the response.
210 214
211 Args: 215 Args:
212 request: The request object. 216 request: The request object.
213 template: The template name; '.html' is appended automatically. 217 template: The template name; '.html' is appended automatically.
214 params: A dict giving the template parameters; modified in-place. 218 params: A dict giving the template parameters; modified in-place.
215 219
216 Returns: 220 Returns:
217 Whatever render_to_response(template, params) returns. 221 Whatever render_to_response(template, params) returns.
218 222
219 Raises: 223 Raises:
220 Whatever render_to_response(template, params) raises. 224 Whatever render_to_response(template, params) raises.
221 """ 225 """
222 global counter 226 global counter
223 counter += 1 227 counter += 1
224 if params is None: 228 if params is None:
225 params = {} 229 params = {}
226 must_choose_nickname = False 230 must_choose_nickname = False
227 if request.user is not None: 231 if request.user is not None:
228 account = models.Account.get_account_for_user(request.user) 232 account = models.Account.get_account_for_user(request.user)
229 delta = account.created - account.modified 233 delta = account.created - account.modified
230 if delta.days < 0: 234 if delta.days < 0:
231 delta = -delta 235 delta = -delta
232 must_choose_nickname = delta.days == 0 and delta.seconds < 2 236 must_choose_nickname = delta.days == 0 and delta.seconds < 2
233 params['request'] = request 237 params['request'] = request
234 params['counter'] = counter 238 params['counter'] = counter
235 params['user'] = request.user 239 params['user'] = request.user
236 params['is_admin'] = request.user_is_admin 240 params['is_admin'] = request.user_is_admin
237 params['is_dev'] = IS_DEV 241 params['is_dev'] = IS_DEV
238 params['sign_in'] = users.create_login_url(request.path) 242 params['sign_in'] = users.create_login_url(request.path)
239 params['sign_out'] = users.create_logout_url(request.path) 243 params['sign_out'] = users.create_logout_url(request.path)
(...skipping 280 matching lines...) Show 10 above Show 10 below
520 assert url 524 assert url
521 try: 525 try:
522 fetch_result = urlfetch.fetch(url) 526 fetch_result = urlfetch.fetch(url)
523 except Exception, err: 527 except Exception, err:
524 form.errors['url'] = [str(err)] 528 form.errors['url'] = [str(err)]
525 return None 529 return None
526 if fetch_result.status_code != 200: 530 if fetch_result.status_code != 200:
527 form.errors['url'] = ['HTTP status code %s' % fetch_result.status_code] 531 form.errors['url'] = ['HTTP status code %s' % fetch_result.status_code]
528 return None 532 return None
529 data = db.Blob(fetch_result.content) 533 data = db.Blob(fetch_result.content)
530 534
531 return data, url 535 return data, url
532 536
533 537
534 @issue_owner_required 538 @issue_owner_required
535 def add(request): 539 def add(request):
536 """/<issue>/add - Add a new PatchSet to an existing Issue.""" 540 """/<issue>/add - Add a new PatchSet to an existing Issue."""
537 issue = request.issue 541 issue = request.issue
538 form = AddForm(request.POST, request.FILES) 542 form = AddForm(request.POST, request.FILES)
539 if not add_patchset_from_form(request, issue, form): 543 if not add_patchset_from_form(request, issue, form):
540 return show(request, issue.key().id(), form) 544 return show(request, issue.key().id(), form)
541 return HttpResponseRedirect('/%s' % issue.key().id()) 545 return HttpResponseRedirect('/%s' % issue.key().id())
542 546
543 547
544 def add_patchset_from_form(request, issue, form, message_key='message'): 548 def add_patchset_from_form(request, issue, form, message_key='message'):
545 """Helper for add() and upload().""" 549 """Helper for add() and upload()."""
546 # TODO(guido): use a transaction like in _make_new(); may be share more code? 550 # TODO(guido): use a transaction like in _make_new(); may be share more code?
547 if form.is_valid(): 551 if form.is_valid():
548 data_url = _get_data_url(form) 552 data_url = _get_data_url(form)
549 if not form.is_valid(): 553 if not form.is_valid():
550 return False 554 return False
551 data, url = data_url 555 data, url = data_url
552 message = form.cleaned_data[message_key] 556 message = form.cleaned_data[message_key]
553 patchset = models.PatchSet(issue=issue, message=message, data=data, url=url, 557 patchset = models.PatchSet(issue=issue, message=message, data=data, url=url,
554 base=issue.base, owner=request.user, parent=issue) 558 base=issue.base, owner=request.user, parent=issue)
555 patchset.put() 559 patchset.put()
556 560
557 patches = engine.ParsePatchSet(patchset) 561 patches = engine.ParsePatchSet(patchset)
558 if not patches: 562 if not patches:
559 patchset.delete() 563 patchset.delete()
560 errkey = url and 'url' or 'data' 564 errkey = url and 'url' or 'data'
561 form.errors[errkey] = ['Patch set contains no recognizable patches'] 565 form.errors[errkey] = ['Patch set contains no recognizable patches']
562 return False 566 return False
563 db.put(patches) 567 db.put(patches)
564 issue.put() # To update last modified time 568 issue.put() # To update last modified time
565 return True 569 return True
566 570
567 571
568 def _get_reviewers(form): 572 def _get_reviewers(form):
569 """Helper to return the list of reviewers, or None for error.""" 573 """Helper to return the list of reviewers, or None for error."""
570 reviewers = [] 574 reviewers = sets.Set()
571 raw_reviewers = form.cleaned_data.get('reviewers') 575 raw_reviewers = form.cleaned_data.get('reviewers')
572 if raw_reviewers: 576 if raw_reviewers:
573 for reviewer in raw_reviewers.split(','): 577 for reviewer in raw_reviewers.split(','):
574 reviewer = reviewer.strip() 578 reviewer = reviewer.strip()
575 if reviewer: 579 if reviewer:
576 try: 580 try:
577 reviewer = db.Email(reviewer) 581 reviewer = db.Email(reviewer)
578 if reviewer.count('@') != 1: 582 if reviewer.count('@') != 1:
579 raise db.BadValueError('Invalid email address: %s' % reviewer) 583 raise db.BadValueError('Invalid email address: %s' % reviewer)
580 head, tail = reviewer.split('@') 584 head, tail = reviewer.split('@')
581 if '.' not in tail: 585 if '.' not in tail:
582 raise db.BadValueError('Invalid email address: %s' % reviewer) 586 raise db.BadValueError('Invalid email address: %s' % reviewer)
583 except db.BadValueError, err: 587 except db.BadValueError, err:
584 form.errors['reviewers'] = [unicode(err)] 588 form.errors['reviewers'] = [unicode(err)]
585 return None 589 return None
586 reviewers.append(reviewer) 590 reviewers.add(reviewer)
587 return reviewers 591 return list(reviewers)
GvR 2008/05/11 19:20:49 I'd prefer to keep reviewers in the order that the
588 592
589 593
590 594
591 @issue_required 595 @issue_required
592 def show(request, form=AddForm()): 596 def show(request, form=AddForm()):
593 """/<issue> - Show an issue.""" 597 """/<issue> - Show an issue."""
594 issue = request.issue 598 issue = request.issue
595 patchsets = list(issue.patchset_set.order('created')) 599 patchsets = list(issue.patchset_set.order('created'))
596 issue.draft_count = 0 600 issue.draft_count = 0
597 issue.comment_count = 0 601 issue.comment_count = 0
598 for patchset in patchsets: 602 for patchset in patchsets:
599 patchset.patches = list(patchset.patch_set.order('filename')) 603 patchset.patches = list(patchset.patch_set.order('filename'))
600 patchset.n_comments = 0 604 patchset.n_comments = 0
601 for patch in patchset.patches: 605 for patch in patchset.patches:
602 patchset.n_comments += patch.num_comments 606 patchset.n_comments += patch.num_comments
603 issue.comment_count += patchset.n_comments 607 issue.comment_count += patchset.n_comments
604 patchset.n_drafts = 0 608 patchset.n_drafts = 0
605 if request.user: 609 if request.user:
606 for patch in patchset.patches: 610 for patch in patchset.patches:
607 patchset.n_drafts += patch.num_drafts 611 patchset.n_drafts += patch.num_drafts
608 issue.draft_count += patchset.n_drafts 612 issue.draft_count += patchset.n_drafts
609 last_patchset = first_patch = None 613 last_patchset = first_patch = None
610 if patchsets: 614 if patchsets:
611 last_patchset = patchsets[-1] 615 last_patchset = patchsets[-1]
612 if last_patchset.patches: 616 if last_patchset.patches:
613 first_patch = last_patchset.patches[0] 617 first_patch = last_patchset.patches[0]
614 messages = list(issue.message_set.order('date')) 618 messages = list(issue.message_set.order('date'))
615 return respond(request, 'issue.html', 619 return respond(request, 'issue.html',
616 {'issue': issue, 'patchsets': patchsets, 620 {'issue': issue, 'patchsets': patchsets,
617 'messages': messages, 'form': form, 621 'messages': messages, 'form': form,
618 'last_patchset': last_patchset, 622 'last_patchset': last_patchset,
619 'first_patch': first_patch}) 623 'first_patch': first_patch})
620 624
621 625
622 @issue_owner_required 626 @issue_owner_required
623 def edit(request): 627 def edit(request):
624 """/<issue>/edit - Edit an issue.""" 628 """/<issue>/edit - Edit an issue."""
625 issue = request.issue 629 issue = request.issue
626 base = issue.base 630 base = issue.base
627 631
628 if request.method != 'POST': 632 if request.method != 'POST':
629 form = EditForm(initial={'subject': issue.subject, 633 form = EditForm(initial={'subject': issue.subject,
630 'description': issue.description, 634 'description': issue.description,
631 'base': base, 635 'base': base,
632 'reviewers': ', '.join(issue.reviewers)}) 636 'reviewers': ', '.join(issue.reviewers)})
633 form.set_branch_choices(base) 637 form.set_branch_choices(base)
634 return respond(request, 'edit.html', {'issue': issue, 'form': form}) 638 return respond(request, 'edit.html', {'issue': issue, 'form': form})
635 639
636 form = EditForm(request.POST) 640 form = EditForm(request.POST)
637 form.set_branch_choices() 641 form.set_branch_choices()
(...skipping 257 matching lines...) Show 10 above Show 10 below
895 'ORDER BY date', 899 'ORDER BY date',
896 patch=patch, lineno=lineno, left=left) 900 patch=patch, lineno=lineno, left=left)
897 comments = list(c for c in query if not c.draft or c.author == request.user) 901 comments = list(c for c in query if not c.draft or c.author == request.user)
898 if comment is not None and comment.author is None: 902 if comment is not None and comment.author is None:
899 # Show anonymous draft even though we don't save it 903 # Show anonymous draft even though we don't save it
900 comments.append(comment) 904 comments.append(comment)
901 if not comments: 905 if not comments:
902 return HttpResponse(' ') 906 return HttpResponse(' ')
903 for c in comments: 907 for c in comments:
904 c.complete(patch) 908 c.complete(patch)
905 return render_to_response('inline_comment.html', 909 return render_to_response('inline_comment.html',
906 {'user': request.user, 910 {'user': request.user,
907 'patch': patch, 911 'patch': patch,
908 'patchset': patchset, 912 'patchset': patchset,
909 'issue': issue, 913 'issue': issue,
910 'comments': comments, 914 'comments': comments,
911 'lineno': lineno, 915 'lineno': lineno,
912 'snapshot': snapshot, 916 'snapshot': snapshot,
913 'side': side}) 917 'side': side})
914 918
915 919
916 PUBLISH_MAIL_TEMPLATE = """Dear %s, 920 PUBLISH_MAIL_TEMPLATE = """Dear %s,
917 921
918 New code review comments by %s have been published. 922 New code review comments by %s have been published.
919 Please go to %s to read them. 923 Please go to %s to read them.
920 924
921 Message: 925 Message:
922 %s 926 %s
923 927
924 Details: 928 Details:
925 %s 929 %s
926 930
927 Issue Description: 931 Issue Description:
928 %s 932 %s
929 933
930 Sincerely, 934 Sincerely,
931 935
932 Your friendly code review daemon (%s). 936 Your friendly code review daemon (%s).
933 """ 937 """
934 938
935 @issue_required 939 @issue_required
936 @login_required 940 @login_required
937 def publish(request): 941 def publish(request):
938 """ /<issue>/publish - Publish draft comments and send mail.""" 942 """ /<issue>/publish - Publish draft comments and send mail."""
939 issue = request.issue 943 issue = request.issue
940 if request.user == issue.owner: 944 if request.user == issue.owner:
941 form_class = PublishForm 945 form_class = PublishForm
942 else: 946 else:
943 form_class = MiniPublishForm 947 form_class = MiniPublishForm
944 if request.method != 'POST': 948 if request.method != 'POST':
949 if request.user != issue.owner and (not request.user.email()
950 in issue.reviewers):
GvR 2008/05/11 19:20:49 You can use 'not in' for the latter test.
951 issue.reviewers.append(request.user.email())
GvR 2008/05/11 19:20:49 I wouldn't assign to issue.reviewers here, since y
945 form = form_class(initial={'subject': issue.subject, 952 form = form_class(initial={'subject': issue.subject,
946 'reviewers': ', '.join(issue.reviewers), 953 'reviewers': ', '.join(issue.reviewers),
947 'send_mail': True, 954 'send_mail': True,
948 }) 955 })
949 return respond(request, 'publish.html', {'form': form, 'issue': issue}) 956 return respond(request, 'publish.html', {'form': form, 'issue': issue})
950 957
951 form = form_class(request.POST) 958 form = form_class(request.POST)
952 if form.is_valid(): 959 if form.is_valid():
953 reviewers = _get_reviewers(form) 960 reviewers = _get_reviewers(form)
954 if not form.is_valid(): 961 if not form.is_valid():
955 return respond(request, 'publish.html', {'form': form, 'issue': issue}) 962 return respond(request, 'publish.html', {'form': form, 'issue': issue})
956 tbd = [] # List of things to put() after all is said and done 963 tbd = [] # List of things to put() after all is said and done
957 if request.user == issue.owner: 964 if request.user == issue.owner:
958 subject = form.cleaned_data['subject'] 965 subject = form.cleaned_data['subject']
959 issue.subject = subject 966 issue.subject = subject
960 issue.reviewers = reviewers 967 issue.reviewers = reviewers
961 else: 968 else:
962 subject = issue.subject 969 subject = issue.subject
970 issue.reviewers = reviewers
963 tbd.append(issue) # To update the last modified time 971 tbd.append(issue) # To update the last modified time
964 message = form.cleaned_data['message'].replace('\r\n', '\n') 972 message = form.cleaned_data['message'].replace('\r\n', '\n')
965 send_mail = form.cleaned_data['send_mail'] 973 send_mail = form.cleaned_data['send_mail']
966 comments = [] 974 comments = []
967 975
968 # XXX Should request all drafts for this issue once, now we can. 976 # XXX Should request all drafts for this issue once, now we can.
969 for patchset in issue.patchset_set.order('created'): 977 for patchset in issue.patchset_set.order('created'):
970 ## ps_comments = list(models.Comment.gql( 978 ## ps_comments = list(models.Comment.gql(
971 ## 'WHERE ANCESTOR IS :1 AND author = :2 AND draft = TRUE', 979 ## 'WHERE ANCESTOR IS :1 AND author = :2 AND draft = TRUE',
972 ## patchset, request.user)) 980 ## patchset, request.user))
973 # XXX Somehow the index broke, do without it 981 # XXX Somehow the index broke, do without it
974 ps_comments = [c for c in 982 ps_comments = [c for c in
975 models.Comment.gql('WHERE ANCESTOR IS :1', patchset) 983 models.Comment.gql('WHERE ANCESTOR IS :1', patchset)
976 if c.draft and c.author == request.user] 984 if c.draft and c.author == request.user]
977 # XXX End 985 # XXX End
978 if ps_comments: 986 if ps_comments:
979 patches = dict((p.key(), p) for p in patchset.patch_set) 987 patches = dict((p.key(), p) for p in patchset.patch_set)
980 for p in patches.itervalues(): 988 for p in patches.itervalues():
981 p.patchset = patchset 989 p.patchset = patchset
982 for c in ps_comments: 990 for c in ps_comments:
983 c.draft = False 991 c.draft = False
984 # XXX Using internal knowledge about db package: the key for 992 # XXX Using internal knowledge about db package: the key for
985 # reference property foo is stored as _foo. 993 # reference property foo is stored as _foo.
986 pkey = getattr(c, '_patch', None) 994 pkey = getattr(c, '_patch', None)
987 if pkey in patches: 995 if pkey in patches:
988 patch = patches[pkey] 996 patch = patches[pkey]
989 c.patch = patch 997 c.patch = patch
990 tbd.append(ps_comments) 998 tbd.append(ps_comments)
991 ps_comments.sort(key=lambda c: (c.patch.filename, not c.left, 999 ps_comments.sort(key=lambda c: (c.patch.filename, not c.left,
992 c.lineno, c.date)) 1000 c.lineno, c.date))
993 comments += ps_comments 1001 comments += ps_comments
994 1002
995 if comments: 1003 if comments:
996 logging.warn('Publishing %d comments', len(comments)) 1004 logging.warn('Publishing %d comments', len(comments))
997 # Decide who should receive mail 1005 # Decide who should receive mail
998 my_email = db.Email(request.user.email()) 1006 my_email = db.Email(request.user.email())
999 addressees = [db.Email(issue.owner.email())] + issue.reviewers 1007 addressees = [db.Email(issue.owner.email())] + issue.reviewers
1000 if my_email in addressees: 1008 if my_email in addressees:
1001 everyone = addressees[:] 1009 everyone = addressees[:]
1002 if len(addressees) > 1: # Keep it if sending only to yourself 1010 if len(addressees) > 1: # Keep it if sending only to yourself
1003 addressees.remove(my_email) 1011 addressees.remove(my_email)
1004 else: 1012 else:
1005 everyone = addressees + [my_email] 1013 everyone = addressees + [my_email]
1006 details = _get_draft_details(request, comments) 1014 details = _get_draft_details(request, comments)
1007 text = ((message.strip() + '\n\n' + details.strip())).strip() 1015 text = ((message.strip() + '\n\n' + details.strip())).strip()
1008 msg = models.Message(issue=issue, 1016 msg = models.Message(issue=issue,
1009 subject=issue.subject, 1017 subject=issue.subject,
1010 sender=my_email, 1018 sender=my_email,
1011 recipients=everyone, 1019 recipients=everyone,
1012 text=db.Text(text), 1020 text=db.Text(text),
(...skipping 183 matching lines...) Show 10 above Show 10 below
1196 return respond(request, 'branch_edit.html', 1204 return respond(request, 'branch_edit.html',
1197 {'branch': branch, 'form': form}) 1205 {'branch': branch, 'form': form})
1198 branch.put() 1206 branch.put()
1199 return HttpResponseRedirect('/repos') 1207 return HttpResponseRedirect('/repos')
1200 1208
1201 1209
1202 @login_required 1210 @login_required
1203 def branch_delete(request, branch_id): 1211 def branch_delete(request, branch_id):
1204 """/branch_delete/<branch> - Delete a Branch record.""" 1212 """/branch_delete/<branch> - Delete a Branch record."""
1205 branch = models.Branch.get_by_id(int(branch_id)) 1213 branch = models.Branch.get_by_id(int(branch_id))
1206 if branch.owner != request.user: 1214 if branch.owner != request.user:
1207 return HttpResponseForbidden('You do not own this branch') 1215 return HttpResponseForbidden('You do not own this branch')
1208 repo = branch.repo 1216 repo = branch.repo