Пример #1
0
    def test_get_with_site(self):
        """Testing the GET review-requests/<id>/changes/ API
        with access to a local site
        """
        review_request = self.create_review_request(publish=True,
                                                    with_local_site=True)

        self._login_user(local_site=True)

        now = timezone.now()
        change1 = ChangeDescription(public=True, timestamp=now)
        change1.record_field_change('summary', 'foo', 'bar')
        change1.save()
        review_request.changedescs.add(change1)

        change2 = ChangeDescription(public=True,
                                    timestamp=now + timedelta(seconds=1))
        change2.record_field_change('description', 'foo', 'bar')
        change2.save()
        review_request.changedescs.add(change2)

        rsp = self.api_get(get_change_list_url(review_request,
                                               self.local_site_name),
                           expected_mimetype=change_list_mimetype)
        self.assertEqual(rsp['stat'], 'ok')
        self.assertEqual(len(rsp['changes']), 2)

        self.assertEqual(rsp['changes'][0]['id'], change2.pk)
        self.assertEqual(rsp['changes'][1]['id'], change1.pk)
Пример #2
0
    def reopen(self, user=None):
        """Reopens the review request for review."""
        from reviewboard.reviews.models.review_request_draft import \
            ReviewRequestDraft

        if (user and not self.is_mutable_by(user) and
            not user.has_perm("reviews.can_change_status", self.local_site)):
            raise PermissionError

        if self.status != self.PENDING_REVIEW:
            changedesc = ChangeDescription()
            changedesc.record_field_change('status', self.status,
                                           self.PENDING_REVIEW)

            if self.status == self.DISCARDED:
                # A draft is needed if reopening a discarded review request.
                self.public = False
                changedesc.save()
                draft = ReviewRequestDraft.create(self)
                draft.changedesc = changedesc
                draft.save()
            else:
                changedesc.public = True
                changedesc.save()
                self.changedescs.add(changedesc)

            self.status = self.PENDING_REVIEW
            self.save(update_counts=True)

        review_request_reopened.send(sender=self.__class__, user=user,
                                     review_request=self)
Пример #3
0
 def test_record_list_mismatch_type(self):
     """Testing ChangeDescription.record_field_change with
     mismatched types
     """
     changedesc = ChangeDescription()
     self.assertRaises(ValueError, changedesc.record_field_change, "test",
                       123, True)
Пример #4
0
    def close(self, type, user=None, description=None, rich_text=False):
        """Closes the review request.

        The type must be one of SUBMITTED or DISCARDED.
        """
        if (user and not self.is_mutable_by(user) and
            not user.has_perm("reviews.can_change_status", self.local_site)):
            raise PermissionError

        if type not in [self.SUBMITTED, self.DISCARDED]:
            raise AttributeError("%s is not a valid close type" % type)

        draft = get_object_or_none(self.draft)

        if self.status != type:
            if (draft is not None and
                not self.public and type == self.DISCARDED):
                # Copy over the draft information if this is a private discard.
                draft.copy_fields_to_request(self)

            # TODO: Use the user's default for rich_text.
            changedesc = ChangeDescription(public=True,
                                           text=description or "",
                                           rich_text=rich_text or False)

            status_field = get_review_request_field('status')(self)
            status_field.record_change_entry(changedesc, self.status, type)
            changedesc.save()

            self.changedescs.add(changedesc)

            if type == self.SUBMITTED:
                if not self.public:
                    raise PublishError("The draft must be public first.")
            else:
                self.commit_id = None

            self.status = type
            self.save(update_counts=True)

            review_request_closed.send(sender=self.__class__, user=user,
                                       review_request=self,
                                       type=type)
        else:
            # Update submission description.
            changedesc = self.changedescs.filter(public=True).latest()
            changedesc.timestamp = timezone.now()
            changedesc.text = description or ""
            changedesc.rich_text = rich_text
            changedesc.save()

            # Needed to renew last-update.
            self.save()

        # Delete the associated draft review request.
        if draft is not None:
            draft.delete()
