def test_post_no_history_allowed(self):
        """Testing the POST <URL> API for a review request created without
        history support
        """
        repository = self.create_repository(tool_name='Git')
        review_request = self.create_review_request(
            repository=repository,
            submitter=self.user,
            create_with_history=False)
        ReviewRequestDraft.create(review_request)
        diffset = self.create_diffset(review_request, draft=True)

        diff = SimpleUploadedFile('diff',
                                  self._DEFAULT_DIFF_CONTENTS,
                                  content_type='text/x-patch')

        with override_feature_checks(self.override_features):
            rsp = self.api_post(
                get_draft_diffcommit_list_url(review_request,
                                              diffset.revision),
                dict(self._DEFAULT_POST_DATA, **{
                    'diff': diff,
                }),
                expected_status=400)

        self.assertEqual(rsp['stat'], 'fail')
        self.assertEqual(rsp['err']['code'], INVALID_ATTRIBUTE.code)
        self.assertEqual(
            rsp['reason'],
            'This review request was not created with support for multiple '
            'commits.\n\n'
            'Use the draft_diff resource to upload diffs instead. See the '
            'draft_diff link on the parent resource for the URL.')
    def test_get_or_create_user_auth_backend(self):
        """Testing the PUT review-requests/<id>/draft/ API
        with AuthBackend.get_or_create_user failure
        """
        class SandboxAuthBackend(AuthBackend):
            backend_id = 'test-id'
            name = 'test'

            def get_or_create_user(self, username, request=None,
                                   password=None):
                raise Exception

        backend = SandboxAuthBackend()

        self.spy_on(auth.get_backends, call_fake=lambda: [backend])

        # The first spy messes with permissions, this lets it through
        self.spy_on(ReviewRequest.is_mutable_by, call_fake=lambda x, y: True)
        self.spy_on(backend.get_or_create_user)

        review_request = self.create_review_request(
            submitter=self.user)

        ReviewRequestDraft.create(review_request)

        rsp = self.api_put(
            get_review_request_draft_url(review_request, None),
            {
                'target_people': 'Target',
            },
            expected_status=400)

        self.assertEqual(rsp['stat'], 'fail')
        self.assertTrue(backend.get_or_create_user.called)
    def test_create_with_existing_new_draft(self):
        """Testing ReviewRequestDraft.create with existing draft"""
        review_request = self.create_review_request(
            publish=True,
            bugs_closed='1,20,300',
            commit_id='abc123',
            description_rich_text=True,
            rich_text=True,
            testing_done_rich_text=True,
            extra_data={
                'key': {
                    'values': [1, 2, 3],
                },
                'mybool': True,
            })

        # Create the first draft.
        orig_draft = ReviewRequestDraft.create(review_request)
        self.assertIsNotNone(orig_draft.changedesc)

        # Try to create it again.
        draft = ReviewRequestDraft.create(review_request)
        self.assertIsNotNone(draft.changedesc)

        self.assertEqual(orig_draft, draft)
        self.assertEqual(orig_draft.changedesc, draft.changedesc)
    def setup_basic_delete_test(self, user, with_local_site, local_site_name):
        review_request = self.create_review_request(
            with_local_site=with_local_site,
            submitter=user,
            publish=True)
        ReviewRequestDraft.create(review_request)

        return (get_review_request_draft_url(review_request, local_site_name),
                [review_request])
Example #5
0
    def test_outgoing_requests(self):
        """Testing counters with creating outgoing review requests"""
        # The review request was already created
        self._check_counters(total_outgoing=1,
                             pending_outgoing=1)

        ReviewRequestDraft.create(self.review_request)
        self.review_request.publish(self.user)

        self._check_counters(total_outgoing=1,
                             pending_outgoing=1,
                             starred_public=1)
    def test_put_publish(self):
        """Testing the PUT review-requests/<id>/draft/?public=1 API"""
        self.siteconfig.set("mail_send_review_mail", True)
        self.siteconfig.save()

        review_request = self.create_review_request(submitter=self.user, publish=True)
        draft = ReviewRequestDraft.create(review_request)
        draft.summary = "My Summary"
        draft.description = "My Description"
        draft.testing_done = "My Testing Done"
        draft.branch = "My Branch"
        draft.target_people.add(User.objects.get(username="******"))
        draft.save()

        mail.outbox = []

        rsp = self.apiPut(
            get_review_request_draft_url(review_request),
            {"public": True},
            expected_mimetype=review_request_draft_item_mimetype,
        )

        self.assertEqual(rsp["stat"], "ok")

        review_request = ReviewRequest.objects.get(pk=review_request.id)
        self.assertEqual(review_request.summary, "My Summary")
        self.assertEqual(review_request.description, "My Description")
        self.assertEqual(review_request.testing_done, "My Testing Done")
        self.assertEqual(review_request.branch, "My Branch")
        self.assertTrue(review_request.public)

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, "Re: Review Request %s: My Summary" % review_request.pk)
        self.assertValidRecipients(["doc", "grumpy"])
    def _test_put_with_text_type_escaping_unspecified_fields(self, text_type, text, expected_text):
        self.assertIn(text_type, ("markdown", "plain"))
        rich_text = text_type == "markdown"

        description = "`This` is the **description**"

        review_request = self.create_review_request(submitter=self.user, publish=True)
        review_request.rich_text = not rich_text
        review_request.description = text
        review_request.testing_done = text
        review_request.save()

        draft = ReviewRequestDraft.create(review_request)
        draft.changedesc.text = text
        draft.changedesc.save()

        rsp = self.api_put(
            get_review_request_draft_url(review_request),
            {"text_type": text_type, "description": description},
            expected_mimetype=review_request_draft_item_mimetype,
        )

        self.assertEqual(rsp["stat"], "ok")

        draft_rsp = rsp["draft"]
        self.assertEqual(draft_rsp["text_type"], text_type)
        self.assertEqual(draft_rsp["changedescription"], expected_text)
        self.assertEqual(draft_rsp["description"], description)
        self.assertEqual(draft_rsp["testing_done"], expected_text)

        draft = ReviewRequestDraft.objects.get(pk=rsp["draft"]["id"])
        self.compare_item(draft_rsp, draft)
    def test_put_without_text_type_and_escaping_provided_fields(self):
        """Testing the PUT review-requests/<id>/draft/ API
        without changing text_type and with escaping provided fields
        """
        review_request = self.create_review_request(submitter=self.user, publish=True)
        review_request.rich_text = True
        review_request.save()

        draft = ReviewRequestDraft.create(review_request)

        self.assertTrue(draft.rich_text)
        self.assertTrue(draft.changedesc.rich_text)

        rsp = self.api_put(
            get_review_request_draft_url(review_request),
            {
                "description": "This is **Description**",
                "testing_done": "This is **Testing Done**",
                "changedescription": "This is **Change Description**",
            },
            expected_mimetype=review_request_draft_item_mimetype,
        )

        self.assertEqual(rsp["stat"], "ok")

        draft_rsp = rsp["draft"]
        self.assertEqual(draft_rsp["text_type"], "markdown")
        self.assertEqual(draft_rsp["description"], "This is \*\*Description\*\*")
        self.assertEqual(draft_rsp["testing_done"], "This is \*\*Testing Done\*\*")
        self.assertEqual(draft_rsp["changedescription"], "This is \*\*Change Description\*\*")

        draft = ReviewRequestDraft.objects.get(pk=rsp["draft"]["id"])
        self.compare_item(draft_rsp, draft)
    def _test_put_with_rich_text_escaping_unspecified_fields(
            self, rich_text, text, expected_text):

        description = '`This` is the **description**'

        review_request = self.create_review_request(submitter=self.user,
                                                    publish=True)
        review_request.rich_text = not rich_text
        review_request.description = text
        review_request.testing_done = text
        review_request.save()

        draft = ReviewRequestDraft.create(review_request)
        draft.changedesc.text = text
        draft.changedesc.save()

        rsp = self.apiPut(
            get_review_request_draft_url(review_request),
            {
                'rich_text': rich_text,
                'description': description,
            },
            expected_mimetype=review_request_draft_item_mimetype)

        self.assertEqual(rsp['stat'], 'ok')

        draft_rsp = rsp['draft']
        self.assertEqual(draft_rsp['rich_text'], rich_text)
        self.assertEqual(draft_rsp['changedescription'], expected_text)
        self.assertEqual(draft_rsp['description'], description)
        self.assertEqual(draft_rsp['testing_done'], expected_text)

        draft = ReviewRequestDraft.objects.get(pk=rsp['draft']['id'])
        self.compare_item(draft_rsp, draft)
Example #10
0
    def create_diffset(self, review_request=None, revision=1, repository=None,
                       draft=False, name='diffset'):
        """Creates a DiffSet for testing.

        The DiffSet defaults to revision 1. This can be overriden by the
        caller.

        DiffSets generally are tied to a ReviewRequest, but it's optional.
        """
        if review_request:
            repository = review_request.repository

        diffset = DiffSet.objects.create(
            name=name,
            revision=revision,
            repository=repository)

        if review_request:
            if draft:
                review_request_draft = \
                    ReviewRequestDraft.create(review_request)
                review_request_draft.diffset = diffset
                review_request_draft.save()
            else:
                review_request.diffset_history.diffsets.add(diffset)

        return diffset
    def _test_put_with_text_type_escaping_all_fields(
            self, text_type, text, expected_text):
        self.assertIn(text_type, ('markdown', 'plain'))
        rich_text = (text_type == 'markdown')

        review_request = self.create_review_request(submitter=self.user,
                                                    publish=True)
        review_request.rich_text = not rich_text
        review_request.description = text
        review_request.testing_done = text
        review_request.save()

        draft = ReviewRequestDraft.create(review_request)
        draft.changedesc.text = text
        draft.changedesc.save()

        rsp = self.api_put(
            get_review_request_draft_url(review_request),
            {
                'text_type': text_type,
            },
            expected_mimetype=review_request_draft_item_mimetype)

        self.assertEqual(rsp['stat'], 'ok')

        draft_rsp = rsp['draft']
        self.assertEqual(draft_rsp['text_type'], text_type)
        self.assertEqual(draft_rsp['changedescription'], expected_text)
        self.assertEqual(draft_rsp['description'], expected_text)
        self.assertEqual(draft_rsp['testing_done'], expected_text)

        draft = ReviewRequestDraft.objects.get(pk=rsp['draft']['id'])
        self.compare_item(draft_rsp, draft)
