def _update_review(self, request, review, public=None, extra_fields={}, *args, **kwargs): """Common function to update fields on a draft review.""" if not self.has_modify_permissions(request, review): # Can't modify published reviews or those not belonging # to the user. return self._no_access_error(request.user) if 'ship_it' in kwargs: review.ship_it = kwargs['ship_it'] self.set_text_fields(review, 'body_top', **kwargs) self.set_text_fields(review, 'body_bottom', **kwargs) self.import_extra_data(review, review.extra_data, extra_fields) review.save() if public: try: review.publish(user=request.user) except PublishError as e: return PUBLISH_ERROR.with_message(e.msg) return 200, { self.item_result_key: review, }
def _update_reply(self, request, reply, public=None, extra_fields={}, *args, **kwargs): """Common function to update fields on a draft reply.""" if not self.has_modify_permissions(request, reply): # Can't modify published replies or those not belonging # to the user. return self._no_access_error(request.user) old_rich_text = reply.rich_text for field in ('body_top', 'body_bottom'): value = kwargs.get(field, None) if value is not None: setattr(reply, field, value.strip()) if value == '': reply_to = None else: reply_to = reply.base_reply_to setattr(reply, '%s_reply_to' % field, reply_to) if 'text_type' in kwargs: reply.rich_text = (kwargs['text_type'] == self.TEXT_TYPE_MARKDOWN) self.normalize_markdown_fields(reply, ['body_top', 'body_bottom'], old_rich_text, **kwargs) self._import_extra_data(reply.extra_data, extra_fields) if public: try: reply.publish(user=request.user) except PublishError as e: return PUBLISH_ERROR.with_message(e.msg) else: reply.save() return 200, { self.item_result_key: reply, }, { 'Last-Modified': self.get_last_modified(request, reply), }
def _update_reply(self, request, reply, public=None, trivial=False, extra_fields={}, *args, **kwargs): """Common function to update fields on a draft reply.""" if not self.has_modify_permissions(request, reply): # Can't modify published replies or those not belonging # to the user. return self.get_no_access_error(request) for field in ('body_top', 'body_bottom'): value = kwargs.get(field, None) if value is not None: if value == '': reply_to = None else: reply_to = reply.base_reply_to setattr(reply, '%s_reply_to' % field, reply_to) self.set_text_fields(reply, 'body_top', **kwargs) self.set_text_fields(reply, 'body_bottom', **kwargs) try: self.import_extra_data(reply, reply.extra_data, extra_fields) except ImportExtraDataError as e: return e.error_payload if public: try: reply.publish(user=request.user, trivial=trivial) except PublishError as e: return PUBLISH_ERROR.with_message(six.text_type(e)) else: reply.save() return 200, { self.item_result_key: reply, }, { 'Last-Modified': self.get_last_modified(request, reply), }
def _update_review(self, request, review, public=None, extra_fields={}, *args, **kwargs): """Common function to update fields on a draft review.""" if not self.has_modify_permissions(request, review): # Can't modify published reviews or those not belonging # to the user. return self._no_access_error(request.user) old_rich_text = review.rich_text for field in ('ship_it', 'body_top', 'body_bottom'): value = kwargs.get(field, None) if value is not None: if isinstance(value, six.string_types): value = value.strip() setattr(review, field, value) if 'text_type' in kwargs: review.rich_text = \ (kwargs['text_type'] == self.TEXT_TYPE_MARKDOWN) self.normalize_markdown_fields(review, ['body_top', 'body_bottom'], old_rich_text, **kwargs) self._import_extra_data(review.extra_data, extra_fields) review.save() if public: try: review.publish(user=request.user) except PublishError as e: return PUBLISH_ERROR.with_message(e.msg) return 200, { self.item_result_key: review, }
def update_review(self, request, review, public=None, publish_to_submitter_only=False, extra_fields={}, ship_it=None, *args, **kwargs): """Common function to update fields on a draft review.""" if not self.has_modify_permissions(request, review): # Can't modify published reviews or those not belonging # to the user. return self.get_no_access_error(request) if ship_it is not None: review.ship_it = ship_it self.set_text_fields(review, 'body_top', **kwargs) self.set_text_fields(review, 'body_bottom', **kwargs) self.import_extra_data(review, review.extra_data, extra_fields) review.save() if public: try: review.publish(user=request.user, to_submitter_only=publish_to_submitter_only, request=request) except PublishError as e: return PUBLISH_ERROR.with_message(six.text_type(e)) return 200, { self.item_result_key: review, }
def update(self, request, always_save=False, local_site_name=None, update_from_commit_id=False, trivial=None, publish_as_owner=False, extra_fields={}, *args, **kwargs): """Updates a draft of a review request. This will update the draft with the newly provided data. Most of the fields correspond to fields in the review request, but there is one special one, ``public``. When ``public`` is set to true, the draft will be published, moving the new content to the review request itself, making it public, and sending out a notification (such as an e-mail) if configured on the server. The current draft will then be deleted. Extra data can be stored later lookup. See :ref:`webapi2.0-extra-data` for more information. """ try: review_request = resources.review_request.get_object( request, local_site_name=local_site_name, *args, **kwargs) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if kwargs.get('commit_id') == '': kwargs['commit_id'] = None commit_id = kwargs.get('commit_id', None) try: draft = self.prepare_draft(request, review_request) except PermissionDenied: return self.get_no_access_error(request) if (commit_id and commit_id != review_request.commit_id and commit_id != draft.commit_id): # Check to make sure the new commit ID isn't being used already # in another review request or draft. repository = review_request.repository existing_review_request = ReviewRequest.objects.filter( commit_id=commit_id, repository=repository) if (existing_review_request and existing_review_request != review_request): return COMMIT_ID_ALREADY_EXISTS existing_draft = ReviewRequestDraft.objects.filter( commit_id=commit_id, review_request__repository=repository) if existing_draft and existing_draft != draft: return COMMIT_ID_ALREADY_EXISTS modified_objects = [] invalid_fields = {} for field_name, field_info in six.iteritems(self.fields): if (field_info.get('mutable', True) and kwargs.get(field_name, None) is not None): field_result, field_modified_objects, invalid = \ self._set_draft_field_data(draft, field_name, kwargs[field_name], local_site_name, request) if invalid: invalid_fields[field_name] = invalid elif field_modified_objects: modified_objects += field_modified_objects if commit_id and update_from_commit_id: try: draft.update_from_commit_id(commit_id) except InvalidChangeNumberError: return INVALID_CHANGE_NUMBER if draft.changedesc_id: changedesc = draft.changedesc modified_objects.append(draft.changedesc) self.set_text_fields(changedesc, 'changedescription', text_model_field='text', rich_text_field_name='rich_text', **kwargs) self.set_text_fields(draft, 'description', **kwargs) self.set_text_fields(draft, 'testing_done', **kwargs) for field_cls in get_review_request_fields(): if (not issubclass(field_cls, BuiltinFieldMixin) and getattr(field_cls, 'enable_markdown', False)): self.set_extra_data_text_fields(draft, field_cls.field_id, extra_fields, **kwargs) try: self.import_extra_data(draft, draft.extra_data, extra_fields) except ImportExtraDataError as e: return e.error_payload if always_save or not invalid_fields: for obj in set(modified_objects): obj.save() draft.save() if invalid_fields: return INVALID_FORM_DATA, { 'fields': invalid_fields, self.item_result_key: draft, } if request.POST.get('public', False): if not review_request.public and not draft.changedesc_id: # This is a new review request. Publish this on behalf of the # owner of the review request, rather than the current user, # regardless of the original publish_as_owner in the request. # This allows a review request previously created with # submit-as= to be published by that user instead of the # logged in user. publish_as_owner = True if publish_as_owner: publish_user = review_request.owner else: # Default to posting as the logged in user. publish_user = request.user try: review_request.publish(user=publish_user, trivial=trivial) except NotModifiedError: return NOTHING_TO_PUBLISH except PublishError as e: return PUBLISH_ERROR.with_message(six.text_type(e)) return 200, { self.item_result_key: draft, }
def update(self, request, local_site_name=None, branch=None, bugs_closed=None, changedescription=None, commit_id=None, depends_on=None, submitter=None, summary=None, target_groups=None, target_people=None, update_from_commit_id=False, trivial=None, publish_as_owner=False, extra_fields={}, *args, **kwargs): """Updates a draft of a review request. This will update the draft with the newly provided data. Most of the fields correspond to fields in the review request, but there is one special one, ``public``. When ``public`` is set to true, the draft will be published, moving the new content to the review request itself, making it public, and sending out a notification (such as an e-mail) if configured on the server. The current draft will then be deleted. Extra data can be stored later lookup. See :ref:`webapi2.0-extra-data` for more information. """ try: review_request = resources.review_request.get_object( request, local_site_name=local_site_name, *args, **kwargs) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if not review_request.is_mutable_by(request.user): return self.get_no_access_error(request) draft = review_request.get_draft() # Before we update anything, sanitize the commit ID, see if it # changed, and make sure that the the new value isn't owned by # another review request or draft. if commit_id == '': commit_id = None if (commit_id and commit_id != review_request.commit_id and (draft is None or commit_id != draft.commit_id)): # The commit ID has changed, so now we check for other usages of # this ID. repository = review_request.repository existing_review_request_ids = (ReviewRequest.objects.filter( commit_id=commit_id, repository=repository).values_list('pk', flat=True)) if (existing_review_request_ids and review_request.pk not in existing_review_request_ids): # Another review request is using this ID. Error out. return COMMIT_ID_ALREADY_EXISTS existing_draft_ids = (ReviewRequestDraft.objects.filter( commit_id=commit_id, review_request__repository=repository).values_list('pk', flat=True)) if (existing_draft_ids and (draft is None or draft.pk not in existing_draft_ids)): # Another review request draft is using this ID. Error out. return COMMIT_ID_ALREADY_EXISTS # Now that we've completed our initial accessibility and conflict # checks, we can start checking for changes to individual fields. # # We'll keep track of state pertaining to the fields we want to # set/save, and any errors we hit. For setting/saving, these's two # types of things we're tracking: The new field values (which will be # applied to the objects or Many-To-Many relations) and a list of # field names to set when calling `save(update_fields=...)`. The # former implies the latter. The latter only needs to be directly # set if the fields are modified directly by another function. new_draft_values = {} new_changedesc_values = {} draft_update_fields = set() changedesc_update_fields = set() invalid_fields = {} if draft is None: draft = ReviewRequestDraft.create(review_request=review_request) # Check for a new value for branch. if branch is not None: new_draft_values['branch'] = branch # Check for a new value for bugs_closed: if bugs_closed is not None: new_draft_values['bugs_closed'] = \ ','.join(self._parse_bug_list(bugs_closed)) # Check for a new value for changedescription. if changedescription is not None and draft.changedesc_id is None: invalid_fields['changedescription'] = [ 'Change descriptions cannot be used for drafts of ' 'new review requests' ] # Check for a new value for commit_id. if commit_id is not None: new_draft_values['commit_id'] = commit_id if update_from_commit_id: try: draft_update_fields.update( draft.update_from_commit_id(commit_id)) except InvalidChangeNumberError: return INVALID_CHANGE_NUMBER # Check for a new value for depends_on. if depends_on is not None: found_deps, missing_dep_ids = self._find_depends_on( dep_ids=self._parse_value_list(depends_on), request=request) if missing_dep_ids: invalid_fields['depends_on'] = missing_dep_ids else: new_draft_values['depends_on'] = found_deps # Check for a new value for submitter. if submitter is not None: # While we only allow for one submitter, we'll try to parse a # possible list of values. This allows us to provide a suitable # error message if users try to set more than one submitter # (which people do try, in practice). found_users, missing_usernames = self._find_users( usernames=self._parse_value_list(submitter), request=request) if len(found_users) + len(missing_usernames) > 1: invalid_fields['submitter'] = [ 'Only one user can be set as the owner of a review ' 'request' ] elif missing_usernames: assert len(missing_usernames) == 1 invalid_fields['submitter'] = missing_usernames elif found_users: assert len(found_users) == 1 new_draft_values['owner'] = found_users[0] else: invalid_fields['submitter'] = [ 'The owner of a review request cannot be blank' ] # Check for a new value for summary. if summary is not None: if '\n' in summary: invalid_fields['summary'] = [ "The summary can't contain a newline" ] else: new_draft_values['summary'] = summary # Check for a new value for target_groups. if target_groups is not None: found_groups, missing_group_names = self._find_review_groups( group_names=self._parse_value_list(target_groups), request=request) if missing_group_names: invalid_fields['target_groups'] = missing_group_names else: new_draft_values['target_groups'] = found_groups # Check for a new value for target_people. if target_people is not None: found_users, missing_usernames = self._find_users( usernames=self._parse_value_list(target_people), request=request) if missing_usernames: invalid_fields['target_people'] = missing_usernames else: new_draft_values['target_people'] = found_users # See if we've caught any invalid values. If so, we can error out # immediately before we update anything else. if invalid_fields: return INVALID_FORM_DATA, { 'fields': invalid_fields, self.item_result_key: draft, } # Start applying any rich text processing to any text fields on the # ChangeDescription and draft. We'll track any fields that get set # for later saving. # # NOTE: If any text fields or text type fields are ever made # parameters of this method, then their values will need to be # passed directly to set_text_fields() calls below. if draft.changedesc_id: changedesc_update_fields.update( self.set_text_fields(obj=draft.changedesc, text_field='changedescription', text_model_field='text', rich_text_field_name='rich_text', changedescription=changedescription, **kwargs)) for text_field in ('description', 'testing_done'): draft_update_fields.update( self.set_text_fields(obj=draft, text_field=text_field, **kwargs)) # Go through the list of Markdown-enabled custom fields and apply # any rich text processing. These will go in extra_data, so we'll # want to make sure that's tracked for saving. for field_cls in get_review_request_fields(): if (not issubclass(field_cls, BuiltinFieldMixin) and getattr(field_cls, 'enable_markdown', False)): modified_fields = self.set_extra_data_text_fields( obj=draft, text_field=field_cls.field_id, extra_fields=extra_fields, **kwargs) if modified_fields: draft_update_fields.add('extra_data') # See if the caller has set or patched extra_data values. For # compatibility, we're going to do this after processing the rich # text fields ine extra_data above. try: if self.import_extra_data(draft, draft.extra_data, extra_fields): # Track extra_data for saving. draft_update_fields.add('extra_data') except ImportExtraDataError as e: return e.error_payload # Everything checks out. We can now begin the process of applying any # field changes and then save objects. # # We'll start by making an initial pass on the objects we need to # either care about. This optimistically lets us avoid a lookup on the # ChangeDescription, if it's not being modified. to_apply = [] if draft_update_fields or new_draft_values: # If there's any changes made at all to the draft, make sure we # allow last_updated to be computed and saved. if draft_update_fields or new_draft_values: draft_update_fields.add('last_updated') to_apply.append((draft, draft_update_fields, new_draft_values)) if changedesc_update_fields or new_changedesc_values: to_apply.append((draft.changedesc, changedesc_update_fields, new_changedesc_values)) for obj, update_fields, new_values in to_apply: new_m2m_values = {} # We may have a mixture of field values and Many-To-Many # relation values, which we want to set only after the object # is saved. Start by setting any field values, and store the # M2M values for after. for key, value in six.iteritems(new_values): field = obj._meta.get_field(key) if isinstance(field, ManyToManyField): # Save this until after the object is saved. new_m2m_values[key] = value else: # We can set this one now, and mark it for saving. setattr(obj, key, value) update_fields.add(key) if update_fields: obj.save(update_fields=sorted(update_fields)) # Now we can set any values on M2M fields. # # Each entry will have zero or more values. We'll be # setting to the list of values, which will fully replace # the stored entries in the database. for key, values in six.iteritems(new_m2m_values): setattr(obj, key, values) # Next, check if the draft is set to be published. if request.POST.get('public', False): if not review_request.public and not draft.changedesc_id: # This is a new review request. Publish this on behalf of the # owner of the review request, rather than the current user, # regardless of the original publish_as_owner in the request. # This allows a review request previously created with # submit-as= to be published by that user instead of the # logged in user. publish_as_owner = True if publish_as_owner: publish_user = review_request.owner else: # Default to posting as the logged in user. publish_user = request.user try: review_request.publish(user=publish_user, trivial=trivial) except NotModifiedError: return NOTHING_TO_PUBLISH except PublishError as e: return PUBLISH_ERROR.with_message(six.text_type(e)) return 200, { self.item_result_key: draft, }
def update(self, request, local_site_name=None, branch=None, bugs_closed=None, changedescription=None, commit_id=None, depends_on=None, submitter=None, summary=None, target_groups=None, target_people=None, update_from_commit_id=False, trivial=None, publish_as_owner=False, extra_fields={}, *args, **kwargs): """Updates a draft of a review request. This will update the draft with the newly provided data. Most of the fields correspond to fields in the review request, but there is one special one, ``public``. When ``public`` is set to true, the draft will be published, moving the new content to the review request itself, making it public, and sending out a notification (such as an e-mail) if configured on the server. The current draft will then be deleted. Extra data can be stored later lookup. See :ref:`webapi2.0-extra-data` for more information. """ try: review_request = resources.review_request.get_object( request, local_site_name=local_site_name, *args, **kwargs) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if not review_request.is_mutable_by(request.user): return self.get_no_access_error(request) draft = review_request.get_draft() # Before we update anything, sanitize the commit ID, see if it # changed, and make sure that the the new value isn't owned by # another review request or draft. if commit_id == '': commit_id = None if (commit_id and commit_id != review_request.commit_id and (draft is None or commit_id != draft.commit_id)): # The commit ID has changed, so now we check for other usages of # this ID. repository = review_request.repository existing_review_request_ids = ( ReviewRequest.objects .filter(commit_id=commit_id, repository=repository) .values_list('pk', flat=True) ) if (existing_review_request_ids and review_request.pk not in existing_review_request_ids): # Another review request is using this ID. Error out. return COMMIT_ID_ALREADY_EXISTS existing_draft_ids = ( ReviewRequestDraft.objects .filter(commit_id=commit_id, review_request__repository=repository) .values_list('pk', flat=True) ) if (existing_draft_ids and (draft is None or draft.pk not in existing_draft_ids)): # Another review request draft is using this ID. Error out. return COMMIT_ID_ALREADY_EXISTS # Now that we've completed our initial accessibility and conflict # checks, we can start checking for changes to individual fields. # # We'll keep track of state pertaining to the fields we want to # set/save, and any errors we hit. For setting/saving, these's two # types of things we're tracking: The new field values (which will be # applied to the objects or Many-To-Many relations) and a list of # field names to set when calling `save(update_fields=...)`. The # former implies the latter. The latter only needs to be directly # set if the fields are modified directly by another function. new_draft_values = {} new_changedesc_values = {} draft_update_fields = set() changedesc_update_fields = set() invalid_fields = {} if draft is None: draft = ReviewRequestDraft.create(review_request=review_request) # Check for a new value for branch. if branch is not None: new_draft_values['branch'] = branch # Check for a new value for bugs_closed: if bugs_closed is not None: new_draft_values['bugs_closed'] = \ ','.join(self._parse_bug_list(bugs_closed)) # Check for a new value for changedescription. if changedescription is not None and draft.changedesc_id is None: invalid_fields['changedescription'] = [ 'Change descriptions cannot be used for drafts of ' 'new review requests' ] # Check for a new value for commit_id. if commit_id is not None: new_draft_values['commit_id'] = commit_id if update_from_commit_id: try: draft_update_fields.update( draft.update_from_commit_id(commit_id)) except InvalidChangeNumberError: return INVALID_CHANGE_NUMBER # Check for a new value for depends_on. if depends_on is not None: found_deps, missing_dep_ids = self._find_depends_on( dep_ids=self._parse_value_list(depends_on), request=request) if missing_dep_ids: invalid_fields['depends_on'] = missing_dep_ids else: new_draft_values['depends_on'] = found_deps # Check for a new value for submitter. if submitter is not None: # While we only allow for one submitter, we'll try to parse a # possible list of values. This allows us to provide a suitable # error message if users try to set more than one submitter # (which people do try, in practice). found_users, missing_usernames = self._find_users( usernames=self._parse_value_list(submitter), request=request) if len(found_users) + len(missing_usernames) > 1: invalid_fields['submitter'] = [ 'Only one user can be set as the owner of a review ' 'request' ] elif missing_usernames: assert len(missing_usernames) == 1 invalid_fields['submitter'] = missing_usernames elif found_users: assert len(found_users) == 1 new_draft_values['owner'] = found_users[0] else: invalid_fields['submitter'] = [ 'The owner of a review request cannot be blank' ] # Check for a new value for summary. if summary is not None: if '\n' in summary: invalid_fields['summary'] = [ "The summary can't contain a newline" ] else: new_draft_values['summary'] = summary # Check for a new value for target_groups. if target_groups is not None: found_groups, missing_group_names = self._find_review_groups( group_names=self._parse_value_list(target_groups), request=request) if missing_group_names: invalid_fields['target_groups'] = missing_group_names else: new_draft_values['target_groups'] = found_groups # Check for a new value for target_people. if target_people is not None: found_users, missing_usernames = self._find_users( usernames=self._parse_value_list(target_people), request=request) if missing_usernames: invalid_fields['target_people'] = missing_usernames else: new_draft_values['target_people'] = found_users # See if we've caught any invalid values. If so, we can error out # immediately before we update anything else. if invalid_fields: return INVALID_FORM_DATA, { 'fields': invalid_fields, self.item_result_key: draft, } # Start applying any rich text processing to any text fields on the # ChangeDescription and draft. We'll track any fields that get set # for later saving. # # NOTE: If any text fields or text type fields are ever made # parameters of this method, then their values will need to be # passed directly to set_text_fields() calls below. if draft.changedesc_id: changedesc_update_fields.update( self.set_text_fields(obj=draft.changedesc, text_field='changedescription', text_model_field='text', rich_text_field_name='rich_text', changedescription=changedescription, **kwargs)) for text_field in ('description', 'testing_done'): draft_update_fields.update(self.set_text_fields( obj=draft, text_field=text_field, **kwargs)) # Go through the list of Markdown-enabled custom fields and apply # any rich text processing. These will go in extra_data, so we'll # want to make sure that's tracked for saving. for field_cls in get_review_request_fields(): if (not issubclass(field_cls, BuiltinFieldMixin) and getattr(field_cls, 'enable_markdown', False)): modified_fields = self.set_extra_data_text_fields( obj=draft, text_field=field_cls.field_id, extra_fields=extra_fields, **kwargs) if modified_fields: draft_update_fields.add('extra_data') # See if the caller has set or patched extra_data values. For # compatibility, we're going to do this after processing the rich # text fields ine extra_data above. try: if self.import_extra_data(draft, draft.extra_data, extra_fields): # Track extra_data for saving. draft_update_fields.add('extra_data') except ImportExtraDataError as e: return e.error_payload # Everything checks out. We can now begin the process of applying any # field changes and then save objects. # # We'll start by making an initial pass on the objects we need to # either care about. This optimistically lets us avoid a lookup on the # ChangeDescription, if it's not being modified. to_apply = [] if draft_update_fields or new_draft_values: # If there's any changes made at all to the draft, make sure we # allow last_updated to be computed and saved. if draft_update_fields or new_draft_values: draft_update_fields.add('last_updated') to_apply.append((draft, draft_update_fields, new_draft_values)) if changedesc_update_fields or new_changedesc_values: to_apply.append((draft.changedesc, changedesc_update_fields, new_changedesc_values)) for obj, update_fields, new_values in to_apply: new_m2m_values = {} # We may have a mixture of field values and Many-To-Many # relation values, which we want to set only after the object # is saved. Start by setting any field values, and store the # M2M values for after. for key, value in six.iteritems(new_values): field = obj._meta.get_field(key) if isinstance(field, ManyToManyField): # Save this until after the object is saved. new_m2m_values[key] = value else: # We can set this one now, and mark it for saving. setattr(obj, key, value) update_fields.add(key) if update_fields: obj.save(update_fields=sorted(update_fields)) # Now we can set any values on M2M fields. # # Each entry will have zero or more values. We'll be # setting to the list of values, which will fully replace # the stored entries in the database. for key, values in six.iteritems(new_m2m_values): setattr(obj, key, values) # Next, check if the draft is set to be published. if request.POST.get('public', False): if not review_request.public and not draft.changedesc_id: # This is a new review request. Publish this on behalf of the # owner of the review request, rather than the current user, # regardless of the original publish_as_owner in the request. # This allows a review request previously created with # submit-as= to be published by that user instead of the # logged in user. publish_as_owner = True if publish_as_owner: publish_user = review_request.owner else: # Default to posting as the logged in user. publish_user = request.user try: review_request.publish(user=publish_user, trivial=trivial) except NotModifiedError: return NOTHING_TO_PUBLISH except PublishError as e: return PUBLISH_ERROR.with_message(six.text_type(e)) return 200, { self.item_result_key: draft, }
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, {}
def update(self, request, always_save=False, local_site_name=None, update_from_commit_id=False, extra_fields={}, *args, **kwargs): """Updates a draft of a review request. This will update the draft with the newly provided data. Most of the fields correspond to fields in the review request, but there is one special one, ``public``. When ``public`` is set to true, the draft will be published, moving the new content to the review request itself, making it public, and sending out a notification (such as an e-mail) if configured on the server. The current draft will then be deleted. Extra data can be stored on the review request for later lookup by passing ``extra_data.key_name=value``. The ``key_name`` and ``value`` can be any valid strings. Passing a blank ``value`` will remove the key. The ``extra_data.`` prefix is required. """ try: review_request = resources.review_request.get_object( request, local_site_name=local_site_name, *args, **kwargs) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if kwargs.get('commit_id') == '': kwargs['commit_id'] = None commit_id = kwargs.get('commit_id', None) try: draft = self.prepare_draft(request, review_request) except PermissionDenied: return self._no_access_error(request.user) if (commit_id and commit_id != review_request.commit_id and commit_id != draft.commit_id): # Check to make sure the new commit ID isn't being used already # in another review request or draft. repository = review_request.repository existing_review_request = ReviewRequest.objects.filter( commit_id=commit_id, repository=repository) if (existing_review_request and existing_review_request != review_request): return COMMIT_ID_ALREADY_EXISTS existing_draft = ReviewRequestDraft.objects.filter( commit_id=commit_id, review_request__repository=repository) if existing_draft and existing_draft != draft: return COMMIT_ID_ALREADY_EXISTS modified_objects = [] invalid_fields = {} for field_name, field_info in six.iteritems(self.fields): if (field_info.get('mutable', True) and kwargs.get(field_name, None) is not None): field_result, field_modified_objects, invalid = \ self._set_draft_field_data(draft, field_name, kwargs[field_name], local_site_name, request) if invalid: invalid_fields[field_name] = invalid elif field_modified_objects: modified_objects += field_modified_objects if commit_id and update_from_commit_id: try: draft.update_from_commit_id(commit_id) except InvalidChangeNumberError: return INVALID_CHANGE_NUMBER if draft.changedesc_id: changedesc = draft.changedesc modified_objects.append(draft.changedesc) self.set_text_fields(changedesc, 'changedescription', text_model_field='text', rich_text_field_name='rich_text', **kwargs) self.set_text_fields(draft, 'description', **kwargs) self.set_text_fields(draft, 'testing_done', **kwargs) for field_cls in get_review_request_fields(): if (not issubclass(field_cls, BuiltinFieldMixin) and getattr(field_cls, 'enable_markdown', False)): self.set_extra_data_text_fields(draft, field_cls.field_id, extra_fields, **kwargs) self.import_extra_data(draft, draft.extra_data, extra_fields) if always_save or not invalid_fields: for obj in set(modified_objects): obj.save() draft.save() if invalid_fields: return INVALID_FORM_DATA, { 'fields': invalid_fields, self.item_result_key: draft, } if request.POST.get('public', False): try: review_request.publish(user=request.user) except PublishError as e: return PUBLISH_ERROR.with_message(e.msg) except NotModifiedError: return NOTHING_TO_PUBLISH return 200, { self.item_result_key: draft, }
def update(self, request, always_save=False, local_site_name=None, update_from_commit_id=False, extra_fields={}, *args, **kwargs): """Updates a draft of a review request. This will update the draft with the newly provided data. Most of the fields correspond to fields in the review request, but there is one special one, ``public``. When ``public`` is set to true, the draft will be published, moving the new content to the review request itself, making it public, and sending out a notification (such as an e-mail) if configured on the server. The current draft will then be deleted. If ``text_type`` is provided and changed from the original value, then the ``changedescription``, ``description`` and ``testing_done`` fields will be set to be interpreted according to the new type. When setting to ``markdown`` and not specifying any new text, the existing text will be escaped so as not to be unintentionally interpreted as Markdown. When setting to ``plain``, and new text is not provided, the existing text will be unescaped. Extra data can be stored on the review request for later lookup by passing ``extra_data.key_name=value``. The ``key_name`` and ``value`` can be any valid strings. Passing a blank ``value`` will remove the key. The ``extra_data.`` prefix is required. """ try: review_request = resources.review_request.get_object( request, local_site_name=local_site_name, *args, **kwargs) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST try: draft = self.prepare_draft(request, review_request) except PermissionDenied: return self._no_access_error(request.user) modified_objects = [] invalid_fields = {} old_rich_text = draft.rich_text old_changedesc_rich_text = (draft.changedesc_id is not None and draft.changedesc.rich_text) for field_name, field_info in six.iteritems(self.fields): if (field_info.get('mutable', True) and kwargs.get(field_name, None) is not None): field_result, field_modified_objects, invalid = \ self._set_draft_field_data(draft, field_name, kwargs[field_name], local_site_name, request) if invalid: invalid_fields[field_name] = invalid elif field_modified_objects: modified_objects += field_modified_objects commit_id = kwargs.get('commit_id', None) if commit_id and update_from_commit_id: try: draft.update_from_commit_id(commit_id) except InvalidChangeNumberError: return INVALID_CHANGE_NUMBER if draft.changedesc_id: changedesc = draft.changedesc modified_objects.append(draft.changedesc) if 'text_type' in kwargs: changedesc.rich_text = \ (kwargs['text_type'] == self.TEXT_TYPE_MARKDOWN) self.normalize_markdown_fields(changedesc, ['changedescription'], old_changedesc_rich_text, model_field_map={ 'changedescription': 'text', }, **kwargs) self.normalize_markdown_fields(draft, ['description', 'testing_done'], old_rich_text, **kwargs) self._import_extra_data(draft.extra_data, extra_fields) if always_save or not invalid_fields: for obj in set(modified_objects): obj.save() draft.save() if invalid_fields: return INVALID_FORM_DATA, { 'fields': invalid_fields, self.item_result_key: draft, } if request.POST.get('public', False): try: review_request.publish(user=request.user) except PublishError as e: return PUBLISH_ERROR.with_message(e.msg) return 200, { self.item_result_key: draft, }
def update(self, request, always_save=False, local_site_name=None, update_from_commit_id=False, trivial=None, extra_fields={}, *args, **kwargs): """Updates a draft of a review request. This will update the draft with the newly provided data. Most of the fields correspond to fields in the review request, but there is one special one, ``public``. When ``public`` is set to true, the draft will be published, moving the new content to the review request itself, making it public, and sending out a notification (such as an e-mail) if configured on the server. The current draft will then be deleted. Extra data can be stored later lookup. See :ref:`webapi2.0-extra-data` for more information. """ try: review_request = resources.review_request.get_object( request, local_site_name=local_site_name, *args, **kwargs) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if kwargs.get('commit_id') == '': kwargs['commit_id'] = None commit_id = kwargs.get('commit_id', None) try: draft = self.prepare_draft(request, review_request) except PermissionDenied: return self.get_no_access_error(request) if (commit_id and commit_id != review_request.commit_id and commit_id != draft.commit_id): # Check to make sure the new commit ID isn't being used already # in another review request or draft. repository = review_request.repository existing_review_request = ReviewRequest.objects.filter( commit_id=commit_id, repository=repository) if (existing_review_request and existing_review_request != review_request): return COMMIT_ID_ALREADY_EXISTS existing_draft = ReviewRequestDraft.objects.filter( commit_id=commit_id, review_request__repository=repository) if existing_draft and existing_draft != draft: return COMMIT_ID_ALREADY_EXISTS modified_objects = [] invalid_fields = {} for field_name, field_info in six.iteritems(self.fields): if (field_info.get('mutable', True) and kwargs.get(field_name, None) is not None): field_result, field_modified_objects, invalid = \ self._set_draft_field_data(draft, field_name, kwargs[field_name], local_site_name, request) if invalid: invalid_fields[field_name] = invalid elif field_modified_objects: modified_objects += field_modified_objects if commit_id and update_from_commit_id: try: draft.update_from_commit_id(commit_id) except InvalidChangeNumberError: return INVALID_CHANGE_NUMBER if draft.changedesc_id: changedesc = draft.changedesc modified_objects.append(draft.changedesc) self.set_text_fields(changedesc, 'changedescription', text_model_field='text', rich_text_field_name='rich_text', **kwargs) self.set_text_fields(draft, 'description', **kwargs) self.set_text_fields(draft, 'testing_done', **kwargs) for field_cls in get_review_request_fields(): if (not issubclass(field_cls, BuiltinFieldMixin) and getattr(field_cls, 'enable_markdown', False)): self.set_extra_data_text_fields(draft, field_cls.field_id, extra_fields, **kwargs) try: self.import_extra_data(draft, draft.extra_data, extra_fields) except ImportExtraDataError as e: return e.error_payload if always_save or not invalid_fields: for obj in set(modified_objects): obj.save() draft.save() if invalid_fields: return INVALID_FORM_DATA, { 'fields': invalid_fields, self.item_result_key: draft, } if request.POST.get('public', False): try: review_request.publish(user=request.user, trivial=trivial) except NotModifiedError: return NOTHING_TO_PUBLISH except PublishError as e: return PUBLISH_ERROR.with_message(six.text_type(e)) return 200, { self.item_result_key: draft, }
def update_review(self, request, review, public=None, publish_to_owner_only=False, extra_fields={}, ship_it=None, **kwargs): """Update an existing review based on the requested data. This will modify a review, setting new fields requested by the caller. Args: request (django.http.HttpRequest): The HTTP request from the client. review (reviewboard.reviews.models.review.Review): The review being modified. public (bool, optional): Whether the review is being made public for the first time. publish_to_owner_only (bool, optional): Whether an e-mail for the published review should only be sent to the owner of the review request. This is ignored if ``public`` is not ``True``. extra_fields (dict, optional): Extra fields from the request not otherwise handled by the API resource. Any ``extra_data`` modifications from this will be applied to the comment. ship_it (bool, optional): The new Ship It state for the review. **kwargs (dict): Keyword arguments representing additional fields handled by the API resource. Returns: tuple or djblets.webapi.errors.WebAPIError: Either a successful payload containing the review, or an error payload. """ if not self.has_modify_permissions(request, review): # Can't modify published reviews or those not belonging # to the user. return self.get_no_access_error(request) if ship_it is not None: review.ship_it = ship_it self.set_text_fields(review, 'body_top', **kwargs) self.set_text_fields(review, 'body_bottom', **kwargs) try: self.import_extra_data(review, review.extra_data, extra_fields) except ImportExtraDataError as e: return e.error_payload review.save() if public: try: review.publish(user=request.user, to_owner_only=publish_to_owner_only, request=request) except PublishError as e: return PUBLISH_ERROR.with_message(six.text_type(e)) return 200, { self.item_result_key: review, }
def update(self, request, always_save=False, local_site_name=None, update_from_commit_id=False, extra_fields={}, *args, **kwargs): """Updates a draft of a review request. This will update the draft with the newly provided data. Most of the fields correspond to fields in the review request, but there is one special one, ``public``. When ``public`` is set to true, the draft will be published, moving the new content to the review request itself, making it public, and sending out a notification (such as an e-mail) if configured on the server. The current draft will then be deleted. If ``text_type`` is provided and changed from the original value, then the ``changedescription``, ``description`` and ``testing_done`` fields will be set to be interpreted according to the new type. When setting to ``markdown`` and not specifying any new text, the existing text will be escaped so as not to be unintentionally interpreted as Markdown. When setting to ``plain``, and new text is not provided, the existing text will be unescaped. Extra data can be stored on the review request for later lookup by passing ``extra_data.key_name=value``. The ``key_name`` and ``value`` can be any valid strings. Passing a blank ``value`` will remove the key. The ``extra_data.`` prefix is required. """ try: review_request = resources.review_request.get_object( request, local_site_name=local_site_name, *args, **kwargs) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if kwargs.get('commit_id') == '': kwargs['commit_id'] = None commit_id = kwargs.get('commit_id', None) try: draft = self.prepare_draft(request, review_request) except PermissionDenied: return self._no_access_error(request.user) if (commit_id and commit_id != review_request.commit_id and commit_id != draft.commit_id): # Check to make sure the new commit ID isn't being used already # in another review request or draft. repository = review_request.repository existing_review_request = ReviewRequest.objects.filter( commit_id=commit_id, repository=repository) if (existing_review_request and existing_review_request != review_request): return COMMIT_ID_ALREADY_EXISTS existing_draft = ReviewRequestDraft.objects.filter( commit_id=commit_id, review_request__repository=repository) if existing_draft and existing_draft != draft: return COMMIT_ID_ALREADY_EXISTS modified_objects = [] invalid_fields = {} old_rich_text = draft.rich_text old_changedesc_rich_text = (draft.changedesc_id is not None and draft.changedesc.rich_text) for field_name, field_info in six.iteritems(self.fields): if (field_info.get('mutable', True) and kwargs.get(field_name, None) is not None): field_result, field_modified_objects, invalid = \ self._set_draft_field_data(draft, field_name, kwargs[field_name], local_site_name, request) if invalid: invalid_fields[field_name] = invalid elif field_modified_objects: modified_objects += field_modified_objects if commit_id and update_from_commit_id: try: draft.update_from_commit_id(commit_id) except InvalidChangeNumberError: return INVALID_CHANGE_NUMBER if draft.changedesc_id: changedesc = draft.changedesc modified_objects.append(draft.changedesc) if 'text_type' in kwargs: changedesc.rich_text = \ (kwargs['text_type'] == self.TEXT_TYPE_MARKDOWN) self.normalize_markdown_fields(changedesc, ['changedescription'], old_changedesc_rich_text, model_field_map={ 'changedescription': 'text', }, **kwargs) self.normalize_markdown_fields(draft, ['description', 'testing_done'], old_rich_text, **kwargs) self._import_extra_data(draft.extra_data, extra_fields) if always_save or not invalid_fields: for obj in set(modified_objects): obj.save() draft.save() if invalid_fields: return INVALID_FORM_DATA, { 'fields': invalid_fields, self.item_result_key: draft, } if request.POST.get('public', False): try: review_request.publish(user=request.user) except PublishError as e: return PUBLISH_ERROR.with_message(e.msg) except NotModifiedError: return NOTHING_TO_PUBLISH return 200, { self.item_result_key: draft, }