Пример #5
0
    def test_get_change_not_modified(self):
        """Testing the GET review-requests/<id>/changes/<id>/ API with Not Modified response"""
        review_request = self.create_review_request()

        changedesc = ChangeDescription(public=True)
        changedesc.save()
        review_request.changedescs.add(changedesc)

        self._testHttpCaching(get_change_item_url(changedesc),
                              check_last_modified=True)
Пример #6
0
    def test_is_new_for_user_with_owner(self):
        """Testing ChangeDescription.is_new_for_user with owner"""
        user = User.objects.create(username='******')

        changedesc = ChangeDescription(
            user=user,
            timestamp=datetime(2017, 9, 7, 15, 27, 0))
        self.assertFalse(changedesc.is_new_for_user(
            user=user,
            last_visited=datetime(2017, 9, 7, 16, 0, 0)))
Пример #7
0
    def test_determine_user_for_review_request(self):
        """Testing ChangeDescription.get_user for change descriptions for
        review requests
        """
        review_request = self.create_review_request(publish=True)
        doc = review_request.submitter
        grumpy = User.objects.get(username='******')

        change1 = ChangeDescription()
        change1.record_field_change('foo', ['bar'], ['baz'])
        change1.save()
        review_request.changedescs.add(change1)

        change2 = ChangeDescription()
        change2.record_field_change('submitter', doc, grumpy, 'username')
        change2.save()
        review_request.changedescs.add(change2)

        change3 = ChangeDescription()
        change3.record_field_change('foo', ['bar'], ['baz'])
        change3.save()
        review_request.changedescs.add(change3)

        change4 = ChangeDescription()
        change4.record_field_change('submitter', grumpy, doc, 'username')
        change4.save()
        review_request.changedescs.add(change4)

        self.assertIsNone(change1.user)
        self.assertIsNone(change2.user)
        self.assertIsNone(change3.user)
        self.assertIsNone(change4.user)

        self.assertEqual(change1.get_user(review_request), doc)
        self.assertEqual(change2.get_user(review_request), doc)
        self.assertEqual(change3.get_user(review_request), grumpy)
        self.assertEqual(change4.get_user(review_request), grumpy)

        self.assertEqual(change1.user, doc)
        self.assertEqual(change2.user, doc)
        self.assertEqual(change3.user, grumpy)
        self.assertEqual(change4.user, grumpy)
Пример #8
0
    def close(self, type, user=None, description=None, rich_text=False):
        """Closes the review request.

        The type must be one of SUBMITTED or DISCARDED.
        """
        if (user and not self.is_mutable_by(user) and not user.has_perm(
                "reviews.can_change_status", self.local_site)):
            raise PermissionError

        if type not in [self.SUBMITTED, self.DISCARDED]:
            raise AttributeError("%s is not a valid close type" % type)

        if self.status != type:
            changedesc = ChangeDescription(public=True,
                                           text=description or "",
                                           rich_text=rich_text)
            changedesc.record_field_change('status', self.status, type)
            changedesc.save()

            self.changedescs.add(changedesc)

            if type == self.SUBMITTED:
                self.public = True
            else:
                self.commit_id = None

            self.status = type
            self.save(update_counts=True)

            review_request_closed.send(sender=self.__class__,
                                       user=user,
                                       review_request=self,
                                       type=type)
        else:
            # Update submission description.
            changedesc = self.changedescs.filter(public=True).latest()
            changedesc.timestamp = timezone.now()
            changedesc.text = description or ""
            changedesc.save()

            # Needed to renew last-update.
            self.save()

        try:
            draft = self.draft.get()
        except ObjectDoesNotExist:
            pass
        else:
            draft.delete()
Пример #9
0
    def test_get_with_site_no_access(self):
        """Testing the GET review-requests/<id>/changes/<id>/ API
        without access to a local site
        """
        review_request = self.create_review_request(publish=True,
                                                    with_local_site=True)

        now = timezone.now()
        change = ChangeDescription(public=True, timestamp=now)
        change.record_field_change('summary', 'foo', 'bar')
        change.save()
        review_request.changedescs.add(change)

        rsp = self.api_get(get_change_item_url(change, self.local_site_name),
                           expected_status=403)
        self.assertEqual(rsp['stat'], 'fail')
        self.assertEqual(rsp['err']['code'], PERMISSION_DENIED.code)