Example #12
0
    def create_screenshot(self, review_request, caption='My caption',
                          draft=False, active=True, **kwargs):
        """Create a Screenshot for testing.

        The screenshot is tied to the given
        :py:class:`~reviewboard.reviews.models.review_request.ReviewRequest`.
        It's populated with default data that can be overridden by the caller.

        Args:
            review_request (reviewboard.reviews.models.review_request.
                            ReviewRequest):
                The review request that ultimately owns the screenshot.

            caption (unicode, optional):
                The caption to use for the screenshot.

            draft (bool or
                   reviewboard.reviews.models.review_request_draft.
                   ReviewRequestDraft):
                A draft to associate the screenshot with. This can also be
                a boolean, for legacy reasons, which will attempt to look up
                or create a draft for the review request.

            active (bool):
                Whether this screenshot is considered active (not deleted).

            **kwargs (dict):
                Additional fields to set on the screenshot.

        Returns:
            reviewboard.reviews.models.screenshot.Screenshot:
            The resulting screenshot.
        """
        screenshot = Screenshot(caption=caption, **kwargs)
        filename = os.path.join(settings.STATIC_ROOT, 'rb', 'images',
                                'logo.png')

        with open(filename, 'r') as f:
            screenshot.image.save(filename, File(f), save=True)

        if draft:
            if isinstance(draft, ReviewRequestDraft):
                review_request_draft = draft
            else:
                review_request_draft = \
                    ReviewRequestDraft.create(review_request)

            if active:
                screenshots = review_request_draft.screenshots
            else:
                screenshots = review_request_draft.inactive_screenshots
        else:
            if active:
                screenshots = review_request.screenshots
            else:
                screenshots = review_request.inactive_screenshots

        screenshots.add(screenshot)

        return screenshot
Example #13
0
    def test_discard_unpublished_public(self):
        """Testing ReviewRequest.close with public requests on discard
        to ensure changes from draft are not copied over
        """
        review_request = self.create_review_request(
            publish=False,
            public=True)

        self.assertTrue(review_request.public)
        self.assertNotEqual(review_request.status, ReviewRequest.DISCARDED)

        draft = ReviewRequestDraft.create(review_request)

        summary = 'Test summary'
        description = 'Test description'
        testing_done = 'Test testing done'

        draft.summary = summary
        draft.description = description
        draft.testing_done = testing_done
        draft.save()

        review_request.close(ReviewRequest.DISCARDED)

        self.assertNotEqual(review_request.summary, summary)
        self.assertNotEqual(review_request.description, description)
        self.assertNotEqual(review_request.testing_done, testing_done)
Example #14
0
    def create(self, file, review_request, filediff=None):
        caption = self.cleaned_data['caption'] or file.name
        mimetype = file.content_type or self._guess_mimetype(file)
        filename = '%s__%s' % (uuid4(), file.name)

        attachment_kwargs = {
            'caption': '',
            'draft_caption': caption,
            'orig_filename': os.path.basename(file.name),
            'mimetype': mimetype,
        }

        if filediff:
            file_attachment = FileAttachment.objects.create_from_filediff(
                filediff,
                save=False,
                **attachment_kwargs)
        else:
            file_attachment = FileAttachment(**attachment_kwargs)

        file_attachment.file.save(filename, file, save=True)

        draft = ReviewRequestDraft.create(review_request)
        draft.file_attachments.add(file_attachment)
        draft.save()

        return file_attachment
    def test_update_from_committed_change_with_rich_text_reset(self):
        """Testing ReviewRequestDraft.update_from_commit_id resets rich text
        fields
        """
        def get_change(repository, commit_to_get):
            commit = Commit(
                message='* This is a summary\n\n* This is a description.')
            diff_filename = os.path.join(self.testdata_dir, 'git_readme.diff')

            with open(diff_filename, 'r') as f:
                commit.diff = f.read()

            return commit

        def get_file_exists(repository, path, revision, base_commit_id=None,
                            request=None):
            return (path, revision) in [('/readme', 'd6613f5')]

        review_request = ReviewRequest.objects.create(self.user,
                                                      self.repository)
        draft = ReviewRequestDraft.create(review_request)

        self.spy_on(draft.repository.get_change, call_fake=get_change)
        self.spy_on(draft.repository.get_file_exists,
                    call_fake=get_file_exists)

        draft.description_rich_text = True
        draft.update_from_commit_id('4')

        self.assertEqual(draft.summary, '* This is a summary')
        self.assertEqual(draft.description, '* This is a description.')
        self.assertFalse(draft.description_rich_text)
        self.assertFalse(review_request.description_rich_text)
Example #16
0
    def create(self, file, review_request, filediff=None):
        caption = self.cleaned_data['caption'] or file.name

        if (not file.content_type or
            file.content_type == 'application/octet-stream'):
            # We can't rely on the browser for the file type here, so
            # attempt to guess it.
            mimetype = self._guess_mimetype(file)
        else:
            mimetype = file.content_type

        filename = '%s__%s' % (uuid4(), file.name)

        attachment_kwargs = {
            'caption': '',
            'draft_caption': caption,
            'orig_filename': os.path.basename(file.name),
            'mimetype': mimetype,
        }

        if filediff:
            file_attachment = FileAttachment.objects.create_from_filediff(
                filediff,
                save=False,
                **attachment_kwargs)
        else:
            file_attachment = FileAttachment(**attachment_kwargs)

        file_attachment.file.save(filename, file, save=True)

        draft = ReviewRequestDraft.create(review_request)
        draft.file_attachments.add(file_attachment)
        draft.save()

        return file_attachment
Example #17
0
    def test_limited_recipients_other_fields(self):
        """Testing that recipient limiting only happens when adding reviewers
        """
        review_request = self.create_review_request(
            summary='My test review request',
            public=True)
        review_request.email_message_id = "junk"
        review_request.target_people.add(User.objects.get(username='******'))
        review_request.save()

        draft = ReviewRequestDraft.create(review_request)
        draft.summary = 'Changed summary'
        draft.target_people.add(User.objects.get(username='******'))
        draft.publish(user=review_request.submitter)

        from_email = get_email_address_for_user(review_request.submitter)

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].from_email, self.sender)
        self.assertEqual(mail.outbox[0].extra_headers['From'], from_email)
        self.assertEqual(mail.outbox[0].subject,
                         'Re: Review Request %s: Changed summary'
                         % review_request.pk)
        self.assertValidRecipients([review_request.submitter.username,
                                    'dopey', 'grumpy'])

        message = mail.outbox[0].message()
        self.assertEqual(message['Sender'],
                         self._get_sender(review_request.submitter))
Example #18
0
    def test_recipients_with_muted_review_requests(self):
        """Testing e-mail recipients when users mute a review request"""
        dopey = User.objects.get(username='******')
        admin = User.objects.get(username='******')

        group = Group.objects.create(name='group')
        group.users.add(admin)
        group.save()

        review_request = self.create_review_request(
            summary='My test review request',
            public=True)
        review_request.target_people.add(dopey)
        review_request.target_people.add(User.objects.get(username='******'))
        review_request.target_groups.add(group)
        review_request.save()

        visit = self.create_visit(review_request, ReviewRequestVisit.MUTED,
                                  dopey)
        visit.save()

        visit = self.create_visit(review_request, ReviewRequestVisit.MUTED,
                                  admin)
        visit.save()

        draft = ReviewRequestDraft.create(review_request)
        draft.summary = 'Summary changed'
        draft.publish(user=review_request.submitter)

        self.assertEqual(len(mail.outbox), 1)
        self.assertValidRecipients(['doc', 'grumpy'])
    def test_create_with_new_draft_and_custom_changedesc(self):
        """Testing ReviewRequestDraft.create with new draft and custom
        ChangeDescription
        """
        review_request = self.create_review_request(
            publish=True,
            bugs_closed='1,20,300',
            commit_id='abc123',
            description_rich_text=True,
            rich_text=True,
            testing_done_rich_text=True,
            extra_data={
                'key': {
                    'values': [1, 2, 3],
                },
                'mybool': True,
            })

        # Create the draft.
        changedesc = ChangeDescription.objects.create()
        orig_draft = ReviewRequestDraft.create(review_request,
                                               changedesc=changedesc)

        self.assertEqual(orig_draft.changedesc_id, changedesc.pk)
        self.assertEqual(ChangeDescription.objects.count(), 1)

        # Reload to be sure.
        draft = ReviewRequestDraft.objects.get(pk=orig_draft.pk)
        self.assertEqual(orig_draft, draft)
        self.assertEqual(draft.changedesc, changedesc)
