def on_review_request_closed_discarded(user, review_request, type, **kwargs): if type != ReviewRequest.DISCARDED: return commit_data = fetch_commit_data(review_request) if is_parent(review_request, commit_data): # close_child_review_requests will call save on this review request, so # we don't have to worry about it. review_request.commit = None _close_child_review_requests(user, review_request, ReviewRequest.DISCARDED, AUTO_CLOSE_DESCRIPTION, commit_data=commit_data) else: # TODO: Remove this once we properly prevent users from closing # commit review requests. b = Bugzilla(get_bugzilla_api_key(user)) bug = int(review_request.get_bug_list()[0]) attachment_updates = BugzillaAttachmentUpdates(b, bug) attachment_updates.obsolete_review_attachments( get_diff_url(review_request)) attachment_updates.do_updates()
def on_reply_publishing(user, reply, **kwargs): review_request = reply.review_request # skip review requests that were not pushed if not is_review_request_pushed(review_request): return bug_id = int(review_request.get_bug_list()[0]) b = Bugzilla(get_bugzilla_api_key(user)) url = get_reply_url(reply) comment = build_plaintext_review(reply, url, {"user": user}) b.post_comment(bug_id, comment)
def on_review_publishing(user, review, **kwargs): """Comment in the bug and potentially r+ or clear a review flag. Note that a reviewer *must* have editbugs to set an attachment flag on someone else's attachment (i.e. the standard BMO review process). TODO: Report lack-of-editbugs properly; see bug 1119065. """ review_request = review.review_request logger.info('Publishing review for user: %s review id: %s ' 'review request id: %s' % (user, review.id, review_request.id)) # skip review requests that were not pushed if not is_pushed(review_request): logger.info('Did not publish review: %s: for user: %d: review not ' 'pushed.' % (user, review.id)) return site = Site.objects.get_current() siteconfig = SiteConfiguration.objects.get_current() comment = build_plaintext_review(review, get_obj_url(review, site, siteconfig), {"user": user}) b = Bugzilla(get_bugzilla_api_key(user)) # TODO: Update all attachments in one call. This is not possible right # now because we have to potentially mix changing and creating flags. if is_parent(review_request): # Mirror the comment to the bug, unless it's a ship-it, in which # case throw an error. Ship-its are allowed only on child commits. if review.ship_it: raise ParentShipItError [b.post_comment(int(bug_id), comment) for bug_id in review_request.get_bug_list()] else: diff_url = '%sdiff/#index_header' % get_obj_url(review_request) bug_id = int(review_request.get_bug_list()[0]) if review.ship_it: commented = b.r_plus_attachment(bug_id, review.user.email, diff_url, comment) else: commented = b.cancel_review_request(bug_id, review.user.email, diff_url, comment) if comment and not commented: b.post_comment(bug_id, comment)
def on_reply_publishing(user, reply, **kwargs): review_request = reply.review_request logger.info('Posting bugzilla reply for review request %s' % (review_request.id)) # skip review requests that were not pushed if not is_pushed(review_request): return bug_id = int(review_request.get_bug_list()[0]) b = Bugzilla(get_bugzilla_api_key(user)) url = get_reply_url(reply) comment = build_plaintext_review(reply, url, {"user": user}) b.post_comment(bug_id, comment, get_diff_url(review_request), True)
def on_review_request_closed_discarded(user, review_request, type, **kwargs): if type != ReviewRequest.DISCARDED: return if is_review_request_squashed(review_request): # close_child_review_requests will call save on this review request, so # we don't have to worry about it. review_request.commit = None close_child_review_requests(user, review_request, ReviewRequest.DISCARDED, AUTO_CLOSE_DESCRIPTION) else: # TODO: Remove this once we properly prevent users from closing # commit review requests. b = Bugzilla(get_bugzilla_api_key(user)) bug = int(review_request.get_bug_list()[0]) url = get_obj_url(review_request) b.obsolete_review_attachments(bug, url)
def on_review_request_closed_discarded(user, review_request, type, **kwargs): if type != ReviewRequest.DISCARDED: return commit_data = fetch_commit_data(review_request) if is_parent(review_request, commit_data): # close_child_review_requests will call save on this review request, so # we don't have to worry about it. review_request.commit = None _close_child_review_requests(user, review_request, ReviewRequest.DISCARDED, AUTO_CLOSE_DESCRIPTION, commit_data=commit_data) else: # TODO: Remove this once we properly prevent users from closing # commit review requests. b = Bugzilla(get_bugzilla_api_key(user)) bug = int(review_request.get_bug_list()[0]) diff_url = '%sdiff/#index_header' % get_obj_url(review_request) b.obsolete_review_attachments(bug, diff_url)
def create(self, request, reviewers, *args, **kwargs): bugzilla = Bugzilla(get_bugzilla_api_key(request.user)) new_reviewers = [u.strip() for u in reviewers.split(',') if u.strip()] invalid_reviewers = [] for reviewer in new_reviewers: try: bugzilla.get_user_from_irc_nick(reviewer) except User.DoesNotExist: invalid_reviewers.append(reviewer) if invalid_reviewers: # Because this isn't called through Review Board's built-in # backbone system, it's dramatically simpler to return just the # intended error message instead of categorising the errors by # field. if len(invalid_reviewers) == 1: return INVALID_FORM_DATA.with_message( "The reviewer '%s' was not found" % invalid_reviewers[0]) else: return INVALID_FORM_DATA.with_message( "The reviewers '%s' were not found" % "', '".join(invalid_reviewers)) return 200, {}
def on_review_request_publishing(user, review_request_draft, **kwargs): # There have been strange cases (all local, and during development), where # when attempting to publish a review request, this handler will fail # because the draft does not exist. This is a really strange case, and not # one we expect to happen in production. However, since we've seen it # locally, we handle it here, and log. if not review_request_draft: logging.error('Strangely, there was no review request draft on the ' 'review request we were attempting to publish.') return review_request = review_request_draft.get_review_request() # skip review requests that were not pushed if not is_review_request_pushed(review_request): return if not is_parent(review_request): # Send a signal asking for approval to publish this review request. # We only want to publish this commit request if we are in the middle # of publishing the parent. If the parent is publishing it will be # listening for this signal to approve it. approvals = commit_request_publishing.send_robust( sender=review_request, user=user, review_request_draft=review_request_draft) for receiver, approved in approvals: if approved: break else: # This publish is not approved by the parent review request. raise CommitPublishProhibited() # The reviewid passed through p2rb is, for Mozilla's instance anyway, # bz://<bug id>/<irc nick>. reviewid = review_request_draft.extra_data.get('p2rb.identifier', None) m = REVIEWID_RE.match(reviewid) if not m: raise InvalidBugIdError('<unknown>') bug_id = m.group(1) using_bugzilla = we_are_using_bugzilla() try: bug_id = int(bug_id) except (TypeError, ValueError): raise InvalidBugIdError(bug_id) if using_bugzilla: b = Bugzilla(get_bugzilla_api_key(user)) try: if b.is_bug_confidential(bug_id): raise ConfidentialBugError except BugzillaError as e: # Special cases: # 100: Invalid Bug Alias # 101: Bug does not exist if e.fault_code and (e.fault_code == 100 or e.fault_code == 101): raise InvalidBugIdError(bug_id) raise # Note that the bug ID has already been set when the review was created. # If this is a squashed/parent review request, automatically publish all # relevant children. if is_review_request_squashed(review_request): unpublished_rids = map(int, json.loads( review_request.extra_data['p2rb.unpublished_rids'])) discard_on_publish_rids = map(int, json.loads( review_request.extra_data['p2rb.discard_on_publish_rids'])) child_rrs = list(gen_child_rrs(review_request_draft)) # Create or update Bugzilla attachments for each draft commit. This # is done before the children are published to ensure that MozReview # doesn't get into a strange state if communication with Bugzilla is # broken or attachment creation otherwise fails. The Bugzilla # attachments will then, of course, be in a weird state, but that # should be fixed by the next successful publish. if using_bugzilla: for child in child_rrs: child_draft = child.get_draft(user=user) if child_draft: if child.id in discard_on_publish_rids: b.obsolete_review_attachments( bug_id, get_obj_url(child)) post_bugzilla_attachment(b, bug_id, child_draft, child) # Publish draft commits. This will already include items that are in # unpublished_rids, so we'll remove anything we publish out of # unpublished_rids. for child in child_rrs: if child.get_draft(user=user) or not child.public: def approve_publish(sender, user, review_request_draft, **kwargs): return child is sender # Setup the parent signal handler to approve the publish # and then publish the child. commit_request_publishing.connect(approve_publish, sender=child, weak=False) try: child.publish(user=user) finally: commit_request_publishing.disconnect( receiver=approve_publish, sender=child, weak=False) if child.id in unpublished_rids: unpublished_rids.remove(child.id) # The remaining unpubished_rids need to be closed as discarded because # they have never been published, and they will appear in the user's # dashboard unless closed. for child in gen_rrs_by_rids(unpublished_rids): child.close(ReviewRequest.DISCARDED, user=user, description=NEVER_USED_DESCRIPTION) # We also close the discard_on_publish review requests because, well, # we don't need them anymore. We use a slightly different message # though. for child in gen_rrs_by_rids(discard_on_publish_rids): child.close(ReviewRequest.DISCARDED, user=user, description=OBSOLETE_DESCRIPTION) review_request.extra_data['p2rb.unpublished_rids'] = '[]' review_request.extra_data['p2rb.discard_on_publish_rids'] = '[]' # Copy p2rb extra data from the draft, overwriting the current # values on the review request. draft_extra_data = review_request_draft.extra_data for key in DRAFTED_EXTRA_DATA_KEYS: if key in draft_extra_data: review_request.extra_data[key] = draft_extra_data[key] review_request.save()
def create(self, request, *args, **kwargs): ext = get_extension_manager().get_enabled_extension( 'mozreview.extension.MozReviewExtension') testing = ext.get_settings('autoland_testing', False) if not testing and not request.user.has_perm( 'mozreview.add_autolandeventlogentry'): return PERMISSION_DENIED try: fields = json.loads(request.body) for field_name in self.fields: assert (type( fields[field_name]) == self.fields[field_name]['type']) except (ValueError, IndexError, KeyError, AssertionError) as e: return INVALID_FORM_DATA, { 'error': '%s' % e, } try: autoland_request = AutolandRequest.objects.get( pk=fields['request_id']) except AutolandRequest.DoesNotExist: return DOES_NOT_EXIST rr = ReviewRequest.objects.get(pk=autoland_request.review_request_id) bz_comment = None if fields['landed']: autoland_request.repository_revision = fields['result'] autoland_request.save() # If we've landed to the "inbound" repository, we'll close the # review request automatically. landing_repo = rr.repository.extra_data.get( 'landing_repository_url') if autoland_request.repository_url == landing_repo: rr.close(ReviewRequest.SUBMITTED) AutolandEventLogEntry.objects.create( autoland_request_id=fields['request_id'], status=AutolandEventLogEntry.SERVED, details=fields['result']) elif not fields.get('error_msg') and fields.get('result'): AutolandEventLogEntry.objects.create( autoland_request_id=fields['request_id'], status=AutolandEventLogEntry.REQUESTED, details=fields['result']) else: AutolandEventLogEntry.objects.create( autoland_request_id=fields['request_id'], status=AutolandEventLogEntry.PROBLEM, error_msg=fields['error_msg']) # The error message contains context explaining that Autoland # failed, so no leading text is necessary. bz_comment = fields['error_msg'] lock_id = get_autoland_lock_id(rr.id, autoland_request.repository_url, autoland_request.push_revision) release_lock(lock_id) if bz_comment: bugzilla = Bugzilla(get_bugzilla_api_key(request.user)) bug_id = int(rr.get_bug_list()[0]) # Catch and log Bugzilla errors rather than bubbling them up, # since we don't want the Autoland server to continously # retry the update. try: bugzilla.post_comment(bug_id, bz_comment) except BugzillaError as e: logger.error('Failed to post comment to Bugzilla: %s' % e) return 200, {}
def on_review_publishing(user, review, **kwargs): """Comment in the bug and potentially r+ or clear a review flag. Note that a reviewer *must* have editbugs to set an attachment flag on someone else's attachment (i.e. the standard BMO review process). TODO: Report lack-of-editbugs properly; see bug 1119065. """ review_request = review.review_request logger.info('Publishing review for user: %s review id: %s ' 'review request id: %s' % (user, review.id, review_request.id)) # skip review requests that were not pushed if not is_pushed(review_request): logger.info('Did not publish review: %s: for user: %d: review not ' 'pushed.' % (user, review.id)) return site = Site.objects.get_current() siteconfig = SiteConfiguration.objects.get_current() comment = build_plaintext_review(review, get_obj_url(review, site, siteconfig), {"user": user}) b = Bugzilla(get_bugzilla_api_key(user)) if is_parent(review_request): # We only support raw comments on parent review requests to prevent # confusion. If the ship-it flag or the review flag was set, throw # an error. # Otherwise, mirror the comment over, associating it with the first # commit. if review.ship_it or review.extra_data.get(REVIEW_FLAG_KEY): raise ParentShipItError # TODO: If we ever allow multiple bugs in a single series, and we want # to continue to allow comments on parents, we'll have to pick one # child for each unique bug. first_child = list(gen_child_rrs(review_request))[0] b.post_comment(int(first_child.get_bug_list()[0]), comment, get_diff_url(first_child), False) else: diff_url = get_diff_url(review_request) bug_id = int(review_request.get_bug_list()[0]) commented = False flag = review.extra_data.get(REVIEW_FLAG_KEY) if flag is not None: commented = b.set_review_flag(bug_id, flag, review.user.email, diff_url, comment) else: # If for some reasons we don't have the flag set in extra_data, # fall back to ship_it logger.warning('Review flag not set on review %s, ' 'updating attachment based on ship_it' % review.id) if review.ship_it: commented = b.r_plus_attachment(bug_id, review.user.email, diff_url, comment) else: commented = b.cancel_review_request(bug_id, review.user.email, diff_url, comment) if comment and not commented: b.post_comment(bug_id, comment, diff_url, False)
def on_review_request_publishing(user, review_request_draft, **kwargs): # There have been strange cases (all local, and during development), where # when attempting to publish a review request, this handler will fail # because the draft does not exist. This is a really strange case, and not # one we expect to happen in production. However, since we've seen it # locally, we handle it here, and log. if not review_request_draft: logger.error('Strangely, there was no review request draft on the ' 'review request we were attempting to publish.') return # If the review request draft has a new DiffSet we will only allow # publishing if that DiffSet has been verified. It is important to # do this for every review request, not just pushed ones, because # we can't trust the storage mechanism which indicates it was pushed. # TODO: This will be fixed when we transition away from extra_data. if review_request_draft.diffset: try: DiffSetVerification.objects.get( diffset=review_request_draft.diffset) except DiffSetVerification.DoesNotExist: logger.error( 'An attempt was made by User %s to publish an unverified ' 'DiffSet with id %s', user.id, review_request_draft.diffset.id) raise PublishError( 'This review request draft contained a manually uploaded ' 'diff, which is prohibited. Please push to the review server ' 'to create review requests. If you believe you received this ' 'message in error, please file a bug.') review_request = review_request_draft.get_review_request() commit_data = fetch_commit_data(review_request) # skip review requests that were not pushed if not is_pushed(review_request, commit_data=commit_data): return if not is_parent(review_request, commit_data): # Send a signal asking for approval to publish this review request. # We only want to publish this commit request if we are in the middle # of publishing the parent. If the parent is publishing it will be # listening for this signal to approve it. approvals = commit_request_publishing.send_robust( sender=review_request, user=user, review_request_draft=review_request_draft) for receiver, approved in approvals: if approved: break else: # This publish is not approved by the parent review request. raise CommitPublishProhibited() # The reviewid passed through p2rb is, for Mozilla's instance anyway, # bz://<bug id>/<irc nick>. reviewid = commit_data.draft_extra_data.get(IDENTIFIER_KEY, None) m = REVIEWID_RE.match(reviewid) if not m: raise InvalidBugIdError('<unknown>') bug_id = m.group(1) try: bug_id = int(bug_id) except (TypeError, ValueError): raise InvalidBugIdError(bug_id) siteconfig = SiteConfiguration.objects.get_current() using_bugzilla = (siteconfig.settings.get("auth_backend", "builtin") == "bugzilla") if using_bugzilla: commit_data = fetch_commit_data(review_request_draft) publish_as_id = commit_data.draft_extra_data.get(PUBLISH_AS_KEY) if publish_as_id: u = User.objects.get(id=publish_as_id) b = Bugzilla(get_bugzilla_api_key(u)) else: b = Bugzilla(get_bugzilla_api_key(user)) try: if b.is_bug_confidential(bug_id): raise ConfidentialBugError except BugzillaError as e: # Special cases: # 100: Invalid Bug Alias # 101: Bug does not exist if e.fault_code and (e.fault_code == 100 or e.fault_code == 101): raise InvalidBugIdError(bug_id) raise # Note that the bug ID has already been set when the review was created. # If this is a squashed/parent review request, automatically publish all # relevant children. if is_parent(review_request, commit_data): unpublished_rids = map( int, json.loads(commit_data.extra_data[UNPUBLISHED_KEY])) discard_on_publish_rids = map( int, json.loads(commit_data.extra_data[DISCARD_ON_PUBLISH_KEY])) child_rrs = list(gen_child_rrs(review_request_draft)) # Create or update Bugzilla attachments for each draft commit. This # is done before the children are published to ensure that MozReview # doesn't get into a strange state if communication with Bugzilla is # broken or attachment creation otherwise fails. The Bugzilla # attachments will then, of course, be in a weird state, but that # should be fixed by the next successful publish. if using_bugzilla: children_to_post = [] children_to_obsolete = [] for child in child_rrs: child_draft = child.get_draft(user=user) if child_draft: if child.id in discard_on_publish_rids: children_to_obsolete.append(child) children_to_post.append((child_draft, child)) if children_to_post or children_to_obsolete: update_bugzilla_attachments(b, bug_id, children_to_post, children_to_obsolete) # Publish draft commits. This will already include items that are in # unpublished_rids, so we'll remove anything we publish out of # unpublished_rids. for child in child_rrs: if child.get_draft(user=user) or not child.public: def approve_publish(sender, user, review_request_draft, **kwargs): return child is sender # Setup the parent signal handler to approve the publish # and then publish the child. commit_request_publishing.connect(approve_publish, sender=child, weak=False) try: child.publish(user=user) except NotModifiedError: # As we create empty drafts as part of allowing reviewer # delegation, delete these empty drafts instead of # throwing an error. child.get_draft(user=user).delete() finally: commit_request_publishing.disconnect( receiver=approve_publish, sender=child, weak=False) if child.id in unpublished_rids: unpublished_rids.remove(child.id) # The remaining unpubished_rids need to be closed as discarded because # they have never been published, and they will appear in the user's # dashboard unless closed. for child in gen_rrs_by_rids(unpublished_rids): child.close(ReviewRequest.DISCARDED, user=user, description=NEVER_USED_DESCRIPTION) # We also close the discard_on_publish review requests because, well, # we don't need them anymore. We use a slightly different message # though. for child in gen_rrs_by_rids(discard_on_publish_rids): child.close(ReviewRequest.DISCARDED, user=user, description=OBSOLETE_DESCRIPTION) commit_data.extra_data[UNPUBLISHED_KEY] = '[]' commit_data.extra_data[DISCARD_ON_PUBLISH_KEY] = '[]' # Copy any drafted CommitData from draft_extra_data to extra_data. for key in DRAFTED_COMMIT_DATA_KEYS: if key in commit_data.draft_extra_data: commit_data.extra_data[key] = commit_data.draft_extra_data[key] commit_data.save(update_fields=['extra_data']) review_request.save()
def on_review_request_publishing(user, review_request_draft, **kwargs): # There have been strange cases (all local, and during development), where # when attempting to publish a review request, this handler will fail # because the draft does not exist. This is a really strange case, and not # one we expect to happen in production. However, since we've seen it # locally, we handle it here, and log. if not review_request_draft: logger.error('Strangely, there was no review request draft on the ' 'review request we were attempting to publish.') return # If the review request draft has a new DiffSet we will only allow # publishing if that DiffSet has been verified. It is important to # do this for every review request, not just pushed ones, because # we can't trust the storage mechanism which indicates it was pushed. # TODO: This will be fixed when we transition away from extra_data. if review_request_draft.diffset: try: DiffSetVerification.objects.get( diffset=review_request_draft.diffset) except DiffSetVerification.DoesNotExist: logger.error( 'An attempt was made by User %s to publish an unverified ' 'DiffSet with id %s', user.id, review_request_draft.diffset.id) raise PublishError( 'This review request draft contained a manually uploaded ' 'diff, which is prohibited. Please push to the review server ' 'to create review requests. If you believe you received this ' 'message in error, please file a bug.') review_request = review_request_draft.get_review_request() commit_data = fetch_commit_data(review_request) # skip review requests that were not pushed if not is_pushed(review_request, commit_data=commit_data): return if not is_parent(review_request, commit_data): # Send a signal asking for approval to publish this review request. # We only want to publish this commit request if we are in the middle # of publishing the parent. If the parent is publishing it will be # listening for this signal to approve it. approvals = commit_request_publishing.send_robust( sender=review_request, user=user, review_request_draft=review_request_draft) for receiver, approved in approvals: if approved: break else: # This publish is not approved by the parent review request. raise CommitPublishProhibited() # The reviewid passed through p2rb is, for Mozilla's instance anyway, # bz://<bug id>/<irc nick>. reviewid = commit_data.draft_extra_data.get(IDENTIFIER_KEY, None) m = REVIEWID_RE.match(reviewid) if not m: raise InvalidBugIdError('<unknown>') bug_id = m.group(1) try: bug_id = int(bug_id) except (TypeError, ValueError): raise InvalidBugIdError(bug_id) siteconfig = SiteConfiguration.objects.get_current() using_bugzilla = ( siteconfig.settings.get("auth_backend", "builtin") == "bugzilla") if using_bugzilla: b = Bugzilla(get_bugzilla_api_key(user)) try: if b.is_bug_confidential(bug_id): raise ConfidentialBugError except BugzillaError as e: # Special cases: # 100: Invalid Bug Alias # 101: Bug does not exist if e.fault_code and (e.fault_code == 100 or e.fault_code == 101): raise InvalidBugIdError(bug_id) raise # Note that the bug ID has already been set when the review was created. # If this is a squashed/parent review request, automatically publish all # relevant children. if is_parent(review_request, commit_data): unpublished_rids = map(int, json.loads( commit_data.extra_data[UNPUBLISHED_KEY])) discard_on_publish_rids = map(int, json.loads( commit_data.extra_data[DISCARD_ON_PUBLISH_KEY])) child_rrs = list(gen_child_rrs(review_request_draft)) # Create or update Bugzilla attachments for each draft commit. This # is done before the children are published to ensure that MozReview # doesn't get into a strange state if communication with Bugzilla is # broken or attachment creation otherwise fails. The Bugzilla # attachments will then, of course, be in a weird state, but that # should be fixed by the next successful publish. if using_bugzilla: for child in child_rrs: child_draft = child.get_draft(user=user) if child_draft: if child.id in discard_on_publish_rids: b.obsolete_review_attachments( bug_id, get_obj_url(child)) post_bugzilla_attachment(b, bug_id, child_draft, child) # Publish draft commits. This will already include items that are in # unpublished_rids, so we'll remove anything we publish out of # unpublished_rids. for child in child_rrs: if child.get_draft(user=user) or not child.public: def approve_publish(sender, user, review_request_draft, **kwargs): return child is sender # Setup the parent signal handler to approve the publish # and then publish the child. commit_request_publishing.connect(approve_publish, sender=child, weak=False) try: child.publish(user=user) finally: commit_request_publishing.disconnect( receiver=approve_publish, sender=child, weak=False) if child.id in unpublished_rids: unpublished_rids.remove(child.id) # The remaining unpubished_rids need to be closed as discarded because # they have never been published, and they will appear in the user's # dashboard unless closed. for child in gen_rrs_by_rids(unpublished_rids): child.close(ReviewRequest.DISCARDED, user=user, description=NEVER_USED_DESCRIPTION) # We also close the discard_on_publish review requests because, well, # we don't need them anymore. We use a slightly different message # though. for child in gen_rrs_by_rids(discard_on_publish_rids): child.close(ReviewRequest.DISCARDED, user=user, description=OBSOLETE_DESCRIPTION) commit_data.extra_data[UNPUBLISHED_KEY] = '[]' commit_data.extra_data[DISCARD_ON_PUBLISH_KEY] = '[]' # Copy any drafted CommitData from draft_extra_data to extra_data. for key in DRAFTED_COMMIT_DATA_KEYS: if key in commit_data.draft_extra_data: commit_data.extra_data[key] = commit_data.draft_extra_data[key] commit_data.save(update_fields=['extra_data']) review_request.save()
def create(self, request, parent_request_id, reviewers, *args, **kwargs): try: parent_rr = ReviewRequest.objects.get(pk=parent_request_id) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if not (parent_rr.is_accessible_by(request.user) or parent_rr.is_mutable_by(request.user)): return PERMISSION_DENIED if not is_parent(parent_rr): return NOT_PARENT # Validate and expand the new reviewer list. bugzilla = Bugzilla(get_bugzilla_api_key(request.user)) child_reviewers = json.loads(reviewers) invalid_reviewers = [] for child_rrid in child_reviewers: users = [] for username in child_reviewers[child_rrid]: try: users.append(bugzilla.get_user_from_irc_nick(username)) except User.DoesNotExist: invalid_reviewers.append(username) child_reviewers[child_rrid] = users if invalid_reviewers: # Because this isn't called through Review Board's built-in # backbone system, it's dramatically simpler to return just the # intended error message instead of categorising the errors by # field. if len(invalid_reviewers) == 1: return INVALID_FORM_DATA.with_message( "The reviewer '%s' was not found" % invalid_reviewers[0]) else: return INVALID_FORM_DATA.with_message( "The reviewers '%s' were not found" % "', '".join(invalid_reviewers)) # Review Board only supports the submitter updating a review # request. In order for this to work, we publish these changes # in Review Board under the review submitter's account, and # set an extra_data field which instructs our bugzilla # connector to use this request's user when adjusting flags. # # Updating the review request requires creating a draft and # publishing it, so we have to be careful to not overwrite # existing drafts. try: with transaction.atomic(): for rr in itertools.chain([parent_rr], gen_child_rrs(parent_rr)): if rr.get_draft() is not None: return REVIEW_REQUEST_UPDATE_NOT_ALLOWED.with_message( "Unable to update reviewers as the review " "request has pending changes (the patch author " "has a draft)") try: for child_rr in gen_child_rrs(parent_rr): if str(child_rr.id) in child_reviewers: if not child_rr.is_accessible_by(request.user): return PERMISSION_DENIED.with_message( "You do not have permission to update " "reviewers on review request %s" % child_rr.id) draft = ReviewRequestDraft.create(child_rr) draft.target_people.clear() for user in child_reviewers[str(child_rr.id)]: draft.target_people.add(user) set_publish_as(parent_rr, request.user) parent_rr_draft = ReviewRequestDraft.create(parent_rr) update_parent_rr_reviewers(parent_rr_draft) parent_rr.publish(user=parent_rr.submitter) finally: clear_publish_as(parent_rr) except PublishError as e: logging.error("failed to update reviewers on %s: %s" % (parent_rr.id, str(e))) return PUBLISH_ERROR.with_message(str(e)) return 200, {}