Пример #10
0
    def testRecordString(self):
        """Testing record_field_change with a string value"""
        old_value = "abc"
        new_value = "def"

        changedesc = ChangeDescription()
        changedesc.record_field_change("test", old_value, new_value)

        self.assertTrue("test" in changedesc.fields_changed)
        self.assertTrue("old" in changedesc.fields_changed["test"])
        self.assertTrue("new" in changedesc.fields_changed["test"])
        self.assertTrue("added" not in changedesc.fields_changed["test"])
        self.assertTrue("removed" not in changedesc.fields_changed["test"])
        self.assertEqual(changedesc.fields_changed["test"]["old"],
                         (old_value,))
        self.assertEqual(changedesc.fields_changed["test"]["new"],
                         (new_value,))
Пример #11
0
    def test_record_string(self):
        """Testing ChangeDescription.record_field_change with a string value"""
        old_value = "abc"
        new_value = "def"

        changedesc = ChangeDescription()
        changedesc.record_field_change("test", old_value, new_value)

        self.assertIn("test", changedesc.fields_changed)
        self.assertIn("old", changedesc.fields_changed["test"])
        self.assertIn("new", changedesc.fields_changed["test"])
        self.assertNotIn("added", changedesc.fields_changed["test"])
        self.assertNotIn("removed", changedesc.fields_changed["test"])
        self.assertEqual(changedesc.fields_changed["test"]["old"],
                         (old_value,))
        self.assertEqual(changedesc.fields_changed["test"]["new"],
                         (new_value,))
Пример #12
0
    def reopen(self, user=None):
        """Reopens the review request for review."""
        from reviewboard.reviews.models.review_request_draft import \
            ReviewRequestDraft

        if (user and not self.is_mutable_by(user) and not user.has_perm(
                "reviews.can_change_status", self.local_site)):
            raise PermissionError

        old_status = self.status
        old_public = self.public

        if old_status != self.PENDING_REVIEW:
            # The reopening signal is only fired when actually making a status
            # change since the main consumers (extensions) probably only care
            # about changes.
            review_request_reopening.send(sender=self.__class__,
                                          user=user,
                                          review_request=self)

            changedesc = ChangeDescription(user=user or self.submitter)
            status_field = get_review_request_field('status')(self)
            status_field.record_change_entry(changedesc, old_status,
                                             self.PENDING_REVIEW)

            if old_status == self.DISCARDED:
                # A draft is needed if reopening a discarded review request.
                self.public = False
                changedesc.save()
                draft = ReviewRequestDraft.create(self)
                draft.changedesc = changedesc
                draft.save()
            else:
                changedesc.public = True
                changedesc.save()
                self.changedescs.add(changedesc)

            self.status = self.PENDING_REVIEW
            self.save(update_counts=True)

        review_request_reopened.send(sender=self.__class__,
                                     user=user,
                                     review_request=self,
                                     old_status=old_status,
                                     old_public=old_public)
Пример #13
0
    def test_get_with_site(self):
        """Testing the GET review-requests/<id>/changes/<id>/ API
        with access to a local site
        """
        review_request = self.create_review_request(publish=True,
                                                    with_local_site=True)

        self._login_user(local_site=True)

        now = timezone.now()
        change = ChangeDescription(public=True, timestamp=now)
        change.record_field_change('summary', 'foo', 'bar')
        change.save()
        review_request.changedescs.add(change)

        rsp = self.api_get(get_change_item_url(change, self.local_site_name),
                           expected_mimetype=change_item_mimetype)
        self.assertEqual(rsp['stat'], 'ok')
        self.assertEqual(rsp['change']['id'], change.pk)