Example #20
0
    def test_add_reviewer_review_request_email(self):
        """Testing limited e-mail recipients
        when adding a reviewer to an existing review request
        """
        review_request = self.create_review_request(
            summary='My test review request',
            public=True)
        review_request.email_message_id = "junk"
        review_request.target_people.add(User.objects.get(username='******'))
        review_request.save()

        draft = ReviewRequestDraft.create(review_request)
        draft.target_people.add(User.objects.get(username='******'))
        draft.publish(user=review_request.submitter)

        from_email = get_email_address_for_user(review_request.submitter)

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].from_email, self.sender)
        self.assertEqual(mail.outbox[0].extra_headers['From'], from_email)
        self.assertEqual(mail.outbox[0].subject,
                         'Re: Review Request %s: My test review request'
                         % review_request.pk)
        # The only included users should be the submitter and 'grumpy' (not
        # 'dopey', since he was already included on the review request earlier)
        self.assertValidRecipients([review_request.submitter.username,
                                    'grumpy'])

        message = mail.outbox[0].message()
        self.assertEqual(message['Sender'],
                         self._get_sender(review_request.submitter))
    def test_update_from_committed_change_with_rich_text_reset(self):
        """Testing ReviewRequestDraft.update_from_commit_id resets rich text
        fields
        """
        def get_change(repository, commit_to_get):
            commit = Commit(
                message='* This is a summary\n\n* This is a description.',
                diff=self.DEFAULT_GIT_README_DIFF)

            return commit

        review_request = ReviewRequest.objects.create(self.user,
                                                      self.repository)
        draft = ReviewRequestDraft.create(review_request)

        self.spy_on(draft.repository.get_change, call_fake=get_change)
        self.spy_on(draft.repository.get_file_exists)

        draft.description_rich_text = True
        draft.update_from_commit_id('4')

        self.assertFalse(draft.repository.get_file_exists.called)
        self.assertEqual(draft.summary, '* This is a summary')
        self.assertEqual(draft.description, '* This is a description.')
        self.assertFalse(draft.description_rich_text)
        self.assertFalse(review_request.description_rich_text)
    def test_put_with_commit_id_and_used_in_draft(self):
        """Testing the PUT review-requests/<id>/draft/ API with commit_id
        used in another review request draft
        """
        commit_id = 'abc123'

        existing_review_request = self.create_review_request(
            submitter=self.user,
            publish=True)
        existing_draft = ReviewRequestDraft.create(existing_review_request)
        existing_draft.commit_id = commit_id
        existing_draft.save()

        review_request = self.create_review_request(submitter=self.user,
                                                    publish=True)

        self.api_put(
            get_review_request_draft_url(review_request),
            {
                'commit_id': commit_id,
            },
            expected_status=409)

        review_request = ReviewRequest.objects.get(pk=review_request.pk)
        self.assertIsNone(review_request.commit_id, None)
Example #23
0
    def create_file_attachment(self, review_request,
                               orig_filename='filename.png',
                               caption='My Caption',
                               draft=False,
                               **kwargs):
        """Creates a FileAttachment for testing.

        The FileAttachment is tied to the given ReviewRequest. It's populated
        with default data that can be overridden by the caller.
        """
        file_attachment = FileAttachment(
            caption=caption,
            orig_filename=orig_filename,
            mimetype='image/png',
            **kwargs)

        filename = os.path.join(settings.STATIC_ROOT, 'rb', 'images',
                                'trophy.png')

        with open(filename, 'r') as f:
            file_attachment.file.save(filename, File(f), save=True)

        if draft:
            review_request_draft = ReviewRequestDraft.create(review_request)
            review_request_draft.file_attachments.add(file_attachment)
        else:
            review_request.file_attachments.add(file_attachment)

        return file_attachment
Example #24
0
    def test_create_with_site_and_commit_id_conflicts_draft(self):
        """Testing ReviewRequest.objects.create with LocalSite and
        commit ID that conflicts with a draft
        """
        user = User.objects.get(username='******')
        local_site = LocalSite.objects.create(name='test')
        repository = self.create_repository()

        # This one should be fine.
        existing_review_request = ReviewRequest.objects.create(
            user, repository, local_site=local_site)
        existing_draft = ReviewRequestDraft.create(existing_review_request)
        existing_draft.commit_id = '123'
        existing_draft.save()

        self.assertEqual(local_site.review_requests.count(), 1)

        # This one will yell.
        with self.assertRaises(ChangeNumberInUseError):
            ReviewRequest.objects.create(
                user,
                repository,
                commit_id='123',
                local_site=local_site)

        # Make sure that entry doesn't exist in the database.
        self.assertEqual(local_site.review_requests.count(), 1)
    def test_put_with_no_changes(self):
        """Testing the PUT review-requests/<id>/draft/ API
        with no changes made to the fields
        """
        review_request = self.create_review_request(submitter=self.user,
                                                    publish=True)

        ReviewRequestDraft.create(review_request)

        rsp = self.api_put(
            get_review_request_draft_url(review_request),
            {'public': True},
            expected_status=NOTHING_TO_PUBLISH.http_status)

        self.assertEqual(rsp['stat'], 'fail')
        self.assertEqual(rsp['err']['code'], NOTHING_TO_PUBLISH.code)
Example #26
0
    def test_populate_counters(self):
        """Testing counters when populated from a fresh upgrade or clear"""
        # The review request was already created
        draft = ReviewRequestDraft.create(self.review_request)
        draft.target_groups.add(self.group)
        draft.target_people.add(self.user)
        self.review_request.publish(self.user)

        self._check_counters(total_outgoing=1,
                             pending_outgoing=1,
                             total_incoming=1,
                             direct_incoming=1,
                             starred_public=1,
                             group_incoming=1)

        LocalSiteProfile.objects.update(
            direct_incoming_request_count=None,
            total_incoming_request_count=None,
            pending_outgoing_request_count=None,
            total_outgoing_request_count=None,
            starred_public_request_count=None)
        Group.objects.update(incoming_request_count=None)

        self._check_counters(total_outgoing=1,
                             pending_outgoing=1,
                             total_incoming=1,
                             direct_incoming=1,
                             starred_public=1,
                             group_incoming=1)
Example #27
0
    def test_remove_person_and_fail_publish(self):
        """Testing counters when removing a person reviewer and then
        failing to publish the draft
        """
        self.test_add_person()

        draft = ReviewRequestDraft.create(self.review_request)
        draft.target_people.remove(self.user)

        self._check_counters(total_outgoing=1,
                             pending_outgoing=1,
                             direct_incoming=1,
                             total_incoming=1,
                             starred_public=1)

        self.spy_on(ReviewRequestDraft.publish,
                    call_fake=self._raise_publish_error)

        with self.assertRaises(NotModifiedError):
            self.review_request.publish(self.user)

        self._check_counters(total_outgoing=1,
                             pending_outgoing=1,
                             direct_incoming=1,
                             total_incoming=1,
                             starred_public=1)
Example #28
0
    def setUp(self):
        super(SandboxTests, self).setUp()

        register_ui(InitReviewUI)
        register_ui(SandboxReviewUI)
        register_ui(ConflictFreeReviewUI)

        self.factory = RequestFactory()

        filename = os.path.join(settings.STATIC_ROOT,
                                'rb', 'images', 'trophy.png')

        with open(filename, 'r') as f:
            self.file = SimpleUploadedFile(f.name, f.read(),
                                           content_type='image/png')

        self.user = User.objects.get(username='******')
        self.review_request = ReviewRequest.objects.create(self.user, None)
        self.file_attachment1 = FileAttachment.objects.create(
            mimetype='image/jpg',
            file=self.file)
        self.file_attachment2 = FileAttachment.objects.create(
            mimetype='image/png',
            file=self.file)
        self.file_attachment3 = FileAttachment.objects.create(
            mimetype='image/gif',
            file=self.file)
        self.review_request.file_attachments.add(self.file_attachment1)
        self.review_request.file_attachments.add(self.file_attachment2)
        self.review_request.file_attachments.add(self.file_attachment3)
        self.draft = ReviewRequestDraft.create(self.review_request)
Example #29
0
    def create_file_attachment(self, review_request,
                               orig_filename='filename.png',
                               caption='My Caption',
                               draft=False,
                               active=True,
                               **kwargs):
        """Creates a FileAttachment for testing.

        The FileAttachment is tied to the given ReviewRequest. It's populated
        with default data that can be overridden by the caller.
        """
        file_attachment = self._create_base_file_attachment(
            caption=caption,
            orig_filename=orig_filename,
            **kwargs)

        if draft:
            review_request_draft = ReviewRequestDraft.create(review_request)

            if active:
                attachments = review_request_draft.file_attachments
            else:
                attachments = review_request_draft.inactive_file_attachments
        else:
            if active:
                attachments = review_request.file_attachments
            else:
                attachments = review_request.inactive_file_attachments

        attachments.add(file_attachment)

        return file_attachment
