| LEFT | RIGHT |
|---|---|
| 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 | 30 |
| 31 # AppEngine imports | 31 # AppEngine imports |
| 32 from google.appengine.api import mail | 32 from google.appengine.api import mail |
| 33 from google.appengine.api import users | 33 from google.appengine.api import users |
| 34 from google.appengine.api import urlfetch | 34 from google.appengine.api import urlfetch |
| 35 from google.appengine.ext import db | 35 from google.appengine.ext import db |
| 36 from google.appengine.ext.db import djangoforms | 36 from google.appengine.ext.db import djangoforms |
| 37 | 37 |
| 38 # DeadlineExceededError can live in two different places | 38 # DeadlineExceededError can live in two different places |
| 39 # TODO(guido): simplify once this is fixed. | 39 # TODO(guido): simplify once this is fixed. |
| 40 try: | 40 try: |
| 41 # When deployed | 41 # When deployed |
| 42 from google.appengine.runtime import DeadlineExceededError | 42 from google.appengine.runtime import DeadlineExceededError |
| 43 except ImportError: | 43 except ImportError: |
| 44 # In the development server | 44 # In the development server |
| 45 from google.appengine.runtime.apiproxy_errors import DeadlineExceededError | 45 from google.appengine.runtime.apiproxy_errors import DeadlineExceededError |
| 46 | 46 |
| 47 # Django imports | 47 # Django imports |
| 48 # TODO(guido): Don't import classes/functions directly. | 48 # TODO(guido): Don't import classes/functions directly. |
| 49 from django import newforms as forms | 49 from django import newforms as forms |
| 50 from django.http import HttpResponse, HttpResponseRedirect | 50 from django.http import HttpResponse, HttpResponseRedirect |
| (...skipping 88 matching lines...) Show 10 above Show 10 below | |
| 139 url = forms.URLField(required=False, | 139 url = forms.URLField(required=False, |
| 140 max_length=2083, | 140 max_length=2083, |
| 141 widget=forms.TextInput(attrs={'size': 60})) | 141 widget=forms.TextInput(attrs={'size': 60})) |
| 142 | 142 |
| 143 | 143 |
| 144 class UploadForm(forms.Form): | 144 class UploadForm(forms.Form): |
| 145 | 145 |
| 146 subject = forms.CharField(max_length=100) | 146 subject = forms.CharField(max_length=100) |
| 147 description = forms.CharField(max_length=10000, required=False) | 147 description = forms.CharField(max_length=10000, required=False) |
| 148 base = forms.CharField(max_length=2000) | 148 base = forms.CharField(max_length=2000) |
| 149 data = forms.FileField() | 149 data = forms.FileField() |
| 150 issue = forms.IntegerField(required=False) | 150 issue = forms.IntegerField(required=False) |
| 151 | 151 |
| 152 def get_base(self): | 152 def get_base(self): |
| 153 return self.cleaned_data.get('base') | 153 return self.cleaned_data.get('base') |
| 154 | 154 |
| 155 | 155 |
| 156 class EditForm(IssueBaseForm): | 156 class EditForm(IssueBaseForm): |
| 157 pass | 157 pass |
| 158 | 158 |
| 159 | 159 |
| 160 class RepoForm(djangoforms.ModelForm): | 160 class RepoForm(djangoforms.ModelForm): |
| 161 | 161 |
| 162 class Meta: | 162 class Meta: |
| 163 model = models.Repository | 163 model = models.Repository |
| 164 exclude = ['owner'] | 164 exclude = ['owner'] |
| 165 | 165 |
| 166 | 166 |
| 167 class BranchForm(djangoforms.ModelForm): | 167 class BranchForm(djangoforms.ModelForm): |
| 168 | 168 |
| 169 class Meta: | 169 class Meta: |
| 170 model = models.Branch | 170 model = models.Branch |
| 171 exclude = ['owner'] | 171 exclude = ['owner'] |
| 172 | 172 |
| 173 | 173 |
| 174 class PublishForm(forms.Form): | 174 class PublishForm(forms.Form): |
| 175 | 175 |
| 176 subject = forms.CharField(max_length=100, | 176 subject = forms.CharField(max_length=100, |
| 177 widget=forms.TextInput(attrs={'size': 60})) | 177 widget=forms.TextInput(attrs={'size': 60})) |
| 178 reviewers = forms.CharField(required=False, | 178 reviewers = forms.CharField(required=False, |
| 179 max_length=1000, | 179 max_length=1000, |
| 180 widget=forms.TextInput(attrs={'size': 60})) | 180 widget=forms.TextInput(attrs={'size': 60})) |
| 181 send_mail = forms.BooleanField() | 181 send_mail = forms.BooleanField() |
| 182 message = forms.CharField(required=False, | 182 message = forms.CharField(required=False, |
| 183 max_length=10000, | 183 max_length=10000, |
| 184 widget=forms.Textarea(attrs={'cols': 60})) | 184 widget=forms.Textarea(attrs={'cols': 60})) |
| 185 | 185 |
| 186 | 186 |
| 187 class MiniPublishForm(forms.Form): | 187 class MiniPublishForm(forms.Form): |
| 188 | 188 |
| 189 send_mail = forms.BooleanField() | 189 send_mail = forms.BooleanField() |
|
GvR
2008/05/11 23:27:41
The order of the fields should be the same as in P
| |
| 190 reviewers = forms.CharField(required=False, | 190 reviewers = forms.CharField(required=False, |
| 191 max_length=1000, | 191 max_length=1000, |
| 192 widget=forms.TextInput(attrs={'size': 60})) | 192 widget=forms.TextInput(attrs={'size': 60})) |
| 193 message = forms.CharField(required=False, | 193 message = forms.CharField(required=False, |
| 194 max_length=10000, | 194 max_length=10000, |
| 195 widget=forms.Textarea(attrs={'cols': 60})) | 195 widget=forms.Textarea(attrs={'cols': 60})) |
| 196 | 196 |
| 197 | 197 |
| 198 class SettingsForm(forms.Form): | 198 class SettingsForm(forms.Form): |
| 199 | 199 |
| 200 nickname = forms.CharField(max_length=30) | 200 nickname = forms.CharField(max_length=30) |
| 201 | 201 |
| 202 | 202 |
| 203 ### Helper functions ### | 203 ### Helper functions ### |
| 204 | 204 |
| 205 | 205 |
| 206 # Counter displayed (by respond()) below) on every page showing how | 206 # Counter displayed (by respond()) below) on every page showing how |
| 207 # many requests the current incarnation has handled, not counting | 207 # many requests the current incarnation has handled, not counting |
| 208 # redirects. Rendered by templates/base.html. | 208 # redirects. Rendered by templates/base.html. |
| 209 counter = 0 | 209 counter = 0 |
| 210 | 210 |
| 211 def respond(request, template, params=None): | 211 def respond(request, template, params=None): |
| 212 """Helper to render a response, passing standard stuff to the response. | 212 """Helper to render a response, passing standard stuff to the response. |
| 213 | 213 |
| 214 Args: | 214 Args: |
| 215 request: The request object. | 215 request: The request object. |
| 216 template: The template name; '.html' is appended automatically. | 216 template: The template name; '.html' is appended automatically. |
| 217 params: A dict giving the template parameters; modified in-place. | 217 params: A dict giving the template parameters; modified in-place. |
| 218 | 218 |
| 219 Returns: | 219 Returns: |
| 220 Whatever render_to_response(template, params) returns. | 220 Whatever render_to_response(template, params) returns. |
| 221 | 221 |
| 222 Raises: | 222 Raises: |
| 223 Whatever render_to_response(template, params) raises. | 223 Whatever render_to_response(template, params) raises. |
| 224 """ | 224 """ |
| 225 global counter | 225 global counter |
| 226 counter += 1 | 226 counter += 1 |
| 227 if params is None: | 227 if params is None: |
| 228 params = {} | 228 params = {} |
| 229 must_choose_nickname = False | 229 must_choose_nickname = False |
| 230 if request.user is not None: | 230 if request.user is not None: |
| 231 account = models.Account.get_account_for_user(request.user) | 231 account = models.Account.get_account_for_user(request.user) |
| 232 delta = account.created - account.modified | 232 delta = account.created - account.modified |
| 233 if delta.days < 0: | 233 if delta.days < 0: |
| 234 delta = -delta | 234 delta = -delta |
| 235 must_choose_nickname = delta.days == 0 and delta.seconds < 2 | 235 must_choose_nickname = delta.days == 0 and delta.seconds < 2 |
| 236 params['request'] = request | 236 params['request'] = request |
| 237 params['counter'] = counter | 237 params['counter'] = counter |
| 238 params['user'] = request.user | 238 params['user'] = request.user |
| 239 params['is_admin'] = request.user_is_admin | 239 params['is_admin'] = request.user_is_admin |
| (...skipping 288 matching lines...) Show 10 above Show 10 below | |
| 528 return None | 528 return None |
| 529 if fetch_result.status_code != 200: | 529 if fetch_result.status_code != 200: |
| 530 form.errors['url'] = ['HTTP status code %s' % fetch_result.status_code] | 530 form.errors['url'] = ['HTTP status code %s' % fetch_result.status_code] |
| 531 return None | 531 return None |
| 532 data = db.Blob(fetch_result.content) | 532 data = db.Blob(fetch_result.content) |
| 533 | 533 |
| 534 return data, url | 534 return data, url |
| 535 | 535 |
| 536 | 536 |
| 537 @issue_owner_required | 537 @issue_owner_required |
| 538 def add(request): | 538 def add(request): |
| 539 """/<issue>/add - Add a new PatchSet to an existing Issue.""" | 539 """/<issue>/add - Add a new PatchSet to an existing Issue.""" |
| 540 issue = request.issue | 540 issue = request.issue |
| 541 form = AddForm(request.POST, request.FILES) | 541 form = AddForm(request.POST, request.FILES) |
| 542 if not add_patchset_from_form(request, issue, form): | 542 if not add_patchset_from_form(request, issue, form): |
| 543 return show(request, issue.key().id(), form) | 543 return show(request, issue.key().id(), form) |
| 544 return HttpResponseRedirect('/%s' % issue.key().id()) | 544 return HttpResponseRedirect('/%s' % issue.key().id()) |
| 545 | 545 |
| 546 | 546 |
| 547 def add_patchset_from_form(request, issue, form, message_key='message'): | 547 def add_patchset_from_form(request, issue, form, message_key='message'): |
| 548 """Helper for add() and upload().""" | 548 """Helper for add() and upload().""" |
| 549 # TODO(guido): use a transaction like in _make_new(); may be share more code? | 549 # TODO(guido): use a transaction like in _make_new(); may be share more code? |
| 550 if form.is_valid(): | 550 if form.is_valid(): |
| 551 data_url = _get_data_url(form) | 551 data_url = _get_data_url(form) |
| 552 if not form.is_valid(): | 552 if not form.is_valid(): |
| 553 return False | 553 return False |
| 554 data, url = data_url | 554 data, url = data_url |
| 555 message = form.cleaned_data[message_key] | 555 message = form.cleaned_data[message_key] |
| 556 patchset = models.PatchSet(issue=issue, message=message, data=data, url=url, | 556 patchset = models.PatchSet(issue=issue, message=message, data=data, url=url, |
| 557 base=issue.base, owner=request.user, parent=issue) | 557 base=issue.base, owner=request.user, parent=issue) |
| 558 patchset.put() | 558 patchset.put() |
| 559 | 559 |
| 560 patches = engine.ParsePatchSet(patchset) | 560 patches = engine.ParsePatchSet(patchset) |
| 561 if not patches: | 561 if not patches: |
| 562 patchset.delete() | 562 patchset.delete() |
| 563 errkey = url and 'url' or 'data' | 563 errkey = url and 'url' or 'data' |
| 564 form.errors[errkey] = ['Patch set contains no recognizable patches'] | 564 form.errors[errkey] = ['Patch set contains no recognizable patches'] |
| 565 return False | 565 return False |
| 566 db.put(patches) | 566 db.put(patches) |
| 567 issue.put() # To update last modified time | 567 issue.put() # To update last modified time |
| 568 return True | 568 return True |
| 569 | 569 |
| 570 | 570 |
| 571 def _get_reviewers(form): | 571 def _get_reviewers(form): |
| 572 """Helper to return the list of reviewers, or None for error.""" | 572 """Helper to return the list of reviewers, or None for error.""" |
| 573 reviewers = [] | 573 reviewers = [] |
| 574 raw_reviewers = form.cleaned_data.get('reviewers') | 574 raw_reviewers = form.cleaned_data.get('reviewers') |
| 575 if raw_reviewers: | 575 if raw_reviewers: |
| 576 for reviewer in raw_reviewers.split(','): | 576 for reviewer in raw_reviewers.split(','): |
| 577 reviewer = reviewer.strip() | 577 reviewer = reviewer.strip() |
| 578 if reviewer: | 578 if reviewer and reviewer not in reviewers: |
| 579 try: | 579 try: |
| 580 reviewer = db.Email(reviewer) | 580 reviewer = db.Email(reviewer) |
| 581 if reviewer.count('@') != 1: | 581 if reviewer.count('@') != 1: |
| 582 raise db.BadValueError('Invalid email address: %s' % reviewer) | 582 raise db.BadValueError('Invalid email address: %s' % reviewer) |
| 583 head, tail = reviewer.split('@') | 583 head, tail = reviewer.split('@') |
| 584 if '.' not in tail: | 584 if '.' not in tail: |
| 585 raise db.BadValueError('Invalid email address: %s' % reviewer) | 585 raise db.BadValueError('Invalid email address: %s' % reviewer) |
| 586 except db.BadValueError, err: | 586 except db.BadValueError, err: |
| 587 form.errors['reviewers'] = [unicode(err)] | 587 form.errors['reviewers'] = [unicode(err)] |
| 588 return None | 588 return None |
| 589 reviewers.append(reviewer) | 589 reviewers.append(reviewer) |
| 590 return reviewers | 590 return reviewers |
| 591 | 591 |
| 592 | 592 |
| 593 | 593 |
| 594 @issue_required | 594 @issue_required |
| 595 def show(request, form=AddForm()): | 595 def show(request, form=AddForm()): |
| 596 """/<issue> - Show an issue.""" | 596 """/<issue> - Show an issue.""" |
| 597 issue = request.issue | 597 issue = request.issue |
| 598 patchsets = list(issue.patchset_set.order('created')) | 598 patchsets = list(issue.patchset_set.order('created')) |
| 599 issue.draft_count = 0 | 599 issue.draft_count = 0 |
| 600 issue.comment_count = 0 | 600 issue.comment_count = 0 |
| 601 for patchset in patchsets: | 601 for patchset in patchsets: |
| 602 patchset.patches = list(patchset.patch_set.order('filename')) | 602 patchset.patches = list(patchset.patch_set.order('filename')) |
| 603 patchset.n_comments = 0 | 603 patchset.n_comments = 0 |
| 604 for patch in patchset.patches: | 604 for patch in patchset.patches: |
| 605 patchset.n_comments += patch.num_comments | 605 patchset.n_comments += patch.num_comments |
| 606 issue.comment_count += patchset.n_comments | 606 issue.comment_count += patchset.n_comments |
| 607 patchset.n_drafts = 0 | 607 patchset.n_drafts = 0 |
| 608 if request.user: | 608 if request.user: |
| 609 for patch in patchset.patches: | 609 for patch in patchset.patches: |
| 610 patchset.n_drafts += patch.num_drafts | 610 patchset.n_drafts += patch.num_drafts |
| 611 issue.draft_count += patchset.n_drafts | 611 issue.draft_count += patchset.n_drafts |
| 612 last_patchset = first_patch = None | 612 last_patchset = first_patch = None |
| 613 if patchsets: | 613 if patchsets: |
| 614 last_patchset = patchsets[-1] | 614 last_patchset = patchsets[-1] |
| 615 if last_patchset.patches: | 615 if last_patchset.patches: |
| 616 first_patch = last_patchset.patches[0] | 616 first_patch = last_patchset.patches[0] |
| 617 messages = list(issue.message_set.order('date')) | 617 messages = list(issue.message_set.order('date')) |
| 618 return respond(request, 'issue.html', | 618 return respond(request, 'issue.html', |
| 619 {'issue': issue, 'patchsets': patchsets, | 619 {'issue': issue, 'patchsets': patchsets, |
| 620 'messages': messages, 'form': form, | 620 'messages': messages, 'form': form, |
| 621 'last_patchset': last_patchset, | 621 'last_patchset': last_patchset, |
| 622 'first_patch': first_patch}) | 622 'first_patch': first_patch}) |
| 623 | 623 |
| 624 | 624 |
| 625 @issue_owner_required | 625 @issue_owner_required |
| 626 def edit(request): | 626 def edit(request): |
| 627 """/<issue>/edit - Edit an issue.""" | 627 """/<issue>/edit - Edit an issue.""" |
| 628 issue = request.issue | 628 issue = request.issue |
| (...skipping 269 matching lines...) Show 10 above Show 10 below | |
| 898 'ORDER BY date', | 898 'ORDER BY date', |
| 899 patch=patch, lineno=lineno, left=left) | 899 patch=patch, lineno=lineno, left=left) |
| 900 comments = list(c for c in query if not c.draft or c.author == request.user) | 900 comments = list(c for c in query if not c.draft or c.author == request.user) |
| 901 if comment is not None and comment.author is None: | 901 if comment is not None and comment.author is None: |
| 902 # Show anonymous draft even though we don't save it | 902 # Show anonymous draft even though we don't save it |
| 903 comments.append(comment) | 903 comments.append(comment) |
| 904 if not comments: | 904 if not comments: |
| 905 return HttpResponse(' ') | 905 return HttpResponse(' ') |
| 906 for c in comments: | 906 for c in comments: |
| 907 c.complete(patch) | 907 c.complete(patch) |
| 908 return render_to_response('inline_comment.html', | 908 return render_to_response('inline_comment.html', |
| 909 {'user': request.user, | 909 {'user': request.user, |
| 910 'patch': patch, | 910 'patch': patch, |
| 911 'patchset': patchset, | 911 'patchset': patchset, |
| 912 'issue': issue, | 912 'issue': issue, |
| 913 'comments': comments, | 913 'comments': comments, |
| 914 'lineno': lineno, | 914 'lineno': lineno, |
| 915 'snapshot': snapshot, | 915 'snapshot': snapshot, |
| 916 'side': side}) | 916 'side': side}) |
| 917 | 917 |
| 918 | 918 |
| 919 PUBLISH_MAIL_TEMPLATE = """Dear %s, | 919 PUBLISH_MAIL_TEMPLATE = """Dear %s, |
| 920 | 920 |
| 921 New code review comments by %s have been published. | 921 New code review comments by %s have been published. |
| 922 Please go to %s to read them. | 922 Please go to %s to read them. |
| 923 | 923 |
| 924 Message: | 924 Message: |
| 925 %s | 925 %s |
| 926 | 926 |
| 927 Details: | 927 Details: |
| 928 %s | 928 %s |
| 929 | 929 |
| 930 Issue Description: | 930 Issue Description: |
| 931 %s | 931 %s |
| 932 | 932 |
| 933 Sincerely, | 933 Sincerely, |
| 934 | 934 |
| 935 Your friendly code review daemon (%s). | 935 Your friendly code review daemon (%s). |
| 936 """ | 936 """ |
| 937 | 937 |
| 938 @issue_required | 938 @issue_required |
| 939 @login_required | 939 @login_required |
| 940 def publish(request): | 940 def publish(request): |
| 941 """ /<issue>/publish - Publish draft comments and send mail.""" | 941 """ /<issue>/publish - Publish draft comments and send mail.""" |
| 942 issue = request.issue | 942 issue = request.issue |
| 943 if request.user == issue.owner: | 943 if request.user == issue.owner: |
| 944 form_class = PublishForm | 944 form_class = PublishForm |
| 945 else: | 945 else: |
| 946 form_class = MiniPublishForm | 946 form_class = MiniPublishForm |
| 947 if request.method != 'POST': | 947 if request.method != 'POST': |
| 948 reviewers = issue.reviewers[:] | |
| 949 if request.user != issue.owner and (request.user.email() | |
| 950 not in issue.reviewers): | |
| 951 reviewers.append(request.user.email()) | |
| 948 form = form_class(initial={'subject': issue.subject, | 952 form = form_class(initial={'subject': issue.subject, |
| 949 'reviewers': ', '.join(issue.reviewers), | 953 'reviewers': ', '.join(reviewers), |
|
GvR
2008/05/10 15:44:24
I would suggest one more addition: add the current
jiayao
2008/05/11 11:33:27
Good suggestion, I've added that. Just another tho
| |
| 950 'send_mail': True, | 954 'send_mail': True, |
| 951 }) | 955 }) |
| 952 return respond(request, 'publish.html', {'form': form, 'issue': issue}) | 956 return respond(request, 'publish.html', {'form': form, 'issue': issue}) |
| 953 | 957 |
| 954 form = form_class(request.POST) | 958 form = form_class(request.POST) |
| 955 if form.is_valid(): | 959 if form.is_valid(): |
| 956 reviewers = _get_reviewers(form) | 960 reviewers = _get_reviewers(form) |
| 957 if not form.is_valid(): | 961 if not form.is_valid(): |
| 958 return respond(request, 'publish.html', {'form': form, 'issue': issue}) | 962 return respond(request, 'publish.html', {'form': form, 'issue': issue}) |
| 959 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 |
| 960 if request.user == issue.owner: | 964 if request.user == issue.owner: |
| 961 subject = form.cleaned_data['subject'] | 965 subject = form.cleaned_data['subject'] |
| 962 issue.subject = subject | 966 issue.subject = subject |
| 963 issue.reviewers = reviewers | 967 issue.reviewers = reviewers |
| 964 else: | 968 else: |
| 969 subject = issue.subject | |
| 965 issue.reviewers = reviewers | 970 issue.reviewers = reviewers |
|
GvR
2008/05/10 15:44:24
I'd move this one line downfor symmetry with the '
jiayao
2008/05/11 11:33:27
On 2008/05/10 15:44:24, GvR wrote:
> I'd move this
| |
| 966 subject = issue.subject | |
| 967 tbd.append(issue) # To update the last modified time | 971 tbd.append(issue) # To update the last modified time |
| 968 message = form.cleaned_data['message'].replace('\r\n', '\n') | 972 message = form.cleaned_data['message'].replace('\r\n', '\n') |
| 969 send_mail = form.cleaned_data['send_mail'] | 973 send_mail = form.cleaned_data['send_mail'] |
| 970 comments = [] | 974 comments = [] |
| 971 | 975 |
| 972 # 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. |
| 973 for patchset in issue.patchset_set.order('created'): | 977 for patchset in issue.patchset_set.order('created'): |
| 974 ## ps_comments = list(models.Comment.gql( | 978 ## ps_comments = list(models.Comment.gql( |
| 975 ## 'WHERE ANCESTOR IS :1 AND author = :2 AND draft = TRUE', | 979 ## 'WHERE ANCESTOR IS :1 AND author = :2 AND draft = TRUE', |
| 976 ## patchset, request.user)) | 980 ## patchset, request.user)) |
| 977 # XXX Somehow the index broke, do without it | 981 # XXX Somehow the index broke, do without it |
| 978 ps_comments = [c for c in | 982 ps_comments = [c for c in |
| 979 models.Comment.gql('WHERE ANCESTOR IS :1', patchset) | 983 models.Comment.gql('WHERE ANCESTOR IS :1', patchset) |
| 980 if c.draft and c.author == request.user] | 984 if c.draft and c.author == request.user] |
| 981 # XXX End | 985 # XXX End |
| 982 if ps_comments: | 986 if ps_comments: |
| 983 patches = dict((p.key(), p) for p in patchset.patch_set) | 987 patches = dict((p.key(), p) for p in patchset.patch_set) |
| 984 for p in patches.itervalues(): | 988 for p in patches.itervalues(): |
| 985 p.patchset = patchset | 989 p.patchset = patchset |
| 986 for c in ps_comments: | 990 for c in ps_comments: |
| 987 c.draft = False | 991 c.draft = False |
| 988 # XXX Using internal knowledge about db package: the key for | 992 # XXX Using internal knowledge about db package: the key for |
| 989 # reference property foo is stored as _foo. | 993 # reference property foo is stored as _foo. |
| 990 pkey = getattr(c, '_patch', None) | 994 pkey = getattr(c, '_patch', None) |
| 991 if pkey in patches: | 995 if pkey in patches: |
| 992 patch = patches[pkey] | 996 patch = patches[pkey] |
| 993 c.patch = patch | 997 c.patch = patch |
| 994 tbd.append(ps_comments) | 998 tbd.append(ps_comments) |
| 995 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, |
| 996 c.lineno, c.date)) | 1000 c.lineno, c.date)) |
| 997 comments += ps_comments | 1001 comments += ps_comments |
| 998 | 1002 |
| 999 if comments: | 1003 if comments: |
| 1000 logging.warn('Publishing %d comments', len(comments)) | 1004 logging.warn('Publishing %d comments', len(comments)) |
| 1001 # Decide who should receive mail | 1005 # Decide who should receive mail |
| 1002 my_email = db.Email(request.user.email()) | 1006 my_email = db.Email(request.user.email()) |
| 1003 addressees = [db.Email(issue.owner.email())] + issue.reviewers | 1007 addressees = [db.Email(issue.owner.email())] + issue.reviewers |
| 1004 if my_email in addressees: | 1008 if my_email in addressees: |
| 1005 everyone = addressees[:] | 1009 everyone = addressees[:] |
| 1006 if len(addressees) > 1: # Keep it if sending only to yourself | 1010 if len(addressees) > 1: # Keep it if sending only to yourself |
| 1007 addressees.remove(my_email) | 1011 addressees.remove(my_email) |
| 1008 else: | 1012 else: |
| 1009 everyone = addressees + [my_email] | 1013 everyone = addressees + [my_email] |
|
GvR
2008/05/11 23:27:41
What would you think of *not* adding my_email in t
jiayao
2008/05/12 09:36:35
Make sense. It would be confusing if this person r
| |
| 1010 details = _get_draft_details(request, comments) | 1014 details = _get_draft_details(request, comments) |
| 1011 text = ((message.strip() + '\n\n' + details.strip())).strip() | 1015 text = ((message.strip() + '\n\n' + details.strip())).strip() |
| 1012 msg = models.Message(issue=issue, | 1016 msg = models.Message(issue=issue, |
| 1013 subject=issue.subject, | 1017 subject=issue.subject, |
| 1014 sender=my_email, | 1018 sender=my_email, |
| 1015 recipients=everyone, | 1019 recipients=everyone, |
| 1016 text=db.Text(text), | 1020 text=db.Text(text), |
| 1017 parent=issue) | 1021 parent=issue) |
| 1018 tbd.append(msg) | 1022 tbd.append(msg) |
| 1019 | 1023 |
| 1020 if send_mail: | 1024 if send_mail: |
| 1021 url = request.build_absolute_uri('/%s' % issue.key().id()) | 1025 url = request.build_absolute_uri('/%s' % issue.key().id()) |
| 1022 addressees_nicknames = ", ".join(library.nickname(addressee, True) | 1026 addressees_nicknames = ", ".join(library.nickname(addressee, True) |
| 1023 for addressee in addressees) | 1027 for addressee in addressees) |
| 1024 my_nickname = library.nickname(request.user, True) | 1028 my_nickname = library.nickname(request.user, True) |
| 1025 addressees = ', '.join(addressees) | 1029 addressees = ', '.join(addressees) |
| 1026 description = (issue.description or '').replace('\r\n', '\n') | 1030 description = (issue.description or '').replace('\r\n', '\n') |
| 1027 home = request.build_absolute_uri('/') | 1031 home = request.build_absolute_uri('/') |
| 1028 body = PUBLISH_MAIL_TEMPLATE % (addressees_nicknames, my_nickname, | 1032 body = PUBLISH_MAIL_TEMPLATE % (addressees_nicknames, my_nickname, |
| 1029 url, message, | 1033 url, message, |
| 1030 details, description, home) | 1034 details, description, home) |
| 1031 logging.warn('Mail: to=%s; cc=%s', addressees, my_email) | 1035 logging.warn('Mail: to=%s; cc=%s', addressees, my_email) |
| 1032 mail.send_mail(sender=SENDER, | 1036 mail.send_mail(sender=SENDER, |
| 1033 to=_encode_safely(addressees), | 1037 to=_encode_safely(addressees), |
| 1034 subject=_encode_safely('Re: ' + subject), | 1038 subject=_encode_safely('Re: ' + subject), |
| 1035 body=_encode_safely(body), | 1039 body=_encode_safely(body), |
| 1036 cc=_encode_safely(my_email), | 1040 cc=_encode_safely(my_email), |
| 1037 reply_to=_encode_safely(', '.join(everyone))) | 1041 reply_to=_encode_safely(', '.join(everyone))) |
| 1038 | 1042 |
| 1039 for obj in tbd: | 1043 for obj in tbd: |
| 1040 db.put(obj) | 1044 db.put(obj) |
| 1041 return HttpResponseRedirect('/%s' % issue.key().id()) | 1045 return HttpResponseRedirect('/%s' % issue.key().id()) |
| 1042 | 1046 |
| 1043 | 1047 |
| 1044 def _encode_safely(s): | 1048 def _encode_safely(s): |
| 1045 """Helper to turn a unicode string into 8-bit bytes.""" | 1049 """Helper to turn a unicode string into 8-bit bytes.""" |
| 1046 if isinstance(s, unicode): | 1050 if isinstance(s, unicode): |
| 1047 s = s.encode('utf-8') | 1051 s = s.encode('utf-8') |
| 1048 return s | 1052 return s |
| 1049 | 1053 |
| 1050 | 1054 |
| 1051 def _get_draft_details(request, comments): | 1055 def _get_draft_details(request, comments): |
| 1052 """Helper to display comments with context in the email message.""" | 1056 """Helper to display comments with context in the email message.""" |
| 1053 last_key = None | 1057 last_key = None |
| 1054 output = [] | 1058 output = [] |
| 1055 linecache = {} # Maps (c.patch.filename, c.left) to list of lines | 1059 linecache = {} # Maps (c.patch.filename, c.left) to list of lines |
| 1056 modified_patches = [] | 1060 modified_patches = [] |
| 1057 for c in comments: | 1061 for c in comments: |
| 1058 if (c.patch.filename, c.left) != last_key: | 1062 if (c.patch.filename, c.left) != last_key: |
| 1059 url = request.build_absolute_uri('/%d/diff/%d/%d' % | 1063 url = request.build_absolute_uri('/%d/diff/%d/%d' % |
| (...skipping 140 matching lines...) Show 10 above Show 10 below | |
| 1200 return respond(request, 'branch_edit.html', | 1204 return respond(request, 'branch_edit.html', |
| 1201 {'branch': branch, 'form': form}) | 1205 {'branch': branch, 'form': form}) |
| 1202 branch.put() | 1206 branch.put() |
| 1203 return HttpResponseRedirect('/repos') | 1207 return HttpResponseRedirect('/repos') |
| 1204 | 1208 |
| 1205 | |