Пример #14
0
    def testRecordList(self):
        """Testing record_field_change with a list value"""
        old_value = [1, 2, 3]
        new_value = [2, 3, 4]

        changedesc = ChangeDescription()
        changedesc.record_field_change("test", old_value, new_value)

        self.assertTrue("test" in changedesc.fields_changed)
        self.assertTrue("old" in changedesc.fields_changed["test"])
        self.assertTrue("new" in changedesc.fields_changed["test"])
        self.assertTrue("added" in changedesc.fields_changed["test"])
        self.assertTrue("removed" in changedesc.fields_changed["test"])
        self.assertEqual(changedesc.fields_changed["test"]["old"],
                         [(i,) for i in old_value])
        self.assertEqual(changedesc.fields_changed["test"]["new"],
                         [(i,) for i in new_value])
        self.assertEqual(changedesc.fields_changed["test"]["added"], [(4,)])
        self.assertEqual(changedesc.fields_changed["test"]["removed"], [(1,)])
Пример #15
0
    def test_is_new_for_user_with_non_owner(self):
        """Testing ChangeDescription.is_new_for_user with non-owner"""
        user1 = User.objects.create_user(username='******',
                                         email='*****@*****.**')
        user2 = User.objects.create_user(username='******',
                                         email='*****@*****.**')

        changedesc = ChangeDescription(
            user=user1,
            timestamp=datetime(2017, 9, 7, 15, 27, 0))
        self.assertTrue(changedesc.is_new_for_user(
            user=user2,
            last_visited=datetime(2017, 9, 7, 10, 0, 0)))
        self.assertFalse(changedesc.is_new_for_user(
            user=user2,
            last_visited=datetime(2017, 9, 7, 16, 0, 0)))
        self.assertFalse(changedesc.is_new_for_user(
            user=user2,
            last_visited=datetime(2017, 9, 7, 15, 27, 0)))
Пример #16
0
    def test_record_object_list_name_field(self):
        """Testing ChangeDescription.record_field_change with an object list
        (using name_field)
        """
        class DummyObject(object):
            def __init__(self, id):
                self.id = id
                self.text = "Object %s" % id

            def get_absolute_url(self):
                return "http://localhost/%s" % self.id

        objs = [DummyObject(i) for i in range(4)]
        old_value = [objs[0], objs[1], objs[2]]
        new_value = [objs[1], objs[2], objs[3]]

        changedesc = ChangeDescription()
        changedesc.record_field_change("test", old_value, new_value, "text")

        self.assertIn("test", changedesc.fields_changed)
        self.assertIn("old", changedesc.fields_changed["test"])
        self.assertIn("new", changedesc.fields_changed["test"])
        self.assertIn("added", changedesc.fields_changed["test"])
        self.assertIn("removed", changedesc.fields_changed["test"])
        self.assertEqual(
            set(changedesc.fields_changed["test"]["old"]),
            set([(obj.text, obj.get_absolute_url(), obj.id)
                 for obj in old_value]))
        self.assertEqual(
            set(changedesc.fields_changed["test"]["new"]),
            set([(obj.text, obj.get_absolute_url(), obj.id)
                 for obj in new_value]))
        self.assertEqual(
            set(changedesc.fields_changed["test"]["added"]),
            set([(new_value[2].text, new_value[2].get_absolute_url(),
                  new_value[2].id)]))
        self.assertEqual(
            set(changedesc.fields_changed["test"]["removed"]),
            set([(old_value[0].text, old_value[0].get_absolute_url(),
                  old_value[0].id)]))
Пример #17
0
    def save_autolandrequest_id(self, fieldname, rr, autoland_request_id):
        # TODO: this method is only required while we are using change
        #       descriptions to render autoland results. Once Bug 1176330 is
        #       fixed this code can be removed.

        # There's possibly a race condition here with multiple web-heads. If
        # two requests come in at the same time to this endpoint, the request
        # that saves their value first here will get overwritten by the second
        # but the first request will have their changedescription come below
        # the second. In that case you'd have the "most recent" try build stats
        # appearing at the top be for a changedescription that has a different
        # try build below it (Super rare, not a big deal really).
        old_request_id = rr.extra_data.get(fieldname, None)
        rr.extra_data[fieldname] = autoland_request_id
        rr.save()

        # In order to display the fact that a build was kicked off in the UI,
        # we construct a change description that our TryField can render.
        changedesc = ChangeDescription(public=True, text='', rich_text=False)
        changedesc.record_field_change(fieldname, old_request_id,
                                       autoland_request_id)
        changedesc.save()
        rr.changedescs.add(changedesc)