Example #30
0
    def create_screenshot(self, review_request, caption='My caption',
                          draft=False, active=True):
        """Creates a Screenshot for testing.

        The Screenshot is tied to the given ReviewRequest. It's populated
        with default data that can be overridden by the caller.
        """
        screenshot = Screenshot(caption=caption)
        filename = os.path.join(settings.STATIC_ROOT, 'rb', 'images',
                                'logo.png')

        with open(filename, 'r') as f:
            screenshot.image.save(filename, File(f), save=True)

        if draft:
            review_request_draft = ReviewRequestDraft.create(review_request)

            if active:
                screenshots = review_request_draft.screenshots
            else:
                screenshots = review_request_draft.inactive_screenshots
        else:
            if active:
                screenshots = review_request.screenshots
            else:
                screenshots = review_request.inactive_screenshots

        screenshots.add(screenshot)

        return screenshot
    def test_put_without_rich_text_and_escaping_provided_fields(self):
        """Testing the PUT review-requests/<id>/draft/ API
        without changing rich_text and with escaping provided fields
        """
        review_request = self.create_review_request(submitter=self.user,
                                                    publish=True)
        review_request.rich_text = True
        review_request.save()

        draft = ReviewRequestDraft.create(review_request)

        self.assertTrue(draft.rich_text)
        self.assertTrue(draft.changedesc.rich_text)

        rsp = self.apiPut(
            get_review_request_draft_url(review_request),
            {
                'description': 'This is **Description**',
                'testing_done': 'This is **Testing Done**',
                'changedescription': 'This is **Change Description**',
            },
            expected_mimetype=review_request_draft_item_mimetype)

        self.assertEqual(rsp['stat'], 'ok')

        draft_rsp = rsp['draft']
        self.assertTrue(draft_rsp['rich_text'])
        self.assertEqual(draft_rsp['description'],
                         'This is \*\*Description\*\*')
        self.assertEqual(draft_rsp['testing_done'],
                         'This is \*\*Testing Done\*\*')
        self.assertEqual(draft_rsp['changedescription'],
                         'This is \*\*Change Description\*\*')

        draft = ReviewRequestDraft.objects.get(pk=rsp['draft']['id'])
        self.compare_item(draft_rsp, draft)
Example #32
0
    def test_remove_group(self):
        """Testing counters when removing a group reviewer"""

        self.test_add_group()

        draft = ReviewRequestDraft.create(self.review_request)
        draft.target_groups.remove(self.group)

        # There must be at least one target_group or target_people
        draft.target_people = [self.user]

        self._check_counters(total_outgoing=1,
                             pending_outgoing=1,
                             total_incoming=1,
                             direct_incoming=0,
                             group_incoming=1,
                             starred_public=1)

        self.review_request.publish(self.user)
        self._check_counters(total_outgoing=1,
                             pending_outgoing=1,
                             direct_incoming=1,
                             total_incoming=1,
                             starred_public=1)
    def test_put_publish(self):
        """Testing the PUT review-requests/<id>/draft/?public=1 API"""
        self.siteconfig.set('mail_send_review_mail', True)
        self.siteconfig.save()

        review_request = self.create_review_request(submitter=self.user,
                                                    publish=True)
        draft = ReviewRequestDraft.create(review_request)
        draft.summary = 'My Summary'
        draft.description = 'My Description'
        draft.testing_done = 'My Testing Done'
        draft.branch = 'My Branch'
        draft.target_people.add(User.objects.get(username='******'))
        draft.save()

        mail.outbox = []

        rsp = self.apiPut(
            get_review_request_draft_url(review_request),
            {'public': True},
            expected_mimetype=review_request_draft_item_mimetype)

        self.assertEqual(rsp['stat'], 'ok')

        review_request = ReviewRequest.objects.get(pk=review_request.id)
        self.assertEqual(review_request.summary, "My Summary")
        self.assertEqual(review_request.description, "My Description")
        self.assertEqual(review_request.testing_done, "My Testing Done")
        self.assertEqual(review_request.branch, "My Branch")
        self.assertTrue(review_request.public)

        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(
            mail.outbox[0].subject,
            "Re: Review Request %s: My Summary" % review_request.pk)
        self.assertValidRecipients(["doc", "grumpy"])
Example #34
0
    def create_screenshot(self,
                          review_request,
                          caption='My caption',
                          draft=False,
                          active=True,
                          **kwargs):
        """Create a Screenshot for testing.

        The screenshot is tied to the given
        :py:class:`~reviewboard.reviews.models.review_request.ReviewRequest`.
        It's populated with default data that can be overridden by the caller.

        Args:
            review_request (reviewboard.reviews.models.review_request.
                            ReviewRequest):
                The review request that ultimately owns the screenshot.

            caption (unicode, optional):
                The caption to use for the screenshot.

            draft (bool or
                   reviewboard.reviews.models.review_request_draft.
                   ReviewRequestDraft):
                A draft to associate the screenshot with. This can also be
                a boolean, for legacy reasons, which will attempt to look up
                or create a draft for the review request.

            active (bool):
                Whether this screenshot is considered active (not deleted).

            **kwargs (dict):
                Additional fields to set on the screenshot.

        Returns:
            reviewboard.reviews.models.screenshot.Screenshot:
            The resulting screenshot.
        """
        screenshot = Screenshot(caption=caption, **kwargs)
        filename = os.path.join(settings.STATIC_ROOT, 'rb', 'images',
                                'logo.png')

        with open(filename, 'r') as f:
            screenshot.image.save(filename, File(f), save=True)

        if draft:
            if isinstance(draft, ReviewRequestDraft):
                review_request_draft = draft
            else:
                review_request_draft = \
                    ReviewRequestDraft.create(review_request)

            if active:
                screenshots = review_request_draft.screenshots
            else:
                screenshots = review_request_draft.inactive_screenshots
        else:
            if active:
                screenshots = review_request.screenshots
            else:
                screenshots = review_request.inactive_screenshots

        screenshots.add(screenshot)

        return screenshot
Example #35
0
    def test_create_with_new_draft(self):
        """Testing ReviewRequestDraft.create with new draft"""
        user1 = User.objects.create(username='******')
        user2 = User.objects.create(username='******')

        group1 = self.create_review_group(name='group1')
        group2 = self.create_review_group(name='group2')

        dep_review_request_1 = self.create_review_request(publish=True)
        dep_review_request_2 = self.create_review_request(publish=True)

        review_request = self.create_review_request(
            publish=True,
            bugs_closed='1,20,300',
            commit_id='abc123',
            description_rich_text=True,
            depends_on=[dep_review_request_1, dep_review_request_2],
            rich_text=True,
            target_groups=[group1, group2],
            target_people=[user1, user2],
            testing_done_rich_text=True,
            extra_data={
                'key': {
                    'values': [1, 2, 3],
                },
                'mybool': True,
            })

        active_file_attachment_1 = self.create_file_attachment(review_request)
        active_file_attachment_2 = self.create_file_attachment(review_request)
        inactive_file_attachment = self.create_file_attachment(review_request,
                                                               active=False)

        active_screenshot_1 = self.create_screenshot(review_request)
        active_screenshot_2 = self.create_screenshot(review_request)
        inactive_screenshot = self.create_screenshot(review_request,
                                                     active=False)

        # Create the draft.
        draft = ReviewRequestDraft.create(review_request)

        # Make sure all the fields are the same.
        self.assertEqual(draft.branch, review_request.branch)
        self.assertEqual(draft.bugs_closed, review_request.bugs_closed)
        self.assertEqual(draft.commit_id, review_request.commit_id)
        self.assertEqual(draft.description, review_request.description)
        self.assertEqual(draft.description_rich_text,
                         review_request.description_rich_text)
        self.assertEqual(draft.extra_data, review_request.extra_data)
        self.assertEqual(draft.rich_text, review_request.rich_text)
        self.assertEqual(draft.summary, review_request.summary)
        self.assertEqual(draft.testing_done, review_request.testing_done)
        self.assertEqual(draft.testing_done_rich_text,
                         review_request.testing_done_rich_text)

        self.assertEqual(list(draft.depends_on.order_by('pk')),
                         [dep_review_request_1, dep_review_request_2])
        self.assertEqual(list(draft.target_groups.all()), [group1, group2])
        self.assertEqual(list(draft.target_people.all()), [user1, user2])
        self.assertEqual(list(draft.file_attachments.all()),
                         [active_file_attachment_1, active_file_attachment_2])
        self.assertEqual(list(draft.inactive_file_attachments.all()),
                         [inactive_file_attachment])
        self.assertEqual(list(draft.screenshots.all()),
                         [active_screenshot_1, active_screenshot_2])
        self.assertEqual(list(draft.inactive_screenshots.all()),
                         [inactive_screenshot])

        self.assertIsNotNone(draft.changedesc)
Example #36
0
    def prepare_draft(self, request, review_request):
        """Creates a draft, if the user has permission to."""
        if not review_request.is_mutable_by(request.user):
            raise PermissionDenied

        return ReviewRequestDraft.create(review_request)
    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_review_request(local_site, request, privileged_user, reviewer_cache,
                          rr, commit):
    """Synchronize the state of a review request with a commit.

    Updates the commit message, refreshes the diff, etc.
    """
    try:
        draft = rr.draft.get()
    except ReviewRequestDraft.DoesNotExist:
        draft = ReviewRequestDraft.create(rr)

    draft.summary = commit['message'].splitlines()[0]
    draft.description = commit['message']
    draft.bugs_closed = commit['bug']

    commit_data = fetch_commit_data(draft)
    commit_data.draft_extra_data.update({
        AUTHOR_KEY:
        commit['author'],
        COMMIT_ID_KEY:
        commit['id'],
        FIRST_PUBLIC_ANCESTOR_KEY:
        commit['first_public_ancestor'],
    })
    commit_data.save(update_fields=['draft_extra_data'])

    reviewer_users, unrecognized_reviewers = \
        resolve_reviewers(reviewer_cache, commit.get('reviewers', []))
    requal_reviewer_users, unrecognized_requal_reviewers = \
        resolve_reviewers(reviewer_cache, commit.get('requal_reviewers', []))

    warnings = []

    for reviewer in unrecognized_reviewers | unrecognized_requal_reviewers:
        warnings.append('unrecognized reviewer: %s' % reviewer)
        logger.info('unrecognized reviewer: %s' % reviewer)

    if requal_reviewer_users:
        pr = previous_reviewers(rr)
        for user in requal_reviewer_users:
            if not pr.get(user.username, False):
                warnings.append('commit message for %s has r=%s but they '
                                'have not granted a ship-it. review will be '
                                'requested on your behalf' %
                                (commit['id'][:12], user.username))

        reviewer_users |= requal_reviewer_users

    # Carry over from last time unless commit message overrules.
    if reviewer_users:
        draft.target_people.clear()
    for user in sorted(reviewer_users):
        draft.target_people.add(user)
        logger.debug('adding reviewer %s to #%d' % (user.username, rr.id))

    try:
        diffset = DiffSet.objects.create_from_data(
            repository=rr.repository,
            diff_file_name='diff',
            diff_file_contents=commit['diff_b64'].encode('ascii').decode(
                'base64'),
            parent_diff_file_name='diff',
            parent_diff_file_contents=None,
            diffset_history=None,
            basedir='',
            request=request,
            base_commit_id=commit.get('base_commit_id'),
            save=True,
        )
        update_diffset_history(rr, diffset)
        diffset.save()

        DiffSetVerification(diffset=diffset).save(
            authorized_user=privileged_user, force_insert=True)
    except Exception:
        logger.exception('error processing diff')
        raise DiffProcessingException()

    update_review_request_draft_diffset(rr, diffset, draft=draft)

    return draft, warnings
