def publish(self, user, trivial=False, validate_fields=True): """Publishes the current draft attached to this review request. The review request will be mark as public, and signals will be emitted for any listeners. """ if not self.is_mutable_by(user): raise PermissionError draft = get_object_or_none(self.draft) old_submitter = self.submitter review_request_publishing.send(sender=self.__class__, user=user, review_request_draft=draft) # Decrement the counts on everything. we lose them. # We'll increment the resulting set during ReviewRequest.save. # This should be done before the draft is published. # Once the draft is published, the target people # and groups will be updated with new values. # Decrement should not happen while publishing # a new request or a discarded request if self.public: self._decrement_reviewer_counts() if draft is not None: # This will in turn save the review request, so we'll be done. try: changes = draft.publish(self, send_notification=False, user=user, validate_fields=validate_fields) except Exception: # The draft failed to publish, for one reason or another. # Check if we need to re-increment those counters we # previously decremented. if self.public: self._increment_reviewer_counts() raise draft.delete() else: changes = None if not self.public and self.changedescs.count() == 0: # This is a brand new review request that we're publishing # for the first time. Set the creation timestamp to now. self.time_added = timezone.now() self.public = True self.save(update_counts=True, old_submitter=old_submitter) review_request_published.send(sender=self.__class__, user=user, review_request=self, trivial=trivial, changedesc=changes)
def publish(self, user): """Publishes the current draft attached to this review request. The review request will be mark as public, and signals will be emitted for any listeners. """ from reviewboard.accounts.models import LocalSiteProfile if not self.is_mutable_by(user): raise PermissionError draft = get_object_or_none(self.draft) review_request_publishing.send(sender=self.__class__, user=user, review_request_draft=draft) # Decrement the counts on everything. we lose them. # We'll increment the resulting set during ReviewRequest.save. # This should be done before the draft is published. # Once the draft is published, the target people # and groups will be updated with new values. # Decrement should not happen while publishing # a new request or a discarded request if self.public: Group.incoming_request_count.decrement(self.target_groups.all()) LocalSiteProfile.direct_incoming_request_count.decrement( LocalSiteProfile.objects.filter( user__in=self.target_people.all(), local_site=self.local_site)) LocalSiteProfile.total_incoming_request_count.decrement( LocalSiteProfile.objects.filter( Q(local_site=self.local_site) & Q( Q(user__review_groups__in=self.target_groups.all()) | Q(user__in=self.target_people.all())))) LocalSiteProfile.starred_public_request_count.decrement( LocalSiteProfile.objects.filter( profile__starred_review_requests=self, local_site=self.local_site)) if draft is not None: # This will in turn save the review request, so we'll be done. changes = draft.publish(self, send_notification=False) draft.delete() else: changes = None if not self.public and self.changedescs.count() == 0: # This is a brand new review request that we're publishing # for the first time. Set the creation timestamp to now. self.time_added = timezone.now() self.public = True self.save(update_counts=True) review_request_published.send(sender=self.__class__, user=user, review_request=self, changedesc=changes)
def publish(self, user): """Publishes the current draft attached to this review request. The review request will be mark as public, and signals will be emitted for any listeners. """ from reviewboard.accounts.models import LocalSiteProfile if not self.is_mutable_by(user): raise PermissionError draft = get_object_or_none(self.draft) review_request_publishing.send(sender=self.__class__, user=user, review_request_draft=draft) # Decrement the counts on everything. we lose them. # We'll increment the resulting set during ReviewRequest.save. # This should be done before the draft is published. # Once the draft is published, the target people # and groups will be updated with new values. # Decrement should not happen while publishing # a new request or a discarded request if self.public: Group.incoming_request_count.decrement(self.target_groups.all()) LocalSiteProfile.direct_incoming_request_count.decrement( LocalSiteProfile.objects.filter( user__in=self.target_people.all(), local_site=self.local_site)) LocalSiteProfile.total_incoming_request_count.decrement( LocalSiteProfile.objects.filter( Q(local_site=self.local_site) & Q(Q(user__review_groups__in=self.target_groups.all()) | Q(user__in=self.target_people.all())))) LocalSiteProfile.starred_public_request_count.decrement( LocalSiteProfile.objects.filter( profile__starred_review_requests=self, local_site=self.local_site)) if draft is not None: # This will in turn save the review request, so we'll be done. changes = draft.publish(self, send_notification=False) draft.delete() else: changes = None if not self.public and self.changedescs.count() == 0: # This is a brand new review request that we're publishing # for the first time. Set the creation timestamp to now. self.time_added = timezone.now() self.public = True self.save(update_counts=True) review_request_published.send(sender=self.__class__, user=user, review_request=self, changedesc=changes)
def publish(self, user, trivial=False): """Publishes the current draft attached to this review request. The review request will be mark as public, and signals will be emitted for any listeners. """ if not self.is_mutable_by(user): raise PermissionError draft = get_object_or_none(self.draft) old_submitter = self.submitter review_request_publishing.send(sender=self.__class__, user=user, review_request_draft=draft) # Decrement the counts on everything. we lose them. # We'll increment the resulting set during ReviewRequest.save. # This should be done before the draft is published. # Once the draft is published, the target people # and groups will be updated with new values. # Decrement should not happen while publishing # a new request or a discarded request if self.public: self._decrement_reviewer_counts() if draft is not None: # This will in turn save the review request, so we'll be done. try: changes = draft.publish(self, send_notification=False, user=user) except Exception: # The draft failed to publish, for one reason or another. # Check if we need to re-increment those counters we # previously decremented. if self.public: self._increment_reviewer_counts() raise draft.delete() else: changes = None if not self.public and self.changedescs.count() == 0: # This is a brand new review request that we're publishing # for the first time. Set the creation timestamp to now. self.time_added = timezone.now() self.public = True self.save(update_counts=True, old_submitter=old_submitter) review_request_published.send(sender=self.__class__, user=user, review_request=self, trivial=trivial, changedesc=changes)
def review_request_draft_publish(request, review_request_id): try: draft = ReviewRequestDraft.objects.get(review_request=review_request_id) review_request = draft.review_request except ReviewRequestDraft.DoesNotExist: return WebAPIResponseError(request, DOES_NOT_EXIST) if not review_request.is_mutable_by(request.user): return WebAPIResponseError(request, PERMISSION_DENIED) changes = draft.publish() draft.delete() review_request_published.send(sender=None, user=request.user, review_request=review_request, changedesc=changes) return WebAPIResponse(request)
def publish(self, user): """Publishes the current draft attached to this review request. The review request will be mark as public, and signals will be emitted for any listeners. """ if not self.is_mutable_by(user): raise PermissionError draft = get_object_or_none(self.draft) review_request_publishing.send(sender=self.__class__, user=user, review_request_draft=draft) # Decrement the counts on everything. we lose them. # We'll increment the resulting set during ReviewRequest.save. # This should be done before the draft is published. # Once the draft is published, the target people # and groups will be updated with new values. # Decrement should not happen while publishing # a new request or a discarded request if self.public: self._decrement_reviewer_counts() if draft is not None: # This will in turn save the review request, so we'll be done. changes = draft.publish(self, send_notification=False) draft.delete() else: changes = None if not self.public and self.changedescs.count() == 0: # This is a brand new review request that we're publishing # for the first time. Set the creation timestamp to now. self.time_added = timezone.now() # make a call to jira api to update status for the new request self.send_jira_transition('start') self.public = True self.save(update_counts=True) review_request_published.send(sender=self.__class__, user=user, review_request=self, changedesc=changes)
def publish(self, user): """ Save the current draft attached to this review request. Send out the associated email. Returns the review request that was saved. """ if not self.is_mutable_by(user): raise PermissionError draft = get_object_or_none(self.draft) if draft is not None: # This will in turn save the review request, so we'll be done. changes = draft.publish(self, send_notification=False) draft.delete() else: changes = None self.public = True self.save() review_request_published.send(sender=self.__class__, user=user, review_request=self, changedesc=changes)
def publish(self, review_request=None, user=None, send_notification=True): """Publishes this draft. This updates and returns the draft's ChangeDescription, which contains the changed fields. This is used by the e-mail template to tell people what's new and interesting. The draft's assocated ReviewRequest object will be used if one isn't passed in. The keys that may be saved in 'fields_changed' in the ChangeDescription are: * 'summary' * 'description' * 'testing_done' * 'bugs_closed' * 'depends_on' * 'branch' * 'target_groups' * 'target_people' * 'screenshots' * 'screenshot_captions' * 'diff' Each field in 'fields_changed' represents a changed field. This will save fields in the standard formats as defined by the 'ChangeDescription' documentation, with the exception of the 'screenshot_captions' and 'diff' fields. For the 'screenshot_captions' field, the value will be a dictionary of screenshot ID/dict pairs with the following fields: * 'old': The old value of the field * 'new': The new value of the field For the 'diff' field, there is only ever an 'added' field, containing the ID of the new diffset. The 'send_notification' parameter is intended for internal use only, and is there to prevent duplicate notifications when being called by ReviewRequest.publish. """ if not review_request: review_request = self.review_request if not user: user = review_request.submitter if not self.changedesc and review_request.public: self.changedesc = ChangeDescription() def update_list(a, b, name, record_changes=True, name_field=None): aset = set([x.id for x in a.all()]) bset = set([x.id for x in b.all()]) if aset.symmetric_difference(bset): if record_changes and self.changedesc: self.changedesc.record_field_change(name, a.all(), b.all(), name_field) a.clear() for item in b.all(): a.add(item) for field_cls in get_review_request_fields(): field = field_cls(review_request) if field.can_record_change_entry: old_value = field.load_value(review_request) new_value = field.load_value(self) if field.has_value_changed(old_value, new_value): field.save_value(new_value) if self.changedesc: field.record_change_entry(self.changedesc, old_value, new_value) # Screenshots are a bit special. The list of associated screenshots # can change, but so can captions within each screenshot. screenshots = list(self.screenshots.all()) caption_changes = {} for s in review_request.screenshots.all(): if s in screenshots and s.caption != s.draft_caption: caption_changes[s.id] = { 'old': (s.caption,), 'new': (s.draft_caption,), } s.caption = s.draft_caption s.save(update_fields=['caption']) # Now scan through again and set the caption correctly for newly-added # screenshots by copying the draft_caption over. We don't need to # include this in the changedescs here because it's a new screenshot, # and update_list will record the newly-added item. for s in screenshots: if s.caption != s.draft_caption: s.caption = s.draft_caption s.save(update_fields=['caption']) if caption_changes and self.changedesc: self.changedesc.fields_changed['screenshot_captions'] = \ caption_changes update_list(review_request.screenshots, self.screenshots, 'screenshots', name_field="caption") # There's no change notification required for this field. review_request.inactive_screenshots = self.inactive_screenshots.all() # Files are treated like screenshots. The list of files can # change, but so can captions within each file. files = list(self.file_attachments.all()) caption_changes = {} for f in review_request.file_attachments.all(): if f in files and f.caption != f.draft_caption: caption_changes[f.id] = { 'old': (f.caption,), 'new': (f.draft_caption,), } f.caption = f.draft_caption f.save(update_fields=['caption']) # Now scan through again and set the caption correctly for newly-added # files by copying the draft_caption over. We don't need to include # this in the changedescs here because it's a new screenshot, and # update_list will record the newly-added item. for f in files: if f.caption != f.draft_caption: f.caption = f.draft_caption f.save(update_fields=['caption']) if caption_changes and self.changedesc: self.changedesc.fields_changed['file_captions'] = caption_changes update_list(review_request.file_attachments, self.file_attachments, 'files', name_field="display_name") # There's no change notification required for this field. review_request.inactive_file_attachments = \ self.inactive_file_attachments.all() if self.diffset: self.diffset.history = review_request.diffset_history self.diffset.save(update_fields=['history']) # If no changes were made, raise exception and do not save if self.changedesc and not self.changedesc.has_modified_fields(): raise NotModifiedError() if self.changedesc: self.changedesc.timestamp = timezone.now() self.changedesc.rich_text = self.rich_text self.changedesc.public = True self.changedesc.save() review_request.changedescs.add(self.changedesc) review_request.rich_text = self.rich_text review_request.save() if send_notification: review_request_published.send(sender=review_request.__class__, user=user, review_request=review_request, changedesc=self.changedesc) return self.changedesc
def publish(self, review_request=None, user=None, send_notification=True): """ Publishes this draft. Uses the draft's assocated ReviewRequest object if one isn't passed in. This updates and returns the draft's ChangeDescription, which contains the changed fields. This is used by the e-mail template to tell people what's new and interesting. The keys that may be saved in 'fields_changed' in the ChangeDescription are: * 'summary' * 'description' * 'testing_done' * 'bugs_closed' * 'branch' * 'target_groups' * 'target_people' * 'screenshots' * 'screenshot_captions' * 'diff' Each field in 'fields_changed' represents a changed field. This will save fields in the standard formats as defined by the 'ChangeDescription' documentation, with the exception of the 'screenshot_captions' and 'diff' fields. For the 'screenshot_captions' field, the value will be a dictionary of screenshot ID/dict pairs with the following fields: * 'old': The old value of the field * 'new': The new value of the field For the 'diff' field, there is only ever an 'added' field, containing the ID of the new diffset. The 'send_notification' parameter is intended for internal use only, and is there to prevent duplicate notifications when being called by ReviewRequest.publish. """ if not review_request: review_request = self.review_request if not user: user = review_request.submitter if not self.changedesc and review_request.public: self.changedesc = ChangeDescription() def update_field(a, b, name, record_changes=True): # Apparently django models don't have __getattr__ or __setattr__, # so we have to update __dict__ directly. Sigh. value = b.__dict__[name] old_value = a.__dict__[name] if old_value != value: if record_changes and self.changedesc: self.changedesc.record_field_change(name, old_value, value) a.__dict__[name] = value def update_list(a, b, name, record_changes=True, name_field=None): aset = set([x.id for x in a.all()]) bset = set([x.id for x in b.all()]) if aset.symmetric_difference(bset): if record_changes and self.changedesc: self.changedesc.record_field_change(name, a.all(), b.all(), name_field) a.clear() map(a.add, b.all()) update_field(review_request, self, 'summary') update_field(review_request, self, 'description') update_field(review_request, self, 'testing_done') update_field(review_request, self, 'branch') update_list(review_request.target_groups, self.target_groups, 'target_groups', name_field="name") update_list(review_request.target_people, self.target_people, 'target_people', name_field="username") # Specifically handle bug numbers old_bugs = set(review_request.get_bug_list()) new_bugs = set(self.get_bug_list()) if old_bugs != new_bugs: update_field(review_request, self, 'bugs_closed', record_changes=False) if self.changedesc: self.changedesc.record_field_change('bugs_closed', old_bugs - new_bugs, new_bugs - old_bugs) # Screenshots are a bit special. The list of associated screenshots can # change, but so can captions within each screenshot. screenshots = self.screenshots.all() caption_changes = {} for s in review_request.screenshots.all(): if s in screenshots and s.caption != s.draft_caption: caption_changes[s.id] = { 'old': (s.caption,), 'new': (s.draft_caption,), } s.caption = s.draft_caption s.save() if caption_changes and self.changedesc: self.changedesc.fields_changed['screenshot_captions'] = \ caption_changes update_list(review_request.screenshots, self.screenshots, 'screenshots', name_field="caption") # There's no change notification required for this field. review_request.inactive_screenshots.clear() map(review_request.inactive_screenshots.add, self.inactive_screenshots.all()) if self.diffset: if self.changedesc: self.changedesc.fields_changed['diff'] = { 'added': [(_("Diff r%s") % self.diffset.revision, reverse("view_diff_revision", args=[review_request.id, self.diffset.revision]), self.diffset.id)], } self.diffset.history = review_request.diffset_history self.diffset.save() if self.changedesc: self.changedesc.timestamp = datetime.now() self.changedesc.public = True self.changedesc.save() review_request.changedescs.add(self.changedesc) review_request.save() if send_notification: review_request_published.send(sender=review_request.__class__, user=user, review_request=review_request, changedesc=self.changedesc) return self.changedesc
def publish(self, review_request=None, user=None, trivial=False, send_notification=True, validate_fields=True, timestamp=None): """Publish this draft. This is an internal method. Programmatic publishes should use :py:meth:`reviewboard.reviews.models.review_request.ReviewRequest.publish` instead. This updates and returns the draft's ChangeDescription, which contains the changed fields. This is used by the e-mail template to tell people what's new and interesting. The keys that may be saved in ``fields_changed`` in the ChangeDescription are: * ``submitter`` * ``summary`` * ``description`` * ``testing_done`` * ``bugs_closed`` * ``depends_on`` * ``branch`` * ``target_groups`` * ``target_people`` * ``screenshots`` * ``screenshot_captions`` * ``diff`` * Any custom field IDs Each field in 'fields_changed' represents a changed field. This will save fields in the standard formats as defined by the 'ChangeDescription' documentation, with the exception of the 'screenshot_captions' and 'diff' fields. For the 'screenshot_captions' field, the value will be a dictionary of screenshot ID/dict pairs with the following fields: * ``old``: The old value of the field * ``new``: The new value of the field For the ``diff`` field, there is only ever an ``added`` field, containing the ID of the new diffset. Args: review_request (reviewboard.reviews.models.review_request. ReviewRequest, optional): The review request associated with this diff. If not provided, it will be looked up. user (django.contrib.auth.models.User, optional): The user publishing the draft. If not provided, this defaults to the review request submitter. trivial (bool, optional): Whether or not this is a trivial publish. Trivial publishes do not result in e-mail notifications. send_notification (bool, optional): Whether or not this will emit the :py:data:`reviewboard.reviews.signals.review_request_published` signal. This parameter is intended for internal use **only**. validate_fields (bool, optional): Whether or not the fields should be validated. This should only be ``False`` in the case of programmatic publishes, e.g., from close as submitted hooks. timestamp (datetime.datetime, optional): The datetime that should be used for all timestamps for objects published (:py:class:`~reviewboard.diffviewer.models.diff_set.DiffSet`, :py:class:`~reviewboard.changedescs.models.ChangeDescription`) over the course of the method. Returns: reviewboard.changedescs.models.ChangeDescription: The change description that results from this publish (if any). If this is an initial publish, there will be no change description (and this function will return ``None``). """ if timestamp is None: timestamp = timezone.now() if not review_request: review_request = self.review_request if not self.changedesc and review_request.public: self.changedesc = ChangeDescription() if not user: if self.changedesc: user = self.changedesc.get_user(self) else: user = review_request.submitter self.copy_fields_to_request(review_request) # If no changes were made, raise exception and do not save if self.changedesc and not self.changedesc.has_modified_fields(): raise NotModifiedError() if validate_fields: if not (self.target_groups.exists() or self.target_people.exists()): raise PublishError( ugettext('There must be at least one reviewer before this ' 'review request can be published.')) if not review_request.summary.strip(): raise PublishError( ugettext('The draft must have a summary.')) if not review_request.description.strip(): raise PublishError( ugettext('The draft must have a description.')) if (review_request.created_with_history and self.diffset and self.diffset.commit_count == 0): raise PublishError( ugettext('There are no commits attached to the diff.')) if self.diffset: if (review_request.created_with_history and not self.diffset.is_commit_series_finalized): raise PublishError(ugettext( 'This commit series is not finalized.')) self.diffset.history = review_request.diffset_history self.diffset.timestamp = timestamp self.diffset.save(update_fields=('history', 'timestamp')) if self.changedesc: self.changedesc.user = user self.changedesc.timestamp = timestamp self.changedesc.public = True self.changedesc.save() review_request.changedescs.add(self.changedesc) review_request.description_rich_text = self.description_rich_text review_request.testing_done_rich_text = self.testing_done_rich_text review_request.rich_text = self.rich_text review_request.save() if send_notification: review_request_published.send(sender=type(review_request), user=user, review_request=review_request, trivial=trivial, changedesc=self.changedesc) return self.changedesc
def publish(self, review_request=None, user=None, trivial=False, send_notification=True): """Publishes this draft. This updates and returns the draft's ChangeDescription, which contains the changed fields. This is used by the e-mail template to tell people what's new and interesting. The draft's associated ReviewRequest object will be used if one isn't passed in. The keys that may be saved in ``fields_changed`` in the ChangeDescription are: * ``submitter`` * ``summary`` * ``description`` * ``testing_done`` * ``bugs_closed`` * ``depends_on`` * ``branch`` * ``target_groups`` * ``target_people`` * ``screenshots`` * ``screenshot_captions`` * ``diff`` * Any custom field IDs Each field in 'fields_changed' represents a changed field. This will save fields in the standard formats as defined by the 'ChangeDescription' documentation, with the exception of the 'screenshot_captions' and 'diff' fields. For the 'screenshot_captions' field, the value will be a dictionary of screenshot ID/dict pairs with the following fields: * ``old``: The old value of the field * ``new``: The new value of the field For the ``diff`` field, there is only ever an ``added`` field, containing the ID of the new diffset. The ``send_notification`` parameter is intended for internal use only, and is there to prevent duplicate notifications when being called by ReviewRequest.publish. """ if not review_request: review_request = self.review_request if not self.changedesc and review_request.public: self.changedesc = ChangeDescription() if not user: if self.changedesc: user = self.changedesc.get_user(self) else: user = review_request.submitter self.copy_fields_to_request(review_request) if self.diffset: self.diffset.history = review_request.diffset_history self.diffset.save(update_fields=['history']) # If no changes were made, raise exception and do not save if self.changedesc and not self.changedesc.has_modified_fields(): raise NotModifiedError() if self.changedesc: self.changedesc.user = user self.changedesc.timestamp = timezone.now() self.changedesc.public = True self.changedesc.save() review_request.changedescs.add(self.changedesc) review_request.description_rich_text = self.description_rich_text review_request.testing_done_rich_text = self.testing_done_rich_text review_request.rich_text = self.rich_text review_request.save() if send_notification: review_request_published.send(sender=review_request.__class__, user=user, review_request=review_request, trivial=trivial, changedesc=self.changedesc) return self.changedesc
def publish(self, review_request=None, user=None, trivial=False, send_notification=True, validate_fields=True, timestamp=None): """Publish this draft. This is an internal method. Programmatic publishes should use :py:meth:`reviewboard.reviews.models.review_request.ReviewRequest.publish` instead. This updates and returns the draft's ChangeDescription, which contains the changed fields. This is used by the e-mail template to tell people what's new and interesting. The keys that may be saved in ``fields_changed`` in the ChangeDescription are: * ``submitter`` * ``summary`` * ``description`` * ``testing_done`` * ``bugs_closed`` * ``depends_on`` * ``branch`` * ``target_groups`` * ``target_people`` * ``screenshots`` * ``screenshot_captions`` * ``diff`` * Any custom field IDs Each field in 'fields_changed' represents a changed field. This will save fields in the standard formats as defined by the 'ChangeDescription' documentation, with the exception of the 'screenshot_captions' and 'diff' fields. For the 'screenshot_captions' field, the value will be a dictionary of screenshot ID/dict pairs with the following fields: * ``old``: The old value of the field * ``new``: The new value of the field For the ``diff`` field, there is only ever an ``added`` field, containing the ID of the new diffset. Args: review_request (reviewboard.reviews.models.review_request. ReviewRequest, optional): The review request associated with this diff. If not provided, it will be looked up. user (django.contrib.auth.models.User, optional): The user publishing the draft. If not provided, this defaults to the review request submitter. trivial (bool, optional): Whether or not this is a trivial publish. Trivial publishes do not result in e-mail notifications. send_notification (bool, optional): Whether or not this will emit the :py:data:`reviewboard.reviews.signals.review_request_published` signal. This parameter is intended for internal use **only**. validate_fields (bool, optional): Whether or not the fields should be validated. This should only be ``False`` in the case of programmatic publishes, e.g., from close as submitted hooks. timestamp (datetime.datetime, optional): The datetime that should be used for all timestamps for objects published (:py:class:`~reviewboard.diffviewer.models.diff_set.DiffSet`, :py:class:`~reviewboard.changedescs.models.ChangeDescription`) over the course of the method. Returns: reviewboard.changedescs.models.ChangeDescription: The change description that results from this publish (if any). If this is an initial publish, there will be no change description (and this function will return ``None``). """ if timestamp is None: timestamp = timezone.now() if not review_request: review_request = self.review_request if not self.changedesc and review_request.public: self.changedesc = ChangeDescription() if not user: if self.changedesc: user = self.changedesc.get_user(self) else: user = review_request.submitter self.copy_fields_to_request(review_request) # If no changes were made, raise exception and do not save if self.changedesc and not self.changedesc.has_modified_fields(): raise NotModifiedError() if validate_fields: if not (self.target_groups.exists() or self.target_people.exists()): raise PublishError( ugettext('There must be at least one reviewer before this ' 'review request can be published.')) if not review_request.summary.strip(): raise PublishError( ugettext('The draft must have a summary.')) if not review_request.description.strip(): raise PublishError( ugettext('The draft must have a description.')) if self.diffset: self.diffset.history = review_request.diffset_history self.diffset.timestamp = timestamp self.diffset.save(update_fields=('history', 'timestamp')) if self.changedesc: self.changedesc.user = user self.changedesc.timestamp = timestamp self.changedesc.public = True self.changedesc.save() review_request.changedescs.add(self.changedesc) review_request.description_rich_text = self.description_rich_text review_request.testing_done_rich_text = self.testing_done_rich_text review_request.rich_text = self.rich_text review_request.save() if send_notification: review_request_published.send(sender=type(review_request), user=user, review_request=review_request, trivial=trivial, changedesc=self.changedesc) return self.changedesc
def publish(self, review_request=None, user=None, send_notification=True): """Publishes this draft. This updates and returns the draft's ChangeDescription, which contains the changed fields. This is used by the e-mail template to tell people what's new and interesting. The draft's assocated ReviewRequest object will be used if one isn't passed in. The keys that may be saved in 'fields_changed' in the ChangeDescription are: * 'summary' * 'description' * 'testing_done' * 'bugs_closed' * 'depends_on' * 'branch' * 'target_groups' * 'target_people' * 'screenshots' * 'screenshot_captions' * 'diff' Each field in 'fields_changed' represents a changed field. This will save fields in the standard formats as defined by the 'ChangeDescription' documentation, with the exception of the 'screenshot_captions' and 'diff' fields. For the 'screenshot_captions' field, the value will be a dictionary of screenshot ID/dict pairs with the following fields: * 'old': The old value of the field * 'new': The new value of the field For the 'diff' field, there is only ever an 'added' field, containing the ID of the new diffset. The 'send_notification' parameter is intended for internal use only, and is there to prevent duplicate notifications when being called by ReviewRequest.publish. """ if not review_request: review_request = self.review_request if not user: user = review_request.submitter if not self.changedesc and review_request.public: self.changedesc = ChangeDescription() def update_list(a, b, name, record_changes=True, name_field=None): aset = set([x.id for x in a.all()]) bset = set([x.id for x in b.all()]) if aset.symmetric_difference(bset): if record_changes and self.changedesc: self.changedesc.record_field_change( name, a.all(), b.all(), name_field) a.clear() for item in b.all(): a.add(item) for field_cls in get_review_request_fields(): field = field_cls(review_request) if field.can_record_change_entry: old_value = field.load_value(review_request) new_value = field.load_value(self) if field.has_value_changed(old_value, new_value): field.save_value(new_value) if self.changedesc: field.record_change_entry(self.changedesc, old_value, new_value) # Screenshots are a bit special. The list of associated screenshots # can change, but so can captions within each screenshot. screenshots = list(self.screenshots.all()) caption_changes = {} for s in review_request.screenshots.all(): if s in screenshots and s.caption != s.draft_caption: caption_changes[s.id] = { 'old': (s.caption, ), 'new': (s.draft_caption, ), } s.caption = s.draft_caption s.save(update_fields=['caption']) # Now scan through again and set the caption correctly for newly-added # screenshots by copying the draft_caption over. We don't need to # include this in the changedescs here because it's a new screenshot, # and update_list will record the newly-added item. for s in screenshots: if s.caption != s.draft_caption: s.caption = s.draft_caption s.save(update_fields=['caption']) if caption_changes and self.changedesc: self.changedesc.fields_changed['screenshot_captions'] = \ caption_changes update_list(review_request.screenshots, self.screenshots, 'screenshots', name_field="caption") # There's no change notification required for this field. review_request.inactive_screenshots = self.inactive_screenshots.all() # Files are treated like screenshots. The list of files can # change, but so can captions within each file. files = list(self.file_attachments.all()) caption_changes = {} for f in review_request.file_attachments.all(): if f in files and f.caption != f.draft_caption: caption_changes[f.id] = { 'old': (f.caption, ), 'new': (f.draft_caption, ), } f.caption = f.draft_caption f.save(update_fields=['caption']) # Now scan through again and set the caption correctly for newly-added # files by copying the draft_caption over. We don't need to include # this in the changedescs here because it's a new screenshot, and # update_list will record the newly-added item. for f in files: if f.caption != f.draft_caption: f.caption = f.draft_caption f.save(update_fields=['caption']) if caption_changes and self.changedesc: self.changedesc.fields_changed['file_captions'] = caption_changes update_list(review_request.file_attachments, self.file_attachments, 'files', name_field="display_name") # There's no change notification required for this field. review_request.inactive_file_attachments = \ self.inactive_file_attachments.all() if self.diffset: self.diffset.history = review_request.diffset_history self.diffset.save(update_fields=['history']) if self.changedesc: self.changedesc.timestamp = timezone.now() self.changedesc.rich_text = self.rich_text self.changedesc.public = True self.changedesc.save() review_request.changedescs.add(self.changedesc) review_request.rich_text = self.rich_text review_request.save() if send_notification: review_request_published.send(sender=review_request.__class__, user=user, review_request=review_request, changedesc=self.changedesc) return self.changedesc
def publish(self, review_request=None, user=None, trivial=False, send_notification=True): """Publishes this draft. This updates and returns the draft's ChangeDescription, which contains the changed fields. This is used by the e-mail template to tell people what's new and interesting. The draft's assocated ReviewRequest object will be used if one isn't passed in. The keys that may be saved in ``fields_changed`` in the ChangeDescription are: * ``summary`` * ``description`` * ``testing_done`` * ``bugs_closed`` * ``depends_on`` * ``branch`` * ``target_groups`` * ``target_people`` * ``screenshots`` * ``screenshot_captions`` * ``diff`` Each field in 'fields_changed' represents a changed field. This will save fields in the standard formats as defined by the 'ChangeDescription' documentation, with the exception of the 'screenshot_captions' and 'diff' fields. For the 'screenshot_captions' field, the value will be a dictionary of screenshot ID/dict pairs with the following fields: * ``old``: The old value of the field * ``new``: The new value of the field For the ``diff`` field, there is only ever an ``added`` field, containing the ID of the new diffset. The ``send_notification`` parameter is intended for internal use only, and is there to prevent duplicate notifications when being called by ReviewRequest.publish. """ if not review_request: review_request = self.review_request if not user: user = review_request.submitter if not self.changedesc and review_request.public: self.changedesc = ChangeDescription() self.copy_fields_to_request(review_request) if self.diffset: self.diffset.history = review_request.diffset_history self.diffset.save(update_fields=['history']) # If no changes were made, raise exception and do not save if self.changedesc and not self.changedesc.has_modified_fields(): raise NotModifiedError() if self.changedesc: self.changedesc.timestamp = timezone.now() self.changedesc.public = True self.changedesc.save() review_request.changedescs.add(self.changedesc) review_request.description_rich_text = self.description_rich_text review_request.testing_done_rich_text = self.testing_done_rich_text review_request.rich_text = self.rich_text review_request.save() if send_notification: review_request_published.send(sender=review_request.__class__, user=user, review_request=review_request, trivial=trivial, changedesc=self.changedesc) return self.changedesc
def publish(self, review_request=None, user=None, send_notification=True): """ Publishes this draft. Uses the draft's assocated ReviewRequest object if one isn't passed in. This updates and returns the draft's ChangeDescription, which contains the changed fields. This is used by the e-mail template to tell people what's new and interesting. The keys that may be saved in 'fields_changed' in the ChangeDescription are: * 'summary' * 'description' * 'testing_done' * 'bugs_closed' * 'branch' * 'target_groups' * 'target_people' * 'screenshots' * 'screenshot_captions' * 'diff' Each field in 'fields_changed' represents a changed field. This will save fields in the standard formats as defined by the 'ChangeDescription' documentation, with the exception of the 'screenshot_captions' and 'diff' fields. For the 'screenshot_captions' field, the value will be a dictionary of screenshot ID/dict pairs with the following fields: * 'old': The old value of the field * 'new': The new value of the field For the 'diff' field, there is only ever an 'added' field, containing the ID of the new diffset. The 'send_notification' parameter is intended for internal use only, and is there to prevent duplicate notifications when being called by ReviewRequest.publish. """ from reviewboard.accounts.models import LocalSiteProfile if not review_request: review_request = self.review_request if not user: user = review_request.submitter if not self.changedesc and review_request.public: self.changedesc = ChangeDescription() def update_field(a, b, name, record_changes=True): # Apparently django models don't have __getattr__ or __setattr__, # so we have to update __dict__ directly. Sigh. value = b.__dict__[name] old_value = a.__dict__[name] if old_value != value: if record_changes and self.changedesc: self.changedesc.record_field_change(name, old_value, value) a.__dict__[name] = value def update_list(a, b, name, record_changes=True, name_field=None, counter_infos=[]): aset = set([x.id for x in a.all()]) bset = set([x.id for x in b.all()]) if aset.symmetric_difference(bset): if record_changes and self.changedesc: self.changedesc.record_field_change(name, a.all(), b.all(), name_field) a.clear() map(a.add, b.all()) # Decrement the counts on everything we had before. # we lose them. We'll increment the resulting set # during ReviewRequest.save. for model, counter, pk_field in counter_infos: counter.decrement( model.objects.filter(**{pk_field + "__in": aset, "local_site": review_request.local_site}) ) update_field(review_request, self, "summary") update_field(review_request, self, "description") update_field(review_request, self, "testing_done") update_field(review_request, self, "branch") update_list( review_request.target_groups, self.target_groups, "target_groups", name_field="name", counter_infos=[ (Group, Group.incoming_request_count, "pk"), (LocalSiteProfile, LocalSiteProfile.total_incoming_request_count, "user__review_groups"), ], ) update_list( review_request.target_people, self.target_people, "target_people", name_field="username", counter_infos=[ (LocalSiteProfile, LocalSiteProfile.direct_incoming_request_count, "user"), (LocalSiteProfile, LocalSiteProfile.total_incoming_request_count, "user"), ], ) # Specifically handle bug numbers old_bugs = set(review_request.get_bug_list()) new_bugs = set(self.get_bug_list()) if old_bugs != new_bugs: update_field(review_request, self, "bugs_closed", record_changes=False) if self.changedesc: self.changedesc.record_field_change("bugs_closed", old_bugs - new_bugs, new_bugs - old_bugs) # Screenshots are a bit special. The list of associated screenshots can # change, but so can captions within each screenshot. screenshots = self.screenshots.all() caption_changes = {} for s in review_request.screenshots.all(): if s in screenshots and s.caption != s.draft_caption: caption_changes[s.id] = {"old": (s.caption,), "new": (s.draft_caption,)} s.caption = s.draft_caption s.save() if caption_changes and self.changedesc: self.changedesc.fields_changed["screenshot_captions"] = caption_changes update_list(review_request.screenshots, self.screenshots, "screenshots", name_field="caption") # There's no change notification required for this field. review_request.inactive_screenshots.clear() map(review_request.inactive_screenshots.add, self.inactive_screenshots.all()) if self.diffset: if self.changedesc: if review_request.local_site: local_site_name = review_request.local_site.name else: local_site_name = None url = local_site_reverse( "view_diff_revision", local_site_name=local_site_name, args=[review_request.display_id, self.diffset.revision], ) self.changedesc.fields_changed["diff"] = { "added": [(_("Diff r%s") % self.diffset.revision, url, self.diffset.id)] } self.diffset.history = review_request.diffset_history self.diffset.save() if self.changedesc: self.changedesc.timestamp = datetime.now() self.changedesc.public = True self.changedesc.save() review_request.changedescs.add(self.changedesc) review_request.save() if send_notification: review_request_published.send( sender=review_request.__class__, user=user, review_request=review_request, changedesc=self.changedesc ) return self.changedesc