| OLD | NEW |
| 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 89 matching lines...) Show 10 above Show 10 below |
| 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() |
| 190 reviewers = forms.CharField(required=False, |
| 191 max_length=1000, |
| 192 widget=forms.TextInput(attrs={'size': 60})) |
| 190 message = forms.CharField(required=False, | 193 message = forms.CharField(required=False, |
| 191 max_length=10000, | 194 max_length=10000, |
| 192 widget=forms.Textarea(attrs={'cols': 60})) | 195 widget=forms.Textarea(attrs={'cols': 60})) |
| 193 | 196 |
| 194 | 197 |
| 195 class SettingsForm(forms.Form): | 198 class SettingsForm(forms.Form): |
| 196 | 199 |
| 197 nickname = forms.CharField(max_length=30) | 200 nickname = forms.CharField(max_length=30) |
| 198 | 201 |
| 199 | 202 |
| 200 ### Helper functions ### | 203 ### Helper functions ### |
| 201 | 204 |
| 202 | 205 |
| 203 # Counter displayed (by respond()) below) on every page showing how | 206 # Counter displayed (by respond()) below) on every page showing how |
| 204 # many requests the current incarnation has handled, not counting | 207 # many requests the current incarnation has handled, not counting |
| 205 # redirects. Rendered by templates/base.html. | 208 # redirects. Rendered by templates/base.html. |
| 206 counter = 0 | 209 counter = 0 |
| 207 | 210 |
| 208 def respond(request, template, params=None): | 211 def respond(request, template, params=None): |
| 209 """Helper to render a response, passing standard stuff to the response. | 212 """Helper to render a response, passing standard stuff to the response. |
| 210 | 213 |
| 211 Args: | 214 Args: |
| 212 request: The request object. | 215 request: The request object. |
| 213 template: The template name; '.html' is appended automatically. | 216 template: The template name; '.html' is appended automatically. |
| 214 params: A dict giving the template parameters; modified in-place. | 217 params: A dict giving the template parameters; modified in-place. |
| 215 | 218 |
| 216 Returns: | 219 Returns: |
| 217 Whatever render_to_response(template, params) returns. | 220 Whatever render_to_response(template, params) returns. |
| 218 | 221 |
| 219 Raises: | 222 Raises: |
| 220 Whatever render_to_response(template, params) raises. | 223 Whatever render_to_response(template, params) raises. |
| 221 """ | 224 """ |
| 222 global counter | 225 global counter |
| 223 counter += 1 | 226 counter += 1 |
| 224 if params is None: | 227 if params is None: |
| 225 params = {} | 228 params = {} |
| 226 must_choose_nickname = False | 229 must_choose_nickname = False |
| 227 if request.user is not None: | 230 if request.user is not None: |
| 228 account = models.Account.get_account_for_user(request.user) | 231 account = models.Account.get_account_for_user(request.user) |
| 229 delta = account.created - account.modified | 232 delta = account.created - account.modified |
| 230 if delta.days < 0: | 233 if delta.days < 0: |
| 231 delta = -delta | 234 delta = -delta |
| 232 must_choose_nickname = delta.days == 0 and delta.seconds < 2 | 235 must_choose_nickname = delta.days == 0 and delta.seconds < 2 |
| 233 params['request'] = request | 236 params['request'] = request |
| 234 params['counter'] = counter | 237 params['counter'] = counter |
| 235 params['user'] = request.user | 238 params['user'] = request.user |
| 236 params['is_admin'] = request.user_is_admin | 239 params['is_admin'] = request.user_is_admin |
| 237 params['is_dev'] = IS_DEV | 240 params['is_dev'] = IS_DEV |
| 238 params['sign_in'] = users.create_login_url(request.path) | 241 params['sign_in'] = users.create_login_url(request.path) |
| 239 params['sign_out'] = users.create_logout_url(request.path) | 242 params['sign_out'] = users.create_logout_url(request.path) |
| (...skipping 285 matching lines...) Show 10 above Show 10 below |
| 525 return None | 528 return None |
| 526 if fetch_result.status_code != 200: | 529 if fetch_result.status_code != 200: |
| 527 form.errors['url'] = ['HTTP status code %s' % fetch_result.status_code] | 530 form.errors['url'] = ['HTTP status code %s' % fetch_result.status_code] |
| 528 return None | 531 return None |
| 529 data = db.Blob(fetch_result.content) | 532 data = db.Blob(fetch_result.content) |
| 530 | 533 |
| 531 return data, url | 534 return data, url |
| 532 | 535 |
| 533 | 536 |
| 534 @issue_owner_required | 537 @issue_owner_required |
| 535 def add(request): | 538 def add(request): |
| 536 """/<issue>/add - Add a new PatchSet to an existing Issue.""" | 539 """/<issue>/add - Add a new PatchSet to an existing Issue.""" |
| 537 issue = request.issue | 540 issue = request.issue |
| 538 form = AddForm(request.POST, request.FILES) | 541 form = AddForm(request.POST, request.FILES) |
| 539 if not add_patchset_from_form(request, issue, form): | 542 if not add_patchset_from_form(request, issue, form): |
| 540 return show(request, issue.key().id(), form) | 543 return show(request, issue.key().id(), form) |
| 541 return HttpResponseRedirect('/%s' % issue.key().id()) | 544 return HttpResponseRedirect('/%s' % issue.key().id()) |
| 542 | 545 |
| 543 | 546 |
| 544 def add_patchset_from_form(request, issue, form, message_key='message'): | 547 def add_patchset_from_form(request, issue, form, message_key='message'): |
| 545 """Helper for add() and upload().""" | 548 """Helper for add() and upload().""" |
| 546 # 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? |
| 547 if form.is_valid(): | 550 if form.is_valid(): |
| 548 data_url = _get_data_url(form) | 551 data_url = _get_data_url(form) |
| 549 if not form.is_valid(): | 552 if not form.is_valid(): |
| 550 return False | 553 return False |
| 551 data, url = data_url | 554 data, url = data_url |
| 552 message = form.cleaned_data[message_key] | 555 message = form.cleaned_data[message_key] |
| 553 patchset = models.PatchSet(issue=issue, message=message, data=data, url=url, | 556 patchset = models.PatchSet(issue=issue, message=message, data=data, url=url, |
| 554 base=issue.base, owner=request.user, parent=issue) | 557 base=issue.base, owner=request.user, parent=issue) |
| 555 patchset.put() | 558 patchset.put() |
| 556 | 559 |
| 557 patches = engine.ParsePatchSet(patchset) | 560 patches = engine.ParsePatchSet(patchset) |
| 558 if not patches: | 561 if not patches: |
| 559 patchset.delete() | 562 patchset.delete() |
| 560 errkey = url and 'url' or 'data' | 563 errkey = url and 'url' or 'data' |
| 561 form.errors[errkey] = ['Patch set contains no recognizable patches'] | 564 form.errors[errkey] = ['Patch set contains no recognizable patches'] |
| 562 return False | 565 return False |
| 563 db.put(patches) | 566 db.put(patches) |
| 564 issue.put() # To update last modified time | 567 issue.put() # To update last modified time |
| 565 return True | 568 return True |
| 566 | 569 |
| 567 | 570 |
| 568 def _get_reviewers(form): | 571 def _get_reviewers(form): |
| 569 """Helper to return the list of reviewers, or None for error.""" | 572 """Helper to return the list of reviewers, or None for error.""" |
| 570 reviewers = [] | 573 reviewers = [] |
| 571 raw_reviewers = form.cleaned_data.get('reviewers') | 574 raw_reviewers = form.cleaned_data.get('reviewers') |
| 572 if raw_reviewers: | 575 if raw_reviewers: |
| 573 for reviewer in raw_reviewers.split(','): | 576 for reviewer in raw_reviewers.split(','): |
| 574 reviewer = reviewer.strip() | 577 reviewer = reviewer.strip() |
| 575 if reviewer: | 578 if reviewer and reviewer not in reviewers: |
| 576 try: | 579 try: |
| 577 reviewer = db.Email(reviewer) | 580 reviewer = db.Email(reviewer) |
| 578 if reviewer.count('@') != 1: | 581 if reviewer.count('@') != 1: |
| 579 raise db.BadValueError('Invalid email address: %s' % reviewer) | 582 raise db.BadValueError('Invalid email address: %s' % reviewer) |
| 580 head, tail = reviewer.split('@') | 583 head, tail = reviewer.split('@') |
| 581 if '.' not in tail: | 584 if '.' not in tail: |
| 582 raise db.BadValueError('Invalid email address: %s' % reviewer) | 585 raise db.BadValueError('Invalid email address: %s' % reviewer) |
| 583 except db.BadValueError, err: | 586 except db.BadValueError, err: |
| 584 form.errors['reviewers'] = [unicode(err)] | 587 form.errors['reviewers'] = [unicode(err)] |
| 585 return None | 588 return None |
| 586 reviewers.append(reviewer) | 589 reviewers.append(reviewer) |
| 587 return reviewers | 590 return reviewers |
| 588 | 591 |
| 589 | 592 |
| 590 | 593 |
| 591 @issue_required | 594 @issue_required |
| 592 def show(request, form=AddForm()): | 595 def show(request, form=AddForm()): |
| 593 """/<issue> - Show an issue.""" | 596 """/<issue> - Show an issue.""" |
| 594 issue = request.issue | 597 issue = request.issue |
| 595 patchsets = list(issue.patchset_set.order('created')) | 598 patchsets = list(issue.patchset_set.order('created')) |
| 596 issue.draft_count = 0 | 599 issue.draft_count = 0 |
| 597 issue.comment_count = 0 | 600 issue.comment_count = 0 |
| 598 for patchset in patchsets: | 601 for patchset in patchsets: |
| 599 patchset.patches = list(patchset.patch_set.order('filename')) | 602 patchset.patches = list(patchset.patch_set.order('filename')) |
| 600 patchset.n_comments = 0 | 603 patchset.n_comments = 0 |
| 601 for patch in patchset.patches: | 604 for patch in patchset.patches: |
| 602 patchset.n_comments += patch.num_comments | 605 patchset.n_comments += patch.num_comments |
| 603 issue.comment_count += patchset.n_comments | 606 issue.comment_count += patchset.n_comments |
| 604 patchset.n_drafts = 0 | 607 patchset.n_drafts = 0 |
| 605 if request.user: | 608 if request.user: |
| 606 for patch in patchset.patches: | 609 for patch in patchset.patches: |
| 607 patchset.n_drafts += patch.num_drafts | 610 patchset.n_drafts += patch.num_drafts |
| 608 issue.draft_count += patchset.n_drafts | 611 issue.draft_count += patchset.n_drafts |
| 609 last_patchset = first_patch = None | 612 last_patchset = first_patch = None |
| 610 if patchsets: | 613 if patchsets: |
| 611 last_patchset = patchsets[-1] | 614 last_patchset = patchsets[-1] |
| 612 if last_patchset.patches: | 615 if last_patchset.patches: |
| 613 first_patch = last_patchset.patches[0] | 616 first_patch = last_patchset.patches[0] |
| 614 messages = list(issue.message_set.order('date')) | 617 messages = list(issue.message_set.order('date')) |
| 615 return respond(request, 'issue.html', | 618 return respond(request, 'issue.html', |
| 616 {'issue': issue, 'patchsets': patchsets, | 619 {'issue': issue, 'patchsets': patchsets, |
| 617 'messages': messages, 'form': form, | 620 'messages': messages, 'form': form, |
| 618 'last_patchset': last_patchset, | 621 'last_patchset': last_patchset, |
| 619 'first_patch': first_patch}) | 622 'first_patch': first_patch}) |
| 620 | 623 |
| 621 | 624 |
| 622 @issue_owner_required | 625 @issue_owner_required |
| 623 def edit(request): | 626 def edit(request): |
| 624 """/<issue>/edit - Edit an issue.""" | 627 """/<issue>/edit - Edit an issue.""" |
| 625 issue = request.issue | 628 issue = request.issue |
| (...skipping 269 matching lines...) Show 10 above Show 10 below |
| 895 'ORDER BY date', | 898 'ORDER BY date', |
| 896 patch=patch, lineno=lineno, left=left) | 899 patch=patch, lineno=lineno, left=left) |
| 897 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) |
| 898 if comment is not None and comment.author is None: | 901 if comment is not None and comment.author is None: |
| 899 # Show anonymous draft even though we don't save it | 902 # Show anonymous draft even though we don't save it |
| 900 comments.append(comment) | 903 comments.append(comment) |
| 901 if not comments: | 904 if not comments: |
| 902 return HttpResponse(' ') | 905 return HttpResponse(' ') |
| 903 for c in comments: | 906 for c in comments: |
| 904 c.complete(patch) | 907 c.complete(patch) |
| 905 return render_to_response('inline_comment.html', | 908 return render_to_response('inline_comment.html', |
| 906 {'user': request.user, | 909 {'user': request.user, |
| 907 'patch': patch, | 910 'patch': patch, |
| 908 'patchset': patchset, | 911 'patchset': patchset, |
| 909 'issue': issue, | 912 'issue': issue, |
| 910 'comments': comments, | 913 'comments': comments, |
| 911 'lineno': lineno, | 914 'lineno': lineno, |
| 912 'snapshot': snapshot, | 915 'snapshot': snapshot, |
| 913 'side': side}) | 916 'side': side}) |
| 914 | 917 |
| 915 | 918 |
| 916 PUBLISH_MAIL_TEMPLATE = """Dear %s, | 919 PUBLISH_MAIL_TEMPLATE = """Dear %s, |
| 917 | 920 |
| 918 New code review comments by %s have been published. | 921 New code review comments by %s have been published. |
| 919 Please go to %s to read them. | 922 Please go to %s to read them. |
| 920 | 923 |
| 921 Message: | 924 Message: |
| 922 %s | 925 %s |
| 923 | 926 |
| 924 Details: | 927 Details: |
| 925 %s | 928 %s |
| 926 | 929 |
| 927 Issue Description: | 930 Issue Description: |
| 928 %s | 931 %s |
| 929 | 932 |
| 930 Sincerely, | 933 Sincerely, |
| 931 | 934 |
| 932 Your friendly code review daemon (%s). | 935 Your friendly code review daemon (%s). |
| 933 """ | 936 """ |
| 934 | 937 |
| 935 @issue_required | 938 @issue_required |
| 936 @login_required | 939 @login_required |
| 937 def publish(request): | 940 def publish(request): |
| 938 """ /<issue>/publish - Publish draft comments and send mail.""" | 941 """ /<issue>/publish - Publish draft comments and send mail.""" |
| 939 issue = request.issue | 942 issue = request.issue |
| 940 if request.user == issue.owner: | 943 if request.user == issue.owner: |
| 941 form_class = PublishForm | 944 form_class = PublishForm |
| 942 else: | 945 else: |
| 943 form_class = MiniPublishForm | 946 form_class = MiniPublishForm |
| 944 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()) |
| 945 form = form_class(initial={'subject': issue.subject, | 952 form = form_class(initial={'subject': issue.subject, |
| 946 'reviewers': ', '.join(issue.reviewers), | 953 'reviewers': ', '.join(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 |
| 1209 branch.delete() | 1217 branch.delete() |
| 1210 num_branches = models.Branch.gql('WHERE repo = :1', repo).count() | 1218 num_branches = models.Branch.gql('WHERE repo = :1', repo).count() |
| 1211 if not num_branches: | 1219 if not num_branches: |
| 1212 # Even if we don't own the repository? Yes, I think so! Empty | 1220 # Even if we don't own the repository? Yes, I think so! Empty |
| 1213 # repositories have no representation on screen. | 1221 # repositories have no representation on screen. |
| 1214 repo.delete() | 1222 repo.delete() |
| 1215 return HttpResponseRedirect('/repos') | 1223 return HttpResponseRedirect('/repos') |
| 1216 | 1224 |
| 1217 | 1225 |
| 1218 ### User Profiles ### | 1226 ### User Profiles ### |
| 1219 | 1227 |
| 1220 @login_required | 1228 @login_required |
| 1221 def settings(request): | 1229 def settings(request): |
| 1222 account = models.Account.get_account_for_user(request.user) | 1230 account = models.Account.get_account_for_user(request.user) |
| 1223 if request.method != 'POST': | 1231 if request.method != 'POST': |
| 1224 nickname = account.nickname | 1232 nickname = account.nickname |
| 1225 form = SettingsForm(initial={'nickname': nickname}) | 1233 form = SettingsForm(initial={'nickname': nickname}) |
| 1226 return respond(request, 'settings.html', {'form': form}) | 1234 return respond(request, 'settings.html', {'form': form}) |
| 1227 form = SettingsForm(request.POST) | 1235 form = SettingsForm(request.POST) |
| 1228 if form.is_valid(): | 1236 if form.is_valid(): |
| 1229 nickname = form.cleaned_data['nickname'].strip() | 1237 nickname = form.cleaned_data['nickname'].strip() |
| 1230 if not nickname: | 1238 if not nickname: |
| 1231 form.errors['nickname'] = ['Your nickname cannot be empty.'] | 1239 form.errors['nickname'] = ['Your nickname cannot be empty.'] |
| 1232 elif '@' in nickname: | 1240 elif '@' in nickname: |
| 1233 form.errors['nickname'] = ['Your nickname cannot contain "@".'] | 1241 form.errors['nickname'] = ['Your nickname cannot contain "@".'] |
| 1234 elif ',' in nickname: | 1242 elif ',' in nickname: |
| 1235 form.errors['nickname'] = ['Your nickname cannot contain ",".'] | 1243 form.errors['nickname'] = ['Your nickname cannot contain ",".'] |
| 1236 else: | 1244 else: |
| 1237 accounts = models.Account.get_accounts_for_nickname(nickname) | 1245 accounts = models.Account.get_accounts_for_nickname(nickname) |
| 1238 if nickname != account.nickname and accounts: | 1246 if nickname != account.nickname and accounts: |
| 1239 form.errors['nickname'] = ['This nickname is already in use.'] | 1247 form.errors['nickname'] = ['This nickname is already in use.'] |
| 1240 else: | 1248 else: |
| 1241 account.nickname = nickname | 1249 account.nickname = nickname |
| 1242 account.put() | 1250 account.put() |
| 1243 if not form.is_valid(): | 1251 if not form.is_valid(): |
| 1244 return respond(request, 'settings.html', {'form': form}) | 1252 return respond(request, 'settings.html', {'form': form}) |
| 1245 return HttpResponseRedirect('/settings') | 1253 return HttpResponseRedirect('/settings') |
| OLD | NEW |