Example #39
0
    def create_file_attachment(self,
                               review_request,
                               orig_filename='filename.png',
                               caption='My Caption',
                               draft=False,
                               active=True,
                               **kwargs):
        """Create a FileAttachment for testing.

        The attachment is tied to the given
        :py:class:`~reviewboard.reviews.models.review_request.ReviewRequest`.
        It's populated with default data that can be overridden by the caller.

        Args:
            review_request (reviewboard.reviews.models.review_request.
                            ReviewRequest):
                The review request that ultimately owns the file attachment.

            orig_filename (unicode, optional):
                The filename to use for the file attachment.

            caption (unicode, optional):
                The caption to use for the file attachment.

            draft (bool or
                   reviewboard.reviews.models.review_request_draft.
                   ReviewRequestDraft):
                A draft to associate the attachment with. This can also be
                a boolean, for legacy reasons, which will attempt to look up
                or create a draft for the review request.

            active (bool):
                Whether this attachment is considered active (not deleted).

            **kwargs (dict):
                Additional fields to set on the attachment.

        Returns:
            reviewboard.attachments.models.FileAttachment:
            The resulting file attachment.
        """
        file_attachment = self._create_base_file_attachment(
            caption=caption, orig_filename=orig_filename, **kwargs)

        if draft:
            if isinstance(draft, ReviewRequestDraft):
                review_request_draft = draft
            else:
                review_request_draft = \
                    ReviewRequestDraft.create(review_request)

            if active:
                attachments = review_request_draft.file_attachments
            else:
                attachments = review_request_draft.inactive_file_attachments
        else:
            if active:
                attachments = review_request.file_attachments
            else:
                attachments = review_request.inactive_file_attachments

        attachments.add(file_attachment)

        return file_attachment
Example #40
0
    def test_sidebar(self):
        """Testing dashboard sidebar"""
        self.client.login(username='******', password='******')
        user = User.objects.get(username='******')
        profile = user.get_profile()

        # Create all the test data.
        devgroup = self.create_review_group(name='devgroup')
        devgroup.users.add(user)

        privgroup = self.create_review_group(name='privgroup')
        privgroup.users.add(user)

        review_request = self.create_review_request(submitter=user,
                                                    publish=True)

        review_request = self.create_review_request(submitter='grumpy')
        draft = ReviewRequestDraft.create(review_request)
        draft.target_people.add(user)
        review_request.publish(review_request.submitter)

        review_request = self.create_review_request(submitter='grumpy')
        draft = ReviewRequestDraft.create(review_request)
        draft.target_groups.add(devgroup)
        review_request.publish(review_request.submitter)

        review_request = self.create_review_request(submitter='grumpy')
        draft = ReviewRequestDraft.create(review_request)
        draft.target_groups.add(privgroup)
        review_request.publish(review_request.submitter)
        profile.star_review_request(review_request)

        # Now load the dashboard and get the sidebar items.
        response = self.client.get('/dashboard/')
        self.assertEqual(response.status_code, 200)

        sidebar_items = \
            self._get_context_var(response, 'datagrid').sidebar_items
        self.assertEqual(len(sidebar_items), 3)

        # Test the "Overview" section.
        section = sidebar_items[0]
        self.assertEqual(six.text_type(section.label), 'Overview')

        # Test the "Outgoing" section.
        section = sidebar_items[1]
        self.assertEqual(six.text_type(section.label), 'Outgoing')
        self.assertEqual(len(section.items), 2)
        self.assertEqual(six.text_type(section.items[0].label), 'All')
        self.assertEqual(section.items[0].count, 1)
        self.assertEqual(six.text_type(section.items[1].label), 'Open')
        self.assertEqual(section.items[1].count, 1)

        # Test the "Incoming" section.
        section = sidebar_items[2]
        self.assertEqual(six.text_type(section.label), 'Incoming')
        self.assertEqual(len(section.items), 5)
        self.assertEqual(six.text_type(section.items[0].label), 'Open')
        self.assertEqual(section.items[0].count, 3)
        self.assertEqual(six.text_type(section.items[1].label), 'To Me')
        self.assertEqual(section.items[1].count, 1)
        self.assertEqual(six.text_type(section.items[2].label), 'Starred')
        self.assertEqual(section.items[2].count, 1)
        self.assertEqual(six.text_type(section.items[3].label), 'devgroup')
        self.assertEqual(section.items[3].count, 1)
        self.assertEqual(six.text_type(section.items[4].label), 'privgroup')
        self.assertEqual(section.items[4].count, 1)
 def check_put_result(self, user, item_rsp, draft, review_request):
     draft = ReviewRequestDraft.create(review_request)
     self.compare_item(item_rsp, draft)
Example #42
0
    def create(self,
               user,
               repository,
               commit_id=None,
               local_site=None,
               create_from_commit_id=False):
        """
        Creates a new review request, optionally filling in fields based off
        a commit ID.
        """
        from reviewboard.reviews.models import ReviewRequestDraft

        if commit_id:
            # Try both the new commit_id and old changenum versions
            try:
                review_request = self.get(commit_id=commit_id,
                                          repository=repository)
                raise ChangeNumberInUseError(review_request)
            except ObjectDoesNotExist:
                pass

            try:
                draft = ReviewRequestDraft.objects.get(
                    commit_id=commit_id, review_request__repository=repository)
                raise ChangeNumberInUseError(draft.review_request)
            except ObjectDoesNotExist:
                pass

            try:
                review_request = self.get(changenum=int(commit_id),
                                          repository=repository)
                raise ChangeNumberInUseError(review_request)
            except (ObjectDoesNotExist, TypeError, ValueError):
                pass

        diffset_history = DiffSetHistory()
        diffset_history.save()

        review_request = self.model(submitter=user,
                                    status='P',
                                    public=False,
                                    repository=repository,
                                    diffset_history=diffset_history,
                                    local_site=local_site)

        if commit_id and not create_from_commit_id:
            review_request.commit_id = commit_id

        review_request.validate_unique()
        review_request.save()

        if commit_id and create_from_commit_id:
            try:
                draft = ReviewRequestDraft.objects.create(
                    review_request=review_request)
                draft.update_from_commit_id(commit_id)
                draft.save()
                draft.add_default_reviewers()
            except Exception as e:
                review_request.commit_id = commit_id
                review_request.save(update_fields=['commit_id'])
                logging.error(
                    'Unable to update new review request from '
                    'commit ID %s: %s',
                    commit_id,
                    e,
                    exc_info=1)

        if local_site:
            # We want to atomically set the local_id to be a monotonically
            # increasing ID unique to the local_site. This isn't really
            # possible in django's DB layer, so we have to drop back to pure
            # SQL and then reload the model.
            from reviewboard.reviews.models import ReviewRequest
            db = router.db_for_write(ReviewRequest)
            cursor = connections[db].cursor()
            cursor.execute(
                'UPDATE %(table)s SET'
                '  local_id = COALESCE('
                '    (SELECT MAX(local_id) from'
                '      (SELECT local_id FROM %(table)s'
                '        WHERE local_site_id = %(local_site_id)s) as x'
                '      ) + 1,'
                '    1),'
                '  local_site_id = %(local_site_id)s'
                '    WHERE %(table)s.id = %(id)s' % {
                    'table': ReviewRequest._meta.db_table,
                    'local_site_id': local_site.pk,
                    'id': review_request.pk,
                })
            transaction.commit()

            review_request = ReviewRequest.objects.get(pk=review_request.pk)

        # Ensure that a draft exists, so that users will be prompted to publish
        # the new review request.
        ReviewRequestDraft.create(review_request)

        return review_request
    def setup_basic_get_test(self,
                             user,
                             with_local_site=False,
                             local_site_name=None,
                             update_review_request=False,
                             update_diffset=False,
                             update_review=False,
                             update_reply=False,
                             publish_user=None):
        """Setup a basic HTTP GET test.

        Args:
            user (django.contrib.auth.models.User):
                The user that will be used to retrieve the resource.

                They will also be the submitter/author of all objects (although
                not necessary the publisher).

            with_local_site (boolean, optional):
                Whether or not a LocalSite-specific resource will be used.

            local_site_name (unicode, optional):
                The name of the LocalSite to use.

            update_review_request (boolean, optional):
                Whether or not the review request should be updated after
                publishing.

            update_diffset (boolean, optional):
                Whether or not the review request should be updated with a diff
                after publishing.

            update_review (boolean, optional):
                Whether or not the review request should be reviewed after
                publishing.

            update_reply (boolean, optional):
                Whther or not to create a reply.

                This implies ``update_review``.

            publish_user (django.contrib.auth.models.User, optional):
                The user that will trigger the update (i.e., they will publish
                all requested objects).

                If not provided, ``user`` will be used.

        Returns:
            tuple:
            A 3-tuple of:

            * The URL to request.
            * The expected mimetype.
            * The review request to use for comparison.
        """
        update_review = update_review or update_reply
        publish_user = publish_user or user
        review_request = self.create_review_request(
            create_repository=True,
            submitter=user,
            with_local_site=with_local_site,
            target_people=[user])

        review_request.publish(user=publish_user)

        if update_review_request:
            draft = ReviewRequestDraft.create(review_request)
            draft.summary = '%s updated' % review_request.summary
            draft.save()

            review_request.publish(user=publish_user)

        if update_diffset:
            self.create_diffset(review_request=review_request, draft=True)
            review_request.publish(user=publish_user)

        if update_review:
            review = self.create_review(review_request=review_request,
                                        user=user,
                                        publish=False)
            review.publish(user=publish_user)

            if update_reply:
                reply = self.create_reply(review, user=user)
                reply.publish(user=publish_user)

        return (
            get_review_request_last_update_url(local_site_name=local_site_name,
                                               review_request=review_request),
            review_request_last_update_mimetype,
            review_request,
        )
 def _get_draft(self):
     """Convenience function for getting a new draft to work with."""
     review_request = self.create_review_request(publish=True)
     return ReviewRequestDraft.create(review_request)