Пример #18
0
    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
Пример #19
0
    def close(self,
              close_type=None,
              user=None,
              description=None,
              rich_text=False,
              **kwargs):
        """Closes the review request.

        Args:
            close_type (unicode):
                How the close occurs. This should be one of
                :py:attr:`SUBMITTED` or :py:attr:`DISCARDED`.

            user (django.contrib.auth.models.User):
                The user who is closing the review request.

            description (unicode):
                An optional description that indicates why the review request
                was closed.

            rich_text (bool):
                Indicates whether or not that the description is rich text.

        Raises:
            ValueError:
                The provided close type is not a valid value.

            PermissionError:
                The user does not have permission to close the review request.

            TypeError:
                Keyword arguments were supplied to the function.

        .. versionchanged:: 3.0
           The ``type`` argument is deprecated: ``close_type`` should be used
           instead.

           This method raises :py:exc:`ValueError` instead of
           :py:exc:`AttributeError` when the ``close_type`` has an incorrect
           value.
        """
        if close_type is None:
            try:
                close_type = kwargs.pop('type')
            except KeyError:
                raise AttributeError('close_type must be provided')

            warnings.warn(
                'The "type" argument was deprecated in Review Board 3.0 and '
                'will be removed in a future version. Use "close_type" '
                'instead.')

        if kwargs:
            raise TypeError('close() does not accept keyword arguments.')

        if (user and not self.is_mutable_by(user) and not user.has_perm(
                "reviews.can_change_status", self.local_site)):
            raise PermissionError

        if close_type not in [self.SUBMITTED, self.DISCARDED]:
            raise ValueError("%s is not a valid close type" % type)

        review_request_closing.send(sender=type(self),
                                    user=user,
                                    review_request=self,
                                    close_type=close_type,
                                    type=deprecated_signal_argument(
                                        signal_name='review_request_closing',
                                        old_name='type',
                                        new_name='close_type',
                                        value=close_type),
                                    description=description,
                                    rich_text=rich_text)

        draft = get_object_or_none(self.draft)

        if self.status != close_type:
            if (draft is not None and not self.public
                    and close_type == self.DISCARDED):
                # Copy over the draft information if this is a private discard.
                draft.copy_fields_to_request(self)

            # TODO: Use the user's default for rich_text.
            changedesc = ChangeDescription(public=True,
                                           text=description or "",
                                           rich_text=rich_text or False,
                                           user=user or self.submitter)

            status_field = get_review_request_field('status')(self)
            status_field.record_change_entry(changedesc, self.status,
                                             close_type)
            changedesc.save()

            self.changedescs.add(changedesc)

            if close_type == self.SUBMITTED:
                if not self.public:
                    raise PublishError("The draft must be public first.")
            else:
                self.commit_id = None

            self.status = close_type
            self.save(update_counts=True)

            review_request_closed.send(sender=type(self),
                                       user=user,
                                       review_request=self,
                                       close_type=close_type,
                                       type=deprecated_signal_argument(
                                           signal_name='review_request_closed',
                                           old_name='type',
                                           new_name='close_type',
                                           value=close_type),
                                       description=description,
                                       rich_text=rich_text)
        else:
            # Update submission description.
            changedesc = self.changedescs.filter(public=True).latest()
            changedesc.timestamp = timezone.now()
            changedesc.text = description or ""
            changedesc.rich_text = rich_text
            changedesc.save()

            # Needed to renew last-update.
            self.save()

        # Delete the associated draft review request.
        if draft is not None:
            draft.delete()
Пример #20
0
    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
Пример #21
0
    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