Example #45
0
def update_review_request(local_site, request, privileged_user, reviewer_cache,
                          rr, commit, create_commit_msg_filediff):
    """Synchronize the state of a review request with a commit.

    Updates the commit message, refreshes the diff, etc.
    """
    try:
        draft = rr.draft.get()
    except ReviewRequestDraft.DoesNotExist:
        draft = ReviewRequestDraft.create(rr)

    draft.summary = commit['message'].splitlines()[0]
    draft.description = commit['message']
    draft.bugs_closed = commit['bug']

    commit_data = fetch_commit_data(draft)

    reviewer_users, unrecognized_reviewers = \
        resolve_reviewers(reviewer_cache, commit.get('reviewers', []))
    requal_reviewer_users, unrecognized_requal_reviewers = \
        resolve_reviewers(reviewer_cache, commit.get('requal_reviewers', []))

    warnings = []

    for reviewer in unrecognized_reviewers | unrecognized_requal_reviewers:
        warnings.append('unrecognized reviewer: %s' % reviewer)
        logger.info('unrecognized reviewer: %s' % reviewer)

    if requal_reviewer_users:
        pr = previous_reviewers(rr)
        for user in requal_reviewer_users:
            if not pr.get(user.username, False):
                warnings.append('commit message for %s has r=%s but they '
                                'have not granted a ship-it. review will be '
                                'requested on your behalf' %
                                (commit['id'][:12], user.username))

        reviewer_users |= requal_reviewer_users

    # Commit message FileDiff creation.
    base_commit_id = commit.get('base_commit_id')
    commit_message_filediff = None

    if create_commit_msg_filediff:
        # Prepare commit message data
        commit_message_name = commit_data.draft_extra_data.get(
            COMMIT_MSG_FILENAME_KEY, 'commit-message-%s' % base_commit_id[0:5])
        commit_message_lines = commit['message'].split('\n')
        commit_message_diff = COMMIT_MSG_DIFF_FORMAT % {
            'source_filename': commit_message_name,
            'target_filename': commit_message_name,
            'num_lines': len(commit_message_lines),
            'diff':
            '%s\n' % '\n'.join(['+%s' % l for l in commit_message_lines])
        }

        commit_data.extra_data[COMMIT_MSG_FILENAME_KEY] = commit_message_name

        # Commit message FileDiff has to be displayed as the first one.
        # Therefore it needs to be created before other FileDiffs in
        # the DiffSet.
        # FileDiff object has a required DiffSet field. Because target DiffSet
        # is created along with other FileDiffs, there is a need to
        # create a temporary one.
        # Later in the code temporary DiffSet is replaced in the commit
        # message FileDiff with the target one.
        temp_diffset = get_temp_diffset(rr.repository)

        commit_message_filediff = FileDiff.objects.create(
            diffset=temp_diffset,
            source_file=commit_message_name,
            dest_file=commit_message_name,
            source_revision=PRE_CREATION,
            dest_detail='',
            parent_diff='',
            binary=False,
            status='M',
            diff=commit_message_diff)

    # Carry over from last time unless commit message overrules.
    if reviewer_users:
        draft.target_people.clear()
    for user in sorted(reviewer_users):
        draft.target_people.add(user)
        logger.debug('adding reviewer %s to #%d' % (user.username, rr.id))

    try:
        diffset = DiffSet.objects.create_from_data(
            repository=rr.repository,
            diff_file_name='diff',
            diff_file_contents=commit['diff_b64'].encode('ascii').decode(
                'base64'),
            parent_diff_file_name='diff',
            parent_diff_file_contents=None,
            diffset_history=None,
            basedir='',
            request=request,
            base_commit_id=base_commit_id,
            save=True,
        )

        update_diffset_history(rr, diffset)
        diffset.save()

        DiffSetVerification(diffset=diffset).save(
            authorized_user=privileged_user, force_insert=True)
    except Exception:
        logger.exception('error processing diff')
        raise DiffProcessingException()

    # Now that the proper DiffSet has been created, re-assign
    # the commit message FileDiff we created to the new DiffSet.
    if commit_message_filediff:
        commit_message_filediff.diffset = diffset
        commit_message_filediff.save()

        commit_msg_filediff_ids = json.loads(
            commit_data.draft_extra_data.get(COMMIT_MSG_FILEDIFF_IDS_KEY,
                                             '{}'))
        commit_msg_filediff_ids[str(
            diffset.revision)] = commit_message_filediff.pk
        # Store commit message FileDiffs ids in extra_data
        commit_data.draft_extra_data[COMMIT_MSG_FILEDIFF_IDS_KEY] = json.dumps(
            commit_msg_filediff_ids)

    commit_data.draft_extra_data.update({
        AUTHOR_KEY:
        commit['author'],
        COMMIT_ID_KEY:
        commit['id'],
        FIRST_PUBLIC_ANCESTOR_KEY:
        commit['first_public_ancestor'],
    })
    commit_data.save(update_fields=['draft_extra_data', 'extra_data'])

    update_review_request_draft_diffset(rr, diffset, draft=draft)

    return draft, warnings
Example #46
0
    def create(self, filediff=None):
        """Create a FileAttachment based on this form.

        Args:
            filediff (reviewboard.diffviewer.models.filediff.FileDiff, optional):
                The optional diff to attach this file to (for use when this
                file represents a binary file within the diff).

        Returns:
            reviewboard.attachments.models.FileAttachment:
            The new file attachment model.
        """
        file_obj = self.files['path']
        caption = self.cleaned_data['caption'] or file_obj.name

        mimetype = get_uploaded_file_mimetype(file_obj)
        filename = get_unique_filename(file_obj.name)

        if self.cleaned_data['attachment_history'] is None:
            # This is a new file: create a new FileAttachmentHistory for it
            attachment_history = FileAttachmentHistory()
            attachment_revision = 1

            attachment_history.display_position = \
                FileAttachmentHistory.compute_next_display_position(
                    self.review_request)
            attachment_history.save()
            self.review_request.file_attachment_histories.add(
                attachment_history)
        else:
            attachment_history = self.cleaned_data['attachment_history']

            try:
                latest = attachment_history.file_attachments.latest()
            except FileAttachment.DoesNotExist:
                latest = None

            if latest is None:
                # This should theoretically never happen, but who knows.
                attachment_revision = 1
            elif latest.review_request.exists():
                # This is a new update in the draft.
                attachment_revision = latest.attachment_revision + 1
            else:
                # The most recent revision is part of the same draft. Delete it
                # and replace with the newly uploaded file.
                attachment_revision = latest.attachment_revision
                latest.delete()

        attachment_kwargs = {
            'attachment_history': attachment_history,
            'attachment_revision': attachment_revision,
            'caption': '',
            'draft_caption': caption,
            'orig_filename': os.path.basename(file_obj.name),
            'mimetype': mimetype,
        }

        if filediff:
            file_attachment = FileAttachment.objects.create_from_filediff(
                filediff, save=False, **attachment_kwargs)
        else:
            file_attachment = FileAttachment(**attachment_kwargs)

        file_attachment.file.save(filename, file_obj, save=True)

        draft = ReviewRequestDraft.create(self.review_request)
        draft.file_attachments.add(file_attachment)
        draft.save()

        return file_attachment
Example #47
0
    def create(self,
               user,
               repository,
               commit_id=None,
               local_site=None,
               create_from_commit_id=False,
               create_with_history=False):
        """Create a new review request.

        Args:
            user (django.contrib.auth.models.User):
                The user creating the review request. They will be tracked as
                the submitter.

            repository (reviewboard.scmtools.Models.Repository):
                The repository, if any, the review request is associated with.

                If ``None``, diffs cannot be added to the review request.

            commit_id (unicode, optional):
                An optional commit ID.

            local_site (reviewboard.site.models.LocalSite, optional):
                An optional LocalSite to associate the review request with.

            create_from_commit_id (bool, optional):
                Whether or not the given ``commit_id`` should be used to
                pre-populate the review request data. If ``True``, the given
                ``repository`` will be used to do so.

            create_with_history (bool, optional):
                Whether or not the created review request will support
                attaching multiple commits per diff revision.

                If ``False``, it will not be possible to use the
                :py:class:`~reviewboard.webapi.resources.diff.DiffResource` to
                upload diffs; the
                :py:class:`~reviewboard.webapi.resources.DiffCommitResource`
                must be used instead.

        Returns:
            reviewboard.reviews.models.review_request.ReviewRequest:
            The created review request.

        Raises:
            reviewboard.scmtools.errors.ChangeNumberInUseError:
                The commit ID is already in use by another review request.

            ValueError:
                An invalid value was passed for an argument.
        """
        from reviewboard.reviews.models import ReviewRequestDraft

        if commit_id:
            # Try both the new commit_id and old changenum versions
            try:
                review_request = self.get(commit_id=commit_id,
                                          repository=repository)
                raise ChangeNumberInUseError(review_request)
            except ObjectDoesNotExist:
                pass

            try:
                draft = ReviewRequestDraft.objects.get(
                    commit_id=commit_id, review_request__repository=repository)
                raise ChangeNumberInUseError(draft.review_request)
            except ObjectDoesNotExist:
                pass

            try:
                review_request = self.get(changenum=int(commit_id),
                                          repository=repository)
                raise ChangeNumberInUseError(review_request)
            except (ObjectDoesNotExist, TypeError, ValueError):
                pass

        if create_with_history:
            if repository is None:
                raise ValueError('create_with_history requires a repository.')
            elif create_from_commit_id:
                raise ValueError(
                    'create_from_commit_id and create_with_history cannot '
                    'both be set to True.')
            elif not repository.scmtool_class.supports_history:
                raise ValueError(
                    'This repository does not support review requests created '
                    'with history.')

        # Create the review request. We're not going to actually save this
        # until we're confident we have all the data we need.
        review_request = self.model(submitter=user,
                                    status='P',
                                    public=False,
                                    repository=repository,
                                    diffset_history=DiffSetHistory(),
                                    local_site=local_site)

        review_request.created_with_history = create_with_history

        if commit_id:
            review_request.commit = commit_id

        review_request.validate_unique()

        draft = None

        if commit_id and create_from_commit_id:
            try:
                draft = ReviewRequestDraft(review_request=review_request)
                draft.update_from_commit_id(commit_id)
            except Exception as e:
                logging.exception(
                    'Unable to update new review request from '
                    'commit ID %s on repository ID=%s: %s', commit_id,
                    repository.pk, e)
                raise

        # Now that we've guaranteed we have everything needed for this review
        # request, we can save all related objects and re-attach (since the
        # "None" IDs are cached).
        review_request.diffset_history.save()
        review_request.diffset_history = review_request.diffset_history
        review_request.save()

        if draft:
            draft.review_request = review_request
            draft.save()

            draft.add_default_reviewers()

        if local_site:
            # We want to atomically set the local_id to be a monotonically
            # increasing ID unique to the local_site. This isn't really
            # possible in django's DB layer, so we have to drop back to pure
            # SQL and then reload the model.
            from reviewboard.reviews.models import ReviewRequest

            with transaction.atomic():
                # TODO: Use the cursor as a context manager when we move over
                # to Django 1.7+.
                db = router.db_for_write(ReviewRequest)
                cursor = connections[db].cursor()
                cursor.execute(
                    'UPDATE %(table)s SET'
                    '  local_id = COALESCE('
                    '    (SELECT MAX(local_id) from'
                    '      (SELECT local_id FROM %(table)s'
                    '        WHERE local_site_id = %(local_site_id)s) as x'
                    '      ) + 1,'
                    '    1),'
                    '  local_site_id = %(local_site_id)s'
                    '    WHERE %(table)s.id = %(id)s' % {
                        'table': ReviewRequest._meta.db_table,
                        'local_site_id': local_site.pk,
                        'id': review_request.pk,
                    })
                cursor.close()

            review_request.local_id = (ReviewRequest.objects.filter(
                pk=review_request.pk).values_list('local_id', flat=True)[0])

        # Ensure that a draft exists, so that users will be prompted to publish
        # the new review request.
        ReviewRequestDraft.create(review_request)

        return review_request
Example #48
0
    def create(self, user, repository, commit_id=None, local_site=None,
               create_from_commit_id=False):
        """
        Creates a new review request, optionally filling in fields based off
        a commit ID.
        """
        from reviewboard.reviews.models import ReviewRequestDraft

        if commit_id:
            # Try both the new commit_id and old changenum versions
            try:
                review_request = self.get(commit_id=commit_id,
                                          repository=repository)
                raise ChangeNumberInUseError(review_request)
            except ObjectDoesNotExist:
                pass

            try:
                draft = ReviewRequestDraft.objects.get(
                    commit_id=commit_id,
                    review_request__repository=repository)
                raise ChangeNumberInUseError(draft.review_request)
            except ObjectDoesNotExist:
                pass

            try:
                review_request = self.get(changenum=int(commit_id),
                                          repository=repository)
                raise ChangeNumberInUseError(review_request)
            except (ObjectDoesNotExist, TypeError, ValueError):
                pass

        # Create the review request. We're not going to actually save this
        # until we're confident we have all the data we need.
        review_request = self.model(
            submitter=user,
            status='P',
            public=False,
            repository=repository,
            diffset_history=DiffSetHistory(),
            local_site=local_site)

        if commit_id:
            review_request.commit = commit_id

        review_request.validate_unique()

        draft = None

        if commit_id and create_from_commit_id:
            try:
                draft = ReviewRequestDraft(review_request=review_request)
                draft.update_from_commit_id(commit_id)
            except Exception as e:
                logging.exception('Unable to update new review request from '
                                  'commit ID %s on repository ID=%s: %s',
                                  commit_id, repository.pk, e)
                raise

        # Now that we've guaranteed we have everything needed for this review
        # request, we can save all related objects and re-attach (since the
        # "None" IDs are cached).
        review_request.diffset_history.save()
        review_request.diffset_history = review_request.diffset_history
        review_request.save()

        if draft:
            draft.review_request = review_request
            draft.save()

            draft.add_default_reviewers()

        if local_site:
            # We want to atomically set the local_id to be a monotonically
            # increasing ID unique to the local_site. This isn't really
            # possible in django's DB layer, so we have to drop back to pure
            # SQL and then reload the model.
            from reviewboard.reviews.models import ReviewRequest

            with transaction.atomic():
                # TODO: Use the cursor as a context manager when we move over
                # to Django 1.7+.
                db = router.db_for_write(ReviewRequest)
                cursor = connections[db].cursor()
                cursor.execute(
                    'UPDATE %(table)s SET'
                    '  local_id = COALESCE('
                    '    (SELECT MAX(local_id) from'
                    '      (SELECT local_id FROM %(table)s'
                    '        WHERE local_site_id = %(local_site_id)s) as x'
                    '      ) + 1,'
                    '    1),'
                    '  local_site_id = %(local_site_id)s'
                    '    WHERE %(table)s.id = %(id)s' % {
                        'table': ReviewRequest._meta.db_table,
                        'local_site_id': local_site.pk,
                        'id': review_request.pk,
                    })
                cursor.close()

            review_request.local_id = (
                ReviewRequest.objects.filter(pk=review_request.pk)
                .values_list('local_id', flat=True)[0]
            )

        # Ensure that a draft exists, so that users will be prompted to publish
        # the new review request.
        ReviewRequestDraft.create(review_request)

        return review_request
Example #49
0
    def test_get(self):
        """Testing the GET review-requests/<id>/changes/<id>/ API"""
        def write_fields(obj, index):
            for field, data in six.iteritems(test_data):
                value = data[index]

                if isinstance(value, list) and field not in model_fields:
                    value = ','.join(value)

                if field == 'diff':
                    field = 'diffset'

                setattr(obj, field, value)

        changedesc_text = 'Change description text'
        user1, user2 = User.objects.all()[:2]
        group1 = Group.objects.create(name='group1')
        group2 = Group.objects.create(name='group2')
        repository = self.create_repository()
        diff1 = self.create_diffset(revision=1, repository=repository)
        diff2 = self.create_diffset(revision=2, repository=repository)
        old_screenshot_caption = 'old screenshot'
        new_screenshot_caption = 'new screenshot'
        screenshot1 = Screenshot.objects.create()
        screenshot2 = Screenshot.objects.create()
        screenshot3 = Screenshot.objects.create(caption=old_screenshot_caption)

        for screenshot in [screenshot1, screenshot2, screenshot3]:
            with open(self._getTrophyFilename(), 'r') as f:
                screenshot.image.save('foo.png', File(f), save=True)

        test_data = {
            'summary': ('old summary', 'new summary', None, None),
            'description': ('old description', 'new description', None, None),
            'testing_done':
            ('old testing done', 'new testing done', None, None),
            'branch': ('old branch', 'new branch', None, None),
            'bugs_closed': (['1', '2', '3'], ['2', '3', '4'], ['1'], ['4']),
            'target_people': ([user1], [user2], [user1], [user2]),
            'target_groups': ([group1], [group2], [group1], [group2]),
            'screenshots':
            ([screenshot1,
              screenshot3], [screenshot2,
                             screenshot3], [screenshot1], [screenshot2]),
            'diff': (diff1, diff2, None, diff2),
        }
        model_fields = ('target_people', 'target_groups', 'screenshots',
                        'diff')

        # Set the initial data on the review request.
        r = self.create_review_request(submitter=self.user)
        write_fields(r, 0)
        r.publish(self.user)

        # Create some draft data that will end up in the change description.
        draft = ReviewRequestDraft.create(r)
        write_fields(draft, 1)

        # Special-case screenshots
        draft.inactive_screenshots = test_data['screenshots'][2]
        screenshot3.draft_caption = new_screenshot_caption
        screenshot3.save()

        draft.changedesc.text = changedesc_text
        draft.changedesc.save()
        draft.save()
        r.publish(self.user)

        # Sanity check the ChangeDescription
        self.assertEqual(r.changedescs.count(), 1)
        change = r.changedescs.get()
        self.assertEqual(change.text, changedesc_text)

        for field, data in six.iteritems(test_data):
            old, new, removed, added = data
            field_data = change.fields_changed[field]

            if field == 'diff':
                # Diff fields are special. They only have "added".
                self.assertEqual(len(field_data['added']), 1)
                self.assertEqual(field_data['added'][0][2], added.pk)
            elif field in model_fields:
                self.assertEqual([item[2] for item in field_data['old']],
                                 [obj.pk for obj in old])
                self.assertEqual([item[2] for item in field_data['new']],
                                 [obj.pk for obj in new])
                self.assertEqual([item[2] for item in field_data['removed']],
                                 [obj.pk for obj in removed])
                self.assertEqual([item[2] for item in field_data['added']],
                                 [obj.pk for obj in added])
            elif isinstance(old, list):
                self.assertEqual(field_data['old'], [[value] for value in old])
                self.assertEqual(field_data['new'], [[value] for value in new])
                self.assertEqual(field_data['removed'],
                                 [[value] for value in removed])
                self.assertEqual(field_data['added'],
                                 [[value] for value in added])
            else:
                self.assertEqual(field_data['old'], [old])
                self.assertEqual(field_data['new'], [new])
                self.assertNotIn('removed', field_data)
                self.assertNotIn('added', field_data)

        self.assertIn('screenshot_captions', change.fields_changed)
        field_data = change.fields_changed['screenshot_captions']
        screenshot_id = six.text_type(screenshot3.pk)
        self.assertIn(screenshot_id, field_data)
        self.assertIn('old', field_data[screenshot_id])
        self.assertIn('new', field_data[screenshot_id])
        self.assertEqual(field_data[screenshot_id]['old'][0],
                         old_screenshot_caption)
        self.assertEqual(field_data[screenshot_id]['new'][0],
                         new_screenshot_caption)

        # Now confirm with the API
        rsp = self.api_get(get_change_list_url(r),
                           expected_mimetype=change_list_mimetype)
        self.assertEqual(rsp['stat'], 'ok')
        self.assertEqual(len(rsp['changes']), 1)

        self.assertEqual(rsp['changes'][0]['id'], change.pk)
        rsp = self.api_get(rsp['changes'][0]['links']['self']['href'],
                           expected_mimetype=change_item_mimetype)
        self.assertEqual(rsp['stat'], 'ok')
        self.assertEqual(rsp['change']['text'], changedesc_text)

        fields_changed = rsp['change']['fields_changed']

        for field, data in six.iteritems(test_data):
            old, new, removed, added = data

            self.assertIn(field, fields_changed)
            field_data = fields_changed[field]

            if field == 'diff':
                self.assertIn('added', field_data)
                self.assertEqual(field_data['added']['id'], added.pk)
            elif field in model_fields:
                self.assertIn('old', field_data)
                self.assertIn('new', field_data)
                self.assertIn('added', field_data)
                self.assertIn('removed', field_data)
                self.assertEqual([item['id'] for item in field_data['old']],
                                 [obj.pk for obj in old])
                self.assertEqual([item['id'] for item in field_data['new']],
                                 [obj.pk for obj in new])
                self.assertEqual(
                    [item['id'] for item in field_data['removed']],
                    [obj.pk for obj in removed])
                self.assertEqual([item['id'] for item in field_data['added']],
                                 [obj.pk for obj in added])
            else:
                self.assertIn('old', field_data)
                self.assertIn('new', field_data)
                self.assertEqual(field_data['old'], old)
                self.assertEqual(field_data['new'], new)

                if isinstance(old, list):
                    self.assertIn('added', field_data)
                    self.assertIn('removed', field_data)

                    self.assertEqual(field_data['added'], added)
                    self.assertEqual(field_data['removed'], removed)

        self.assertIn('screenshot_captions', fields_changed)
        field_data = fields_changed['screenshot_captions']
        self.assertEqual(len(field_data), 1)
        screenshot_data = field_data[0]
        self.assertIn('old', screenshot_data)
        self.assertIn('new', screenshot_data)
        self.assertIn('screenshot', screenshot_data)
        self.assertEqual(screenshot_data['old'], old_screenshot_caption)
        self.assertEqual(screenshot_data['new'], new_screenshot_caption)
        self.assertEqual(screenshot_data['screenshot']['id'], screenshot3.pk)
Example #50
0
    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 _populate_review_request(self):
        now = timezone.now()

        # Create the review request, diffs, attachments, and screenshots.
        self.review_request = self.create_review_request(
            create_repository=True, publish=True)

        self.diffset1 = self.create_diffset(self.review_request)
        self.filediff1 = self.create_filediff(self.diffset1)

        self.diffset2 = self.create_diffset(self.review_request)
        self.filediff2 = self.create_filediff(self.diffset2)

        self.file_attachment1 = \
            self.create_file_attachment(self.review_request)
        self.file_attachment2 = \
            self.create_file_attachment(self.review_request)
        self.inactive_file_attachment1 = \
            self.create_file_attachment(self.review_request, active=False)

        self.screenshot1 = self.create_screenshot(self.review_request)
        self.screenshot2 = self.create_screenshot(self.review_request)
        self.inactive_screenshot1 = \
            self.create_screenshot(self.review_request, active=False)

        # Create a draft for this review request.
        self.draft = ReviewRequestDraft.create(self.review_request)

        # Create some reviews.
        self.review1 = self.create_review(self.review_request,
                                          timestamp=now,
                                          publish=True)
        self.general_comment1 = self.create_general_comment(
            self.review1, issue_opened=True, issue_status=BaseComment.OPEN)
        self.diff_comment1 = self.create_diff_comment(
            self.review1,
            self.filediff1,
            issue_opened=True,
            issue_status=BaseComment.RESOLVED)
        self.file_attachment_comment1 = self.create_file_attachment_comment(
            self.review1,
            self.file_attachment1,
            issue_opened=True,
            issue_status=BaseComment.DROPPED)
        self.screenshot_comment1 = self.create_screenshot_comment(
            self.review1, self.screenshot1, issue_opened=False)

        self.review2 = self.create_review(self.review_request,
                                          timestamp=now + timedelta(days=3),
                                          publish=True)
        self.general_comment2 = self.create_general_comment(
            self.review2, issue_opened=True, issue_status=BaseComment.OPEN)
        self.diff_comment2 = self.create_diff_comment(
            self.review2,
            self.filediff2,
            issue_opened=True,
            issue_status=BaseComment.RESOLVED)
        self.file_attachment_comment2 = self.create_file_attachment_comment(
            self.review2,
            self.file_attachment2,
            issue_opened=True,
            issue_status=BaseComment.DROPPED)
        self.screenshot_comment2 = self.create_screenshot_comment(
            self.review2, self.screenshot2, issue_opened=False)

        # Create some change descriptions.
        self.changedesc1 = self.review_request.changedescs.create(
            timestamp=now + timedelta(days=2), public=True)
        self.changedesc2 = self.review_request.changedescs.create(
            timestamp=now + timedelta(days=4), public=True)

        # Create some status updates.
        self.status_update1 = self.create_status_update(self.review_request)
        self.status_update2 = self.create_status_update(self.review_request)
Example #52
0
    def create(self, filediff=None):
        file = self.files['path']
        caption = self.cleaned_data['caption'] or file.name

        # There are several things that can go wrong with browser-provided
        # mimetypes. In one case (bug 3427), Firefox on Linux Mint was
        # providing a mimetype that looked like 'text/text/application/pdf',
        # which is unparseable. IE also has a habit of setting any unknown file
        # type to 'application/octet-stream', rather than just choosing not to
        # provide a mimetype. In the case where what we get from the browser
        # is obviously wrong, try to guess.
        if (file.content_type and len(file.content_type.split('/')) == 2
                and file.content_type != 'application/octet-stream'):
            mimetype = file.content_type
        else:
            mimetype = self._guess_mimetype(file)

        filename = '%s__%s' % (uuid4(), file.name)

        if self.cleaned_data['attachment_history'] is None:
            # This is a new file: create a new FileAttachmentHistory for it
            attachment_history = FileAttachmentHistory()
            attachment_revision = 1

            attachment_history.display_position = \
                FileAttachmentHistory.compute_next_display_position(
                    self.review_request)
            attachment_history.save()
            self.review_request.file_attachment_histories.add(
                attachment_history)
        else:
            attachment_history = self.cleaned_data['attachment_history']

            try:
                latest = attachment_history.file_attachments.latest()
            except FileAttachment.DoesNotExist:
                latest = None

            if latest is None:
                # This should theoretically never happen, but who knows.
                attachment_revision = 1
            elif latest.review_request.exists():
                # This is a new update in the draft.
                attachment_revision = latest.attachment_revision + 1
            else:
                # The most recent revision is part of the same draft. Delete it
                # and replace with the newly uploaded file.
                attachment_revision = latest.attachment_revision
                latest.delete()

        attachment_kwargs = {
            'attachment_history': attachment_history,
            'attachment_revision': attachment_revision,
            'caption': '',
            'draft_caption': caption,
            'orig_filename': os.path.basename(file.name),
            'mimetype': mimetype,
        }

        if filediff:
            file_attachment = FileAttachment.objects.create_from_filediff(
                filediff, save=False, **attachment_kwargs)
        else:
            file_attachment = FileAttachment(**attachment_kwargs)

        file_attachment.file.save(filename, file, save=True)

        draft = ReviewRequestDraft.create(self.review_request)
        draft.file_attachments.add(file_attachment)
        draft.save()

        return file_attachment