Exemple #1
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        webhook_endpoint_url = build_server_url(local_site_reverse(
            'bitbucket-hooks-close-submitted',
            local_site=repository.local_site,
            kwargs={
                'repository_id': repository.pk,
                'hosting_service_id': repository.hosting_account.service_name,
                'hooks_uuid': repository.get_or_create_hooks_uuid(),
            }))
        add_webhook_url = (
            'https://bitbucket.org/%s/%s/admin/hooks?service=POST&url=%s'
            % (self._get_repository_owner(repository),
               self._get_repository_name(repository),
               webhook_endpoint_url))

        example_id = 123
        example_url = build_server_url(local_site_reverse(
            'review-request-detail',
            local_site=repository.local_site,
            kwargs={
                'review_request_id': example_id,
            }))

        return render_to_string(
            'hostingsvcs/bitbucket/repo_hook_instructions.html',
            RequestContext(request, {
                'example_id': example_id,
                'example_url': example_url,
                'repository': repository,
                'server_url': get_server_url(),
                'add_webhook_url': add_webhook_url,
            }))
    def test_get_comment_thumbnail_diff(self):
        """Testing ImageReviewUI.get_comment_thumbnail for an image diff
        comment
        """
        diff_attachment = self.create_file_attachment(self.review_request)

        ui = ImageReviewUI(self.review_request, self.attachment)
        ui.set_diff_against(diff_attachment)

        comment = self.create_file_attachment_comment(self.review,
                                                      self.attachment,
                                                      diff_attachment,
                                                      extra_fields={
                                                          'x': 0,
                                                          'y': 0,
                                                          'width': 1,
                                                          'height': 1,
                                                      })
        thumbnail = ui.get_comment_thumbnail(comment)

        self.assertHTMLEqual(
            thumbnail, '<div class="image-review-ui-diff-thumbnail">'
            '<img class="orig-image" src="%s" width="1" height="1" alt="%s" />'
            '<img class="modified-image" src="%s" width="1" height="1"'
            ' alt="%s" />'
            '</div>' %
            (build_server_url(crop_image(diff_attachment.file, 0, 0, 1,
                                         1)), comment.text,
             build_server_url(crop_image(self.attachment.file, 0, 0, 1,
                                         1)), comment.text))
Exemple #3
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        webhook_endpoint_url = build_server_url(local_site_reverse(
            'bitbucket-hooks-close-submitted',
            local_site=repository.local_site,
            kwargs={
                'repository_id': repository.pk,
                'hosting_service_id': repository.hosting_account.service_name,
                'hooks_uuid': repository.get_or_create_hooks_uuid(),
            }))
        add_webhook_url = (
            'https://bitbucket.org/%s/%s/admin/hooks?service=POST&url=%s'
            % (self._get_repository_owner(repository),
               self._get_repository_name(repository),
               webhook_endpoint_url))

        example_id = 123
        example_url = build_server_url(local_site_reverse(
            'review-request-detail',
            local_site=repository.local_site,
            kwargs={
                'review_request_id': example_id,
            }))

        return render_to_string(
            'hostingsvcs/bitbucket/repo_hook_instructions.html',
            RequestContext(request, {
                'example_id': example_id,
                'example_url': example_url,
                'repository': repository,
                'server_url': get_server_url(),
                'add_webhook_url': add_webhook_url,
            }))
Exemple #4
0
    def test_get_comment_thumbnail_diff(self):
        """Testing ImageReviewUI.get_comment_thumbnail for an image diff
        comment
        """
        diff_attachment = self.create_file_attachment(self.review_request)

        ui = ImageReviewUI(self.review_request, self.attachment)
        ui.set_diff_against(diff_attachment)

        comment = self.create_file_attachment_comment(
            self.review,
            self.attachment,
            diff_attachment,
            extra_fields={
                'x': 0,
                'y': 0,
                'width': 1,
                'height': 1,
            })
        thumbnail = ui.get_comment_thumbnail(comment)

        self.assertEqual(
            thumbnail,
            '<div class="image-review-ui-diff-thumbnail">'
            '<img class="orig-image" src="%s" width="1" height="1" alt="%s" />'
            '<img class="modified-image" src="%s" width="1" height="1"'
            ' alt="%s" />'
            '</div>'
            % (build_server_url(crop_image(diff_attachment.file, 0, 0, 1, 1)),
               comment.text,
               build_server_url(crop_image(self.attachment.file, 0, 0, 1, 1)),
               comment.text)
        )
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        webhook_endpoint_url = build_server_url(local_site_reverse(
            'googlecode-hooks-close-submitted',
            local_site=repository.local_site,
            kwargs={
                'repository_id': repository.pk,
                'hosting_service_id': repository.hosting_account.service_name,
                'hooks_uuid': repository.get_or_create_hooks_uuid(),
            }))
        add_webhook_url = (
            'https://code.google.com/p/%s/adminSource'
            % repository.extra_data['googlecode_project_name'])

        example_id = 123
        example_url = build_server_url(local_site_reverse(
            'review-request-detail',
            local_site=repository.local_site,
            kwargs={
                'review_request_id': example_id,
            }))

        return render_to_string(
            'hostingsvcs/googlecode/repo_hook_instructions.html',
            RequestContext(request, {
                'example_id': example_id,
                'example_url': example_url,
                'repository': repository,
                'server_url': get_server_url(),
                'add_webhook_url': add_webhook_url,
                'webhook_endpoint_url': webhook_endpoint_url,
            }))
Exemple #6
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        webhook_endpoint_url = build_server_url(
            local_site_reverse('googlecode-hooks-close-submitted',
                               local_site=repository.local_site,
                               kwargs={
                                   'repository_id':
                                   repository.pk,
                                   'hosting_service_id':
                                   repository.hosting_account.service_name,
                                   'hooks_uuid':
                                   repository.get_or_create_hooks_uuid(),
                               }))
        add_webhook_url = ('https://code.google.com/p/%s/adminSource' %
                           repository.extra_data['googlecode_project_name'])

        example_id = 123
        example_url = build_server_url(
            local_site_reverse('review-request-detail',
                               local_site=repository.local_site,
                               kwargs={
                                   'review_request_id': example_id,
                               }))

        return render_to_string(
            'hostingsvcs/googlecode/repo_hook_instructions.html',
            RequestContext(
                request, {
                    'example_id': example_id,
                    'example_url': example_url,
                    'repository': repository,
                    'server_url': get_server_url(),
                    'add_webhook_url': add_webhook_url,
                    'webhook_endpoint_url': webhook_endpoint_url,
                }))
Exemple #7
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        plan = repository.extra_data["repository_plan"]
        add_webhook_url = urljoin(
            self.account.hosting_url or "https://github.com/",
            "%s/%s/settings/hooks/new"
            % (
                self._get_repository_owner_raw(plan, repository.extra_data),
                self._get_repository_name_raw(plan, repository.extra_data),
            ),
        )

        webhook_endpoint_url = build_server_url(
            local_site_reverse(
                "github-hooks-close-submitted",
                local_site=repository.local_site,
                kwargs={"repository_id": repository.pk, "hosting_service_id": repository.hosting_account.service_name},
            )
        )

        return render_to_string(
            "hostingsvcs/github/repo_hook_instructions.html",
            RequestContext(
                request,
                {
                    "repository": repository,
                    "server_url": get_server_url(),
                    "add_webhook_url": add_webhook_url,
                    "webhook_endpoint_url": webhook_endpoint_url,
                    "hook_uuid": repository.get_or_create_hooks_uuid(),
                },
            ),
        )
Exemple #8
0
def preview_password_changed_email(
        request,
        text_template_name='notifications/password_changed.txt',
        html_template_name='notifications/password_changed.html'):
    if not settings.DEBUG:
        raise Http404

    format = request.GET.get('format', 'html')

    if format == 'text':
        template_name = text_template_name
        mimetype = 'text/plain'
    elif format == 'html':
        template_name = html_template_name
        mimetype = 'text/html'
    else:
        raise Http404

    api_token_url = ('%s#api-tokens' %
                     build_server_url(reverse('user-preferences')))

    return HttpResponse(render_to_string(
        template_name,
        RequestContext(
            request, {
                'api_token_url': api_token_url,
                'has_api_tokens': request.user.webapi_tokens.exists(),
                'server_url': get_server_url(),
                'user': request.user,
            })),
                        content_type=mimetype)
Exemple #9
0
    def get_absolute_url(self):
        url = self.file.url

        if url.startswith('http:') or url.startswith('https:'):
            return url

        return build_server_url(url)
Exemple #10
0
    def get_absolute_url(self):
        url = self.file.url

        if url.startswith('http:') or url.startswith('https:'):
            return url

        return build_server_url(url)
Exemple #11
0
    def get_comment_thumbnail(self, comment):
        try:
            x = int(comment.extra_data['x'])
            y = int(comment.extra_data['y'])
            width = int(comment.extra_data['width'])
            height = int(comment.extra_data['height'])
        except (KeyError, ValueError):
            # This may be a comment from before we had review UIs. Or,
            # corrupted data. Either way, don't display anything.
            return None

        image_url = crop_image(comment.file_attachment.file, x, y, width,
                               height)

        if not urlparse(image_url).netloc:
            image_url = build_server_url(image_url)

        image_html = (
            '<img class="modified-image" src="%s" width="%s" height="%s" '
            'alt="%s" />' % (image_url, width, height, escape(comment.text)))

        if comment.diff_against_file_attachment_id:
            diff_against_image_url = crop_image(
                comment.diff_against_file_attachment.file, x, y, width, height)

            diff_against_image_html = (
                '<img class="orig-image" src="%s" width="%s" '
                'height="%s" alt="%s" />' %
                (diff_against_image_url, width, height, escape(comment.text)))

            return ('<div class="image-review-ui-diff-thumbnail">%s%s</div>' %
                    (diff_against_image_html, image_html))
        else:
            return image_html
Exemple #12
0
def prepare_user_registered_mail(user):
    """Prepare an e-mail to the administrators notifying of a new user.

    Args:
        user (django.contrib.auth.models.User):
            The user who registered.

    Returns:
        EmailMessage:
        The generated e-mail.
    """
    subject = "New Review Board user registration for %s" % user.username

    context = {
        'site_url': _get_server_base_url(),
        'user': user,
        'user_url': build_server_url(reverse('admin:auth_user_change',
                                             args=(user.id,))),
    }

    text_message = render_to_string('notifications/new_user_email.txt',
                                    context)
    html_message = render_to_string('notifications/new_user_email.html',
                                    context)

    return EmailMessage(
        subject=subject.strip(),
        text_body=text_message,
        html_body=html_message,
        from_email=settings.DEFAULT_FROM_EMAIL,
        sender=settings.DEFAULT_FROM_EMAIL,
        to=[
            build_email_address(full_name=admin[0], email=admin[1])
            for admin in settings.ADMINS
        ])
Exemple #13
0
    def get_absolute_url(self):
        """Return the absolute URL to download this file."""
        url = self.file.url

        if url.startswith('http:') or url.startswith('https:'):
            return url

        return build_server_url(url)
Exemple #14
0
    def get_absolute_url(self):
        """Return the absolute URL to download this file."""
        url = self.file.url

        if url.startswith('http:') or url.startswith('https:'):
            return url

        return build_server_url(url)
    def get_review_request_url(self, review_request):
        """Return the absolute URL to a review request.

        Returns:
            unicode:
            The absolute URL to the review request.
        """
        return build_server_url(review_request.get_absolute_url())
    def get_review_request_url(self, review_request):
        """Return the absolute URL to a review request.

        Returns:
            unicode:
            The absolute URL to the review request.
        """
        return build_server_url(review_request.get_absolute_url())
Exemple #17
0
def prepare_webapi_token_mail(webapi_token, op):
    """Return an e-mail message notifying a user about a WebAPI token change.

    Args:
        webapi_token (reviewboard.notifications.models.WebAPIToken):
            The token that was created, updated, or deleted.

        op (unicode):
            The operation on the token. This is one of:

            * ``'created'``
            * ``'updated'``
            * ``'deleted'``

    Returns:
        EmailMessage:
        The genereated e-mail.
    """
    if op == 'created':
        subject = 'New Review Board API token created'
        template_name = 'notifications/api_token_created'
    elif op == 'updated':
        subject = 'Review Board API token updated'
        template_name = 'notifications/api_token_updated'
    elif op == 'deleted':
        subject = 'Review Board API token deleted'
        template_name = 'notifications/api_token_deleted'
    else:
        raise ValueError('Unexpected op "%s" passed to mail_webapi_token.' %
                         op)

    user = webapi_token.user
    user_email = build_email_address_for_user(user)

    context = {
        'api_token':
        webapi_token,
        'api_token_url':
        '%s#api-tokens' % build_server_url(reverse('user-preferences')),
        'partial_token':
        '%s...' % webapi_token.token[:10],
        'user':
        user,
        'site_url':
        get_server_url(),
    }

    text_message = render_to_string('%s.txt' % template_name, context)
    html_message = render_to_string('%s.html' % template_name, context)

    return EmailMessage(subject=subject,
                        text_body=text_message,
                        html_body=html_message,
                        from_email=settings.SERVER_EMAIL,
                        sender=settings.SERVER_EMAIL,
                        to=[user_email])
    def get_absolute_url(cls):
        """Return the absolute URL of the page.

        Returns:
            unicode:
            The absolute URL of the page.
        """
        assert cls.page_id
        return ('%s#%s' %
                (build_server_url(reverse('user-preferences')), cls.page_id))
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks.

        Args:
            request (django.http.HttpRequest):
                The HTTP request from the client.

            repository (reviewboard.scmtools.models.Repository):
                The repository to show instructions for.

        Returns:
            django.http.HttpResponse:
            The hook installation instructions rendered to the contents of an
            HTTP response.
        """
        example_id = 123
        example_url = build_server_url(
            local_site_reverse('review-request-detail',
                               local_site=repository.local_site,
                               kwargs={
                                   'review_request_id': example_id,
                               }))

        hook_uuid = repository.get_or_create_hooks_uuid()
        close_url = build_server_url(
            local_site_reverse('rbgateway-hooks-close-submitted',
                               kwargs={
                                   'repository_id': repository.pk,
                                   'hosting_service_id': 'rbgateway',
                               },
                               local_site=repository.local_site))

        return render_to_string(
            template_name='hostingsvcs/rb-gateway/repo_hook_instructions.html',
            request=request,
            context={
                'example_id': example_id,
                'example_url': example_url,
                'hook_uuid': hook_uuid,
                'close_url': close_url,
                'repo_name': repository.extra_data['rbgateway_repo_name'],
            })
Exemple #20
0
def _get_server_base_url():
    """Return the base URL of the server (without a trailing /).

    Returns:
        unicode:
        The server base URL without a trailing slash.

        For a site at :samp:`{scheme}://example.com/site-root`, this function
        will return :samp:`{scheme}://example.com`.
    """
    return build_server_url('/')[:-1]
Exemple #21
0
    def get_absolute_url(self):
        """Return the absolute URL to download this file."""
        if not self.file:
            return None

        url = self.file.url

        if url.startswith("http:") or url.startswith("https:"):
            return url

        return build_server_url(url)
Exemple #22
0
def _get_server_base_url():
    """Return the base URL of the server (without a trailing /).

    Returns:
        unicode:
        The server base URL without a trailing slash.

        For a site at :samp:`{scheme}://example.com/site-root`, this function
        will return :samp:`{scheme}://example.com`.
    """
    return build_server_url('/')[:-1]
Exemple #23
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks.

        Args:
            request (django.http.HttpRequest):
                The HTTP request from the client.

            repository (reviewboard.scmtools.models.Repository):
                The repository to show instructions for.

        Returns:
            django.http.HttpResponse:
            The hook installation instructions rendered to the contents of an
            HTTP response.
        """
        example_id = 123
        example_url = build_server_url(local_site_reverse(
            'review-request-detail',
            local_site=repository.local_site,
            kwargs={
                'review_request_id': example_id,
            }))

        hook_uuid = repository.get_or_create_hooks_uuid()
        close_url = build_server_url(local_site_reverse(
            'rbgateway-hooks-close-submitted',
            kwargs={
                'repository_id': repository.pk,
                'hosting_service_id': 'rbgateway',
            },
            local_site=repository.local_site))

        return render_to_string(
            'hostingsvcs/rb-gateway/repo_hook_instructions.html',
            RequestContext(request, {
                'example_id': example_id,
                'example_url': example_url,
                'hook_uuid': hook_uuid,
                'close_url': close_url,
                'repo_name': repository.extra_data['rbgateway_repo_name'],
            }))
Exemple #24
0
    def get_comment_thumbnail(self, comment):
        try:
            x = int(comment.extra_data['x'])
            y = int(comment.extra_data['y'])
            width = int(comment.extra_data['width'])
            height = int(comment.extra_data['height'])
        except (KeyError, ValueError):
            # This may be a comment from before we had review UIs. Or,
            # corrupted data. Either way, don't display anything.
            return None

        image_url = crop_image(comment.file_attachment.file,
                               x, y, width, height)

        if not urlparse(image_url).netloc:
            image_url = build_server_url(image_url)

        image_html = (
            '<img class="modified-image" src="%s" width="%s" height="%s" '
            'alt="%s" />'
            % (image_url, width, height, escape(comment.text)))

        if comment.diff_against_file_attachment_id:
            diff_against_image_url = crop_image(
                comment.diff_against_file_attachment.file,
                x, y, width, height)

            if not urlparse(diff_against_image_url).netloc:
                diff_against_image_url = build_server_url(
                    diff_against_image_url)

            diff_against_image_html = (
                '<img class="orig-image" src="%s" width="%s" '
                'height="%s" alt="%s" />'
                % (diff_against_image_url, width, height,
                   escape(comment.text)))

            return ('<div class="image-review-ui-diff-thumbnail">%s%s</div>'
                    % (diff_against_image_html, image_html))
        else:
            return image_html
Exemple #25
0
    def serialize_absolute_url_field(self, obj, request, **kwargs):
        if obj.local_site:
            local_site_name = request._local_site_name
        else:
            local_site_name = None

        return build_server_url(
            local_site_reverse('user-file-attachment',
                               local_site_name=local_site_name,
                               kwargs={
                                   'file_attachment_uuid': obj.uuid,
                                   'username': obj.user.username,
                               }))
Exemple #26
0
    def download_and_compare(self, to_download):
        try:
            data = urlopen(build_server_url(self.directory,
                                            to_download)).read()
        except HTTPError as e:
            # An HTTP 403 is also an acceptable response
            if e.code == 403:
                return True
            else:
                raise e

        with self.storage.open(to_download, 'r') as f:
            return data == f.read()
    def serialize_absolute_url_field(self, obj, request, **kwargs):
        if obj.local_site:
            local_site_name = request._local_site_name
        else:
            local_site_name = None

        return build_server_url(local_site_reverse(
            'user-file-attachment',
            local_site_name=local_site_name,
            kwargs={
                'file_attachment_uuid': obj.uuid,
                'username': obj.user.username,
            }))
Exemple #28
0
    def get_absolute_url(cls):
        """Return the absolute URL of the page.

        Returns:
            unicode:
            The absolute URL of the page.
        """
        assert cls.page_id
        return (
            '%s#%s'
            % (build_server_url(reverse('user-preferences')),
               cls.page_id)
        )
    def download_and_compare(self, to_download):
        try:
            data = urlopen(build_server_url(self.directory,
                                            to_download)).read()
        except HTTPError as e:
            # An HTTP 403 is also an acceptable response
            if e.code == 403:
                return True
            else:
                raise e

        with self.storage.open(to_download, 'r') as f:
            return data == f.read()
Exemple #30
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        plan = repository.extra_data['repository_plan']
        add_webhook_url = urljoin(
            self.account.hosting_url or 'https://github.com/',
            '%s/%s/settings/hooks/new' %
            (self._get_repository_owner_raw(plan, repository.extra_data),
             self._get_repository_name_raw(plan, repository.extra_data)))

        webhook_endpoint_url = build_server_url(
            local_site_reverse('github-hooks-close-submitted',
                               local_site=repository.local_site,
                               kwargs={
                                   'repository_id':
                                   repository.pk,
                                   'hosting_service_id':
                                   repository.hosting_account.service_name,
                               }))

        example_id = 123
        example_url = build_server_url(
            local_site_reverse('review-request-detail',
                               local_site=repository.local_site,
                               kwargs={
                                   'review_request_id': example_id,
                               }))

        return render_to_string(
            'hostingsvcs/github/repo_hook_instructions.html',
            RequestContext(
                request, {
                    'example_id': example_id,
                    'example_url': example_url,
                    'repository': repository,
                    'server_url': get_server_url(),
                    'add_webhook_url': add_webhook_url,
                    'webhook_endpoint_url': webhook_endpoint_url,
                    'hook_uuid': repository.get_or_create_hooks_uuid(),
                }))
    def _on_review_published(self, user, review, to_submitter_only, **kwargs):
        """Handler for when a review is published.

        Posts an entry to any configured I Done This teams when a review is
        published.

        Args:
            user (django.contrib.auth.models.User):
                The user who published the review.

            review (reviewboard.reviews.models.review.Review):
                The review that was published.

            to_submitter_only (boolean):
                Whether the review should be sent only to the review request
                submitter.

            **kwargs (dict):
                Additional keyword arguments passed to the handler.
        """
        if to_submitter_only:
            return

        num_issues = 0

        for comment in review.get_all_comments():
            if (comment.issue_opened and
                comment.issue_status == BaseComment.OPEN):
                num_issues += 1

        if review.ship_it:
            if num_issues == 0:
                entry_type = entries.REVIEW_PUBLISHED_SHIPIT
            elif num_issues == 1:
                entry_type = entries.REVIEW_PUBLISHED_SHIPIT_ISSUE
            else:
                entry_type = entries.REVIEW_PUBLISHED_SHIPIT_ISSUES
        else:
            if num_issues == 0:
                entry_type = entries.REVIEW_PUBLISHED
            elif num_issues == 1:
                entry_type = entries.REVIEW_PUBLISHED_ISSUE
            else:
                entry_type = entries.REVIEW_PUBLISHED_ISSUES

        self.post_entry(entry_type=entry_type,
                        user=user,
                        review_request=review.review_request,
                        signal_name='review_published',
                        url=build_server_url(review.get_absolute_url()),
                        num_issues=num_issues)
    def _on_review_published(self, user, review, to_owner_only, **kwargs):
        """Handler for when a review is published.

        Posts an entry to any configured I Done This teams when a review is
        published.

        Args:
            user (django.contrib.auth.models.User):
                The user who published the review.

            review (reviewboard.reviews.models.review.Review):
                The review that was published.

            to_owner_only (boolean):
                Whether the review should be sent only to the review request
                owner.

            **kwargs (dict):
                Additional keyword arguments passed to the handler.
        """
        if to_owner_only:
            return

        num_issues = 0

        for comment in review.get_all_comments():
            if (comment.issue_opened and
                comment.issue_status == BaseComment.OPEN):
                num_issues += 1

        if review.ship_it:
            if num_issues == 0:
                entry_type = entries.REVIEW_PUBLISHED_SHIPIT
            elif num_issues == 1:
                entry_type = entries.REVIEW_PUBLISHED_SHIPIT_ISSUE
            else:
                entry_type = entries.REVIEW_PUBLISHED_SHIPIT_ISSUES
        else:
            if num_issues == 0:
                entry_type = entries.REVIEW_PUBLISHED
            elif num_issues == 1:
                entry_type = entries.REVIEW_PUBLISHED_ISSUE
            else:
                entry_type = entries.REVIEW_PUBLISHED_ISSUES

        self.post_entry(entry_type=entry_type,
                        user=user,
                        review_request=review.review_request,
                        signal_name='review_published',
                        url=build_server_url(review.get_absolute_url()),
                        num_issues=num_issues)
Exemple #33
0
    def _ensure_absolute(self, url):
        """Return the provided URL as an absolute URL.

        Relative URLs will be prefixed with the site's domain method and domain.

        Returns:
            unicode:
            An absolute URL.
        """
        result = urlparse(url)

        if result.scheme and result.netloc:
            return url

        return build_server_url(url)
Exemple #34
0
    def get_repository_hook_instructions(self, request, repository):
        """Returns instructions for setting up incoming webhooks."""
        plan = repository.extra_data['repository_plan']
        add_webhook_url = urljoin(
            self.account.hosting_url or 'https://github.com/',
            '%s/%s/settings/hooks/new'
            % (self._get_repository_owner_raw(plan, repository.extra_data),
               self._get_repository_name_raw(plan, repository.extra_data)))

        webhook_endpoint_url = build_server_url(local_site_reverse(
            'github-hooks-close-submitted',
            local_site=repository.local_site,
            kwargs={
                'repository_id': repository.pk,
                'hosting_service_id': repository.hosting_account.service_name,
            }))

        example_id = 123
        example_url = build_server_url(local_site_reverse(
            'review-request-detail',
            local_site=repository.local_site,
            kwargs={
                'review_request_id': example_id,
            }))

        return render_to_string(
            'hostingsvcs/github/repo_hook_instructions.html',
            RequestContext(request, {
                'example_id': example_id,
                'example_url': example_url,
                'repository': repository,
                'server_url': get_server_url(),
                'add_webhook_url': add_webhook_url,
                'webhook_endpoint_url': webhook_endpoint_url,
                'hook_uuid': repository.get_or_create_hooks_uuid(),
            }))
Exemple #35
0
    def _ensure_absolute(self, url):
        """Return the provided URL as an absolute URL.

        Relative URLs will be prefixed with the site's domain method and domain.

        Returns:
            unicode:
            An absolute URL.
        """
        result = urlparse(url)

        if result.scheme and result.netloc:
            return url

        return build_server_url(url)
    def test_get_comment_thumbnail(self):
        """Testing ImageReviewUI.get_comment_thumbnail for an image comment"""
        ui = ImageReviewUI(self.review_request, self.attachment)
        comment = self.create_file_attachment_comment(self.review,
                                                      self.attachment,
                                                      extra_fields={
                                                          'x': 0,
                                                          'y': 0,
                                                          'width': 1,
                                                          'height': 1,
                                                      })
        thumbnail = ui.get_comment_thumbnail(comment)

        self.assertHTMLEqual(
            thumbnail,
            '<img class="modified-image" src="%s" width="1" height="1"'
            ' alt="%s" />' % (build_server_url(
                crop_image(self.attachment.file, 0, 0, 1, 1)), comment.text))
Exemple #37
0
    def download_and_compare(self, to_download):
        """Download a file and compare the resulting response to the file.

        This makes sure that when we fetch a file via its URL, the returned
        contents are identical to the file contents. This returns True if the
        file contents match, and False otherwise.
        """
        try:
            data = urlopen(build_server_url(self.directory,
                                            to_download)).read()
        except HTTPError as e:
            # An HTTP 403 is also an acceptable response
            if e.code == 403:
                return True
            else:
                raise e

        with self.storage.open(to_download, 'r') as f:
            return data == f.read()
    def download_and_compare(self, to_download):
        """Download a file and compare the resulting response to the file.

        This makes sure that when we fetch a file via its URL, the returned
        contents are identical to the file contents. This returns True if the
        file contents match, and False otherwise.
        """
        try:
            data = urlopen(build_server_url(self.directory,
                                            to_download)).read()
        except HTTPError as e:
            # An HTTP 403 is also an acceptable response
            if e.code == 403:
                return True
            else:
                raise e

        with self.storage.open(to_download, 'r') as f:
            return data == f.read()
Exemple #39
0
    def test_search_review_request_id(self):
        """Testing search with a review request ID"""
        site = Site.objects.get_current()
        site.domain = 'testserver'
        site.save()

        self.client.login(username='******', password='******')
        user = User.objects.get(username='******')

        review_request = self.create_review_request(publish=True)

        self.assertTrue(review_request.is_accessible_by(user))
        self.reindex()

        # Perform the search.
        response = self.search('%d' % review_request.id)

        self.assertEqual(response.url,
                         build_server_url(review_request.get_absolute_url()))
Exemple #40
0
    def test_search_review_request_id(self):
        """Testing search with a review request ID"""
        site = Site.objects.get_current()
        site.domain = 'testserver'
        site.save()

        self.client.login(username='******', password='******')
        user = User.objects.get(username='******')

        review_request = self.create_review_request(publish=True)

        self.assertTrue(review_request.is_accessible_by(user))
        reindex_search()

        # Perform the search.
        response = self.search('%d' % review_request.id)

        self.assertEqual(response.url,
                         build_server_url(review_request.get_absolute_url()))
    def _on_reply_published(self, user, reply, **kwargs):
        """Handler for when a reply to a review is published.

        Posts an entry to any configured I Done This teams when a reply to a
        review is published.

        Args:
            user (django.contrib.auth.models.User):
                The user who published the reply.

            reply (reviewboard.reviews.models.review.Review):
                The reply that was published.

            **kwargs (dict):
                Additional keyword arguments passed to the handler.
        """
        self.post_entry(entry_type=entries.REPLY_PUBLISHED,
                        user=user,
                        review_request=reply.review_request,
                        signal_name='reply_published',
                        url=build_server_url(reply.get_absolute_url()))
    def _on_reply_published(self, user, reply, **kwargs):
        """Handler for when a reply to a review is published.

        Posts an entry to any configured I Done This teams when a reply to a
        review is published.

        Args:
            user (django.contrib.auth.models.User):
                The user who published the reply.

            reply (reviewboard.reviews.models.review.Review):
                The reply that was published.

            **kwargs (dict):
                Additional keyword arguments passed to the handler.
        """
        self.post_entry(entry_type=entries.REPLY_PUBLISHED,
                        user=user,
                        review_request=reply.review_request,
                        signal_name='reply_published',
                        url=build_server_url(reply.get_absolute_url()))
Exemple #43
0
    def test_get_comment_thumbnail(self):
        """Testing ImageReviewUI.get_comment_thumbnail for an image comment"""
        ui = ImageReviewUI(self.review_request, self.attachment)
        comment = self.create_file_attachment_comment(
            self.review,
            self.attachment,
            extra_fields={
                'x': 0,
                'y': 0,
                'width': 1,
                'height': 1,
            })
        thumbnail = ui.get_comment_thumbnail(comment)

        self.assertEqual(
            thumbnail,
            '<img class="modified-image" src="%s" width="1" height="1"'
            ' alt="%s" />'
            % (build_server_url(crop_image(self.attachment.file, 0, 0, 1, 1)),
               comment.text)
        )
Exemple #44
0
def mail_password_changed(user):
    """Send an e-mail when a user's password changes.

    Args:
        user (django.contrib.auth.model.User):
            The user whose password changed.
    """
    api_token_url = ('%s#api-tokens' %
                     build_server_url(reverse('user-preferences')))
    server_url = get_server_url()

    context = {
        'api_token_url': api_token_url,
        'has_api_tokens': user.webapi_tokens.exists(),
        'server_url': server_url,
        'user': user,
    }

    user_email = build_email_address_for_user(user)
    text_body = render_to_string('notifications/password_changed.txt', context)
    html_body = render_to_string('notifications/password_changed.html',
                                 context)

    message = EmailMessage(
        subject='Password changed for user "%s" on %s' % server_url,
        text_body=text_body,
        html_body=html_body,
        from_email=settings.SERVER_EMAIL,
        sender=settings.SERVER_EMAIL,
        to=user_email,
    )

    try:
        message.send()
    except Exception as e:
        logging.exception('Failed to send password changed email to %s: %s',
                          user.username, e)
    def format_link(self, path, text):
        """Format the given URL and text to be shown in a Slack message.

        This will combine together the parts of the URL (method, domain, path)
        and format it using Slack's URL syntax.

        Args:
            path (unicode):
                The path on the Review Board server.

            text (unicode):
                The text for the link.

        Returns:
            unicode:
            The link for use in Slack.
        """
        # Slack only wants these three entities replaced, rather than
        # all the entities that Django's escape() would attempt to replace.
        text = text.replace('&', '&amp;')
        text = text.replace('<', '&lt;')
        text = text.replace('>', '&gt;')

        return '<%s|%s>' % (build_server_url(path), text)
    def execute(self):
        """Execute the security check.

        This will download each file that was created in ``setUp`` and check
        that the content matches what we expect.

        Returns:
            tuple:
            A tuple of ``(success, error_message)``.
        """
        failed_exts = []

        if not self._using_default_storage():
            return True, None

        for extensions_list, content in self.file_checks:
            for ext in extensions_list:
                try:
                    filename = '%s%s' % (self.FILENAME_PREFIX, ext)
                    url = build_server_url(
                        '%s/%s' % (self.directory.rstrip('/'), filename))

                    if not self.check_file(filename, url):
                        failed_exts.append(ext)
                except Exception as e:
                    return (False, _('Uncaught exception during test: %s') % e)

        if failed_exts:
            return (
                False,
                ngettext(
                    'The web server incorrectly executed this file type: %s',
                    'The web server incorrectly executed these file types: %s',
                    len(failed_exts)) % ', '.join(failed_exts))

        return True, None
Exemple #47
0
def prepare_password_changed_mail(user):
    """Return an e-mail notifying the user that their password changed.

    Args:
        user (django.contrib.auth.models.User):
            The user whose password changed.

    Returns:
        EmailMessage:
        The generated message.
    """
    server_url = get_server_url()

    context = {
        'api_token_url':
        '%s#api-tokens' % build_server_url(reverse('user-preferences')),
        'has_api_tokens':
        user.webapi_tokens.exists(),
        'server_url':
        server_url,
        'user':
        user,
    }

    user_email = build_email_address_for_user(user)
    text_body = render_to_string('notifications/password_changed.txt', context)
    html_body = render_to_string('notifications/password_changed.html',
                                 context)

    return EmailMessage(subject='Password changed for user "%s" on %s' %
                        (user.username, server_url),
                        text_body=text_body,
                        html_body=html_body,
                        from_email=settings.SERVER_EMAIL,
                        sender=settings.SERVER_EMAIL,
                        to=user_email)
Exemple #48
0
def format_template_string(template_string,
                           num_issues=0,
                           review_request=None,
                           review_request_id=None,
                           summary=None,
                           group_tags=None,
                           url=None):
    """Format a template string for an I Done This entry.

    Args:
        template_string (unicode):
            The template string to substitute arguments into.

        num_issues (int, optional):
            Number of issues opened in a review, used for ``${num_issues}``.

        review_request (reviewboard.reviews.models.review_request.
                        ReviewRequest, optional):
            Review request used for the remaining template string arguments.
            Any arguments provided separately will override the review request.

        review_request_id (int, optional):
            Review request ID, used for ``${review_request_id}``.

        summary (unicode, optional):
            Review request summary, used for ``${summary}``.

        group_tags (unicode, optional):
            Reviewer group names as #tags, used for ``${group_tags}``.

        url (unicode, optional):
            URL for the review request, review, or reply, used for ``${url}``.

    Returns:
        unicode:
        Template string with replaced arguments and cleaned whitespace.

    Raises:
        ValueError:
            Raised if the template string is invalid.

        KeyError:
            Raised if the template string contains unrecognized arguments.
    """
    if review_request:
        review_request_id = review_request_id or review_request.display_id
        summary = summary or review_request.summary
        url = url or build_server_url(review_request.get_absolute_url())

        if not group_tags:
            # Tags allow only alphanumeric characters and underscore.
            group_names = review_request.target_groups.values_list('name',
                                                                   flat=True)
            group_tags = ' '.join('#%s' %
                                  INVALID_TAG_CHARS_RE.sub('_', group_name)
                                  for group_name in group_names)

    result = string.Template(template_string).substitute(
        num_issues=num_issues,
        review_request_id=review_request_id,
        summary=summary,
        group_tags=group_tags,
        url=url)

    return MULTIPLE_WHITESPACE_RE.sub(' ', result).strip()
Exemple #49
0
def make_review_request_context(request, review_request, extra_context={},
                                is_diff_view=False):
    """Returns a dictionary for template contexts used for review requests.

    The dictionary will contain the common data that is used for all
    review request-related pages (the review request detail page, the diff
    viewer, and the screenshot pages).

    For convenience, extra data can be passed to this dictionary.
    """
    if review_request.repository:
        upload_diff_form = UploadDiffForm(review_request, request=request)
        scmtool = review_request.repository.get_scmtool()
    else:
        upload_diff_form = None
        scmtool = None

    if 'blocks' not in extra_context:
        extra_context['blocks'] = list(review_request.blocks.all())

    tabs = [
        {
            'text': _('Reviews'),
            'url': review_request.get_absolute_url(),
        },
    ]

    draft = review_request.get_draft(request.user)

    if ((draft and draft.diffset_id) or
        (hasattr(review_request, '_diffsets') and
         len(review_request._diffsets) > 0)):
        has_diffs = True
    else:
        # We actually have to do a query
        has_diffs = DiffSet.objects.filter(
            history__pk=review_request.diffset_history_id).exists()

    if has_diffs:
        tabs.append({
            'active': is_diff_view,
            'text': _('Diff'),
            'url': (
                local_site_reverse(
                    'view-diff',
                    args=[review_request.display_id],
                    local_site=review_request.local_site) +
                '#index-header'),
        })

    review_request_details = extra_context.get('review_request_details',
                                               review_request)
    social_page_description = truncatechars(
        review_request_details.description.replace('\n', ' '),
        300)

    context = dict({
        'mutable_by_user': review_request.is_mutable_by(request.user),
        'status_mutable_by_user':
            review_request.is_status_mutable_by(request.user),
        'review_request': review_request,
        'upload_diff_form': upload_diff_form,
        'scmtool': scmtool,
        'tabs': tabs,
        'social_page_description': social_page_description,
        'social_page_url': build_server_url(request.path, request=request),
    }, **extra_context)

    if ('review_request_visit' not in context and
        request.user.is_authenticated()):
        # The main review request view will already have populated this, but
        # other related views (like the diffviewer) don't.
        context['review_request_visit'] = \
            ReviewRequestVisit.objects.get_or_create(
                user=request.user,
                review_request=review_request)[0]

    return context
    def _on_review_request_published(self, sender, review_request,
                                     changedesc=None, **kwargs):
        """Handle when a review request is published.

        Args:
            sender (object):
                The sender of the signal.

            review_request (reviewboard.reviews.models.review_request.
                            ReviewRequest):
                The review request which was published.

            changedesc (reviewboard.changedescs.models.ChangeDescription,
                        optional):
                The change description associated with the publish.

            **kwargs (dict):
                Additional keyword arguments.
        """
        repository = review_request.repository

        # Only build changes against GitHub repositories.
        if not (repository and
                repository.hosting_account and
                repository.hosting_account.service_name == 'github'):
            return

        diffset = review_request.get_latest_diffset()

        # Don't build any review requests that don't include diffs.
        if not diffset:
            return

        # If this was an update to a review request, make sure that there was a
        # diff update in it.
        if changedesc is not None:
            fields_changed = changedesc.fields_changed

            if ('diff' not in fields_changed or
                'added' not in fields_changed['diff']):
                return

        matching_configs = [
            config
            for config in self.get_configs(review_request.local_site)
            if config.match_conditions(form_cls=self.config_form_cls,
                                       review_request=review_request)
        ]

        if not matching_configs:
            return

        user = self._get_or_create_user()

        scmtool = repository.get_scmtool()
        diff_data = base64.b64encode(scmtool.get_parser('').raw_diff(diffset))

        commit_message = '%s\n\n%s' % (review_request.summary,
                                       review_request.description)
        webhook_url = build_server_url(reverse('travis-ci-webhook'))

        for config in matching_configs:
            status_update = StatusUpdate.objects.create(
                service_id='travis-ci',
                user=user,
                summary='Travis CI',
                description='starting build...',
                state=StatusUpdate.PENDING,
                review_request=review_request,
                change_description=changedesc)

            travis_config = yaml.load(config.get('travis_yml'))

            # Add set-up and patching to the start of the "script" section of
            # the config.
            script = travis_config.get('script', [])

            if not isinstance(script, list):
                script = [script]

            travis_config['script'] = [
                'git fetch --unshallow origin',
                'git checkout %s' % diffset.base_commit_id.encode('utf-8'),
                'echo %s | base64 --decode | patch -p1' % diff_data,
            ] + script

            # Set up webhook notifications.
            notifications = travis_config.get('notifications') or {}
            webhooks = notifications.get('webhooks') or {}

            urls = webhooks.get('urls', [])

            if not isinstance(urls, list):
                urls = [urls]

            urls.append(webhook_url)

            webhooks['urls'] = urls
            webhooks['on_start'] = 'always'

            notifications['webhooks'] = webhooks
            notifications['email'] = False
            travis_config['notifications'] = notifications

            # Add some special data in the environment so that when the
            # webhooks come in, we can find the right status update to update.
            env = travis_config.setdefault('env', {})

            if not isinstance(env, dict):
                env = {
                    'matrix': env,
                }

            global_ = env.setdefault('global', [])

            if not isinstance(global_, list):
                global_ = [global_]

            global_ += [
                'REVIEWBOARD_STATUS_UPDATE_ID=%d' % status_update.pk,
                'REVIEWBOARD_TRAVIS_INTEGRATION_CONFIG_ID=%d' % config.pk,
            ]

            env['global'] = global_

            travis_config['env'] = env

            # Time to kick off the build!
            logger.info('Triggering Travis CI build for review request %s '
                        '(diffset revision %d)',
                        review_request.get_absolute_url(), diffset.revision)
            api = TravisAPI(config)
            repo_slug = self._get_github_repository_slug(repository)
            api.start_build(repo_slug, travis_config, commit_message,
                            config.get('branch_name') or 'master')
Exemple #51
0
def prepare_base_review_request_mail(user,
                                     review_request,
                                     subject,
                                     in_reply_to,
                                     to_field,
                                     cc_field,
                                     template_name_base,
                                     context=None,
                                     extra_headers=None):
    """Return a customized review request e-mail.

    This is intended to be called by one of the ``prepare_{type}_mail``
    functions in this file. This method builds up a common context that all
    review request-related e-mails will use to render their templates, as well
    as handling user preferences regarding e-mail and add adding additional
    headers.

    Args:
        user (django.contrib.auth.models.User):
            The user who is sending the e-mail.

        review_request (reviewboard.reviews.models.review_request.ReviewRequest):
            The review request this e-mail is regarding.

        subject (unicode):
            The e-mail subject line.

        in_reply_to (unicode):
            The e-mail message ID this message is in response to or ``None``.

        to_field (set):
            The set of :py:class:`~django.contrib.auth.models.User` and
            :py:class`~reviewboard.reviews.models.group.Group`s to this e-mail
            will be sent to.

        cc_field (set):
            The set of :py:class:`~django.contrib.auth.models.User` and
            :py:class`~reviewboard.reviews.models.group.Group`s to be CC'ed on
            the e-mail.

        template_name_base (unicode):
            The name of the template to use to generate the e-mail without its
            extension. The plain-text version of the e-mail will append
            ``.txt`` to this and and the rich-text version of the e-mail will
            append ``.html``.

        context (dict, optional):
            Optional additional template rendering context.

        extra_headers (dict, optional):
            Optional additional headers to include.

    Returns:
        EmailMessage:
        The prepared e-mail message.
    """
    user_email = build_email_address_for_user(user)
    to_field = recipients_to_addresses(to_field, review_request.id)
    cc_field = recipients_to_addresses(cc_field, review_request.id) - to_field

    if not user.should_send_own_updates():
        to_field.discard(user_email)
        cc_field.discard(user_email)

    if not to_field and not cc_field:
        # This e-mail would have no recipients, so we won't send it.
        return None

    if not context:
        context = {}

    context.update({
        'user': user,
        'site_url': get_server_url(),
        'review_request': review_request,
    })
    local_site = review_request.local_site

    if local_site:
        context['local_site_name'] = local_site.name

    text_body = render_to_string('%s.txt' % template_name_base, context)
    html_body = render_to_string('%s.html' % template_name_base, context)
    server_url = get_server_url(local_site=local_site)

    headers = MultiValueDict({
        'X-ReviewBoard-URL': [server_url],
        'X-ReviewRequest-URL': [
            build_server_url(review_request.get_absolute_url(),
                             local_site=local_site)
        ],
        'X-ReviewGroup': [
            ', '.join(
                review_request.target_groups.values_list('name', flat=True))
        ],
    })

    if extra_headers:
        if not isinstance(extra_headers, MultiValueDict):
            extra_headers = MultiValueDict(
                (key, [value]) for key, value in six.iteritems(extra_headers))

        headers.update(extra_headers)

    if review_request.repository:
        headers['X-ReviewRequest-Repository'] = review_request.repository.name

    latest_diffset = review_request.get_latest_diffset()

    if latest_diffset:
        modified_files = set()

        for filediff in latest_diffset.files.all():
            if filediff.deleted or filediff.copied or filediff.moved:
                modified_files.add(filediff.source_file)

            if filediff.is_new or filediff.copied or filediff.moved:
                modified_files.add(filediff.dest_file)

        # The following code segment deals with the case where the client adds
        # a significant amount of files with large names. We limit the number
        # of headers; when more than 8192 characters are reached, we stop
        # adding filename headers.
        current_header_length = 0

        for filename in modified_files:
            current_header_length += (HEADER_ADDITIONAL_CHARACTERS_LENGTH +
                                      len(filename))

            if current_header_length > MAX_FILENAME_HEADERS_LENGTH:
                logging.warning(
                    'Unable to store all filenames in the '
                    'X-ReviewBoard-Diff-For headers when sending e-mail for '
                    'review request %s: The header size exceeds the limit of '
                    '%s. Remaining headers have been omitted.',
                    review_request.display_id, MAX_FILENAME_HEADERS_LENGTH)
                break

            headers.appendlist('X-ReviewBoard-Diff-For', filename)

    if settings.DEFAULT_FROM_EMAIL:
        sender = build_email_address(full_name=user.get_full_name(),
                                     email=settings.DEFAULT_FROM_EMAIL)
    else:
        sender = None

    return EmailMessage(subject=subject.strip(),
                        text_body=text_body.encode('utf-8'),
                        html_body=html_body.encode('utf-8'),
                        from_email=user_email,
                        sender=sender,
                        to=list(to_field),
                        cc=list(cc_field),
                        in_reply_to=in_reply_to,
                        headers=headers)
Exemple #52
0
def make_review_request_context(request, review_request, extra_context={},
                                is_diff_view=False):
    """Returns a dictionary for template contexts used for review requests.

    The dictionary will contain the common data that is used for all
    review request-related pages (the review request detail page, the diff
    viewer, and the screenshot pages).

    For convenience, extra data can be passed to this dictionary.
    """
    if review_request.repository:
        upload_diff_form = UploadDiffForm(review_request, request=request)
        scmtool = review_request.repository.get_scmtool()
    else:
        upload_diff_form = None
        scmtool = None

    if 'blocks' not in extra_context:
        extra_context['blocks'] = list(review_request.blocks.all())

    tabs = [
        {
            'text': _('Reviews'),
            'url': review_request.get_absolute_url(),
        },
    ]

    draft = review_request.get_draft(request.user)

    if ((draft and draft.diffset_id) or
        (hasattr(review_request, '_diffsets') and
         len(review_request._diffsets) > 0)):
        has_diffs = True
    else:
        # We actually have to do a query
        has_diffs = DiffSet.objects.filter(
            history__pk=review_request.diffset_history_id).exists()

    if has_diffs:
        tabs.append({
            'active': is_diff_view,
            'text': _('Diff'),
            'url': (
                local_site_reverse(
                    'view-diff',
                    args=[review_request.display_id],
                    local_site=review_request.local_site) +
                '#index_header'),
        })

    siteconfig = SiteConfiguration.objects.get_current()

    review_request_details = extra_context.get('review_request_details',
                                               review_request)
    social_page_description = truncatechars(
        review_request_details.description.replace('\n', ' '),
        300)

    context = dict({
        'mutable_by_user': review_request.is_mutable_by(request.user),
        'status_mutable_by_user':
            review_request.is_status_mutable_by(request.user),
        'review_request': review_request,
        'upload_diff_form': upload_diff_form,
        'scmtool': scmtool,
        'send_email': siteconfig.get('mail_send_review_mail'),
        'tabs': tabs,
        'social_page_description': social_page_description,
        'social_page_url': build_server_url(request.path, request=request),
    }, **extra_context)

    if ('review_request_visit' not in context and
        request.user.is_authenticated()):
        # The main review request view will already have populated this, but
        # other related views (like the diffviewer) don't.
        context['review_request_visit'] = \
            ReviewRequestVisit.objects.get_or_create(
                user=request.user,
                review_request=review_request)[0]

    return context
    def notify_review_or_reply(self, user, review, pre_text, fallback_text,
                               event_name, first_comment=None, **kwargs):
        """Notify Slack for any new posted reviews or replies.

        This performs the common work of notifying configured Slack channels
        when there's a review or a reply.

        Args:
            user (django.contrib.auth.models.User):
                The user who posted the review or reply.

            review (reviewboard.reviews.models.Review):
                The review or reply that was posted.

            pre_text (unicode, optional):
                Text to show before the message attachments.

            fallback_text (unicode, optional):
                Text to show in the fallback text, before the review URL and
                after the review request ID.

            event_name (unicode):
                The name of the event triggering this notification.

            first_comment (reviewboard.reviews.models.BaseComment, optional):
                The first comment in a review, to generate the body message
                from. This is optional, and will be computed if needed.

            **kwargs (dict):
                Other keyword arguments to pass to :py:meth:`notify`.
        """
        review_request = review.review_request
        review_url = build_server_url(review.get_absolute_url())
        fallback_text = '#%s: %s: %s' % (review_request.display_id,
                                         fallback_text, review_url)

        if review.body_top:
            body = review.body_top

            # This is silly to show twice.
            if review.ship_it and body == 'Ship It!':
                body = ''
        else:
            if not first_comment:
                for comment_cls in (Comment, FileAttachmentComment,
                                    ScreenshotComment, GeneralComment):
                    try:
                        first_comment = (
                            comment_cls.objects
                            .filter(review=review)
                            .only('text')
                        )[0]
                        break
                    except IndexError:
                        pass

            if first_comment:
                body = first_comment.text

        self.notify(title=self.get_review_request_title(review_request),
                    title_link=review_url,
                    fallback_text=fallback_text,
                    pre_text=pre_text,
                    body=body,
                    local_site=review.review_request.local_site,
                    review_request=review_request,
                    event_name=event_name,
                    **kwargs)
Exemple #54
0
def prepare_base_review_request_mail(user, review_request, subject,
                                     in_reply_to, to_field, cc_field,
                                     template_name_base, context=None,
                                     extra_headers=None):
    """Return a customized review request e-mail.

    This is intended to be called by one of the ``prepare_{type}_mail``
    functions in this file. This method builds up a common context that all
    review request-related e-mails will use to render their templates, as well
    as handling user preferences regarding e-mail and add adding additional
    headers.

    Args:
        user (django.contrib.auth.models.User):
            The user who is sending the e-mail.

        review_request (reviewboard.reviews.models.review_request.ReviewRequest):
            The review request this e-mail is regarding.

        subject (unicode):
            The e-mail subject line.

        in_reply_to (unicode):
            The e-mail message ID this message is in response to or ``None``.

        to_field (set):
            The set of :py:class:`~django.contrib.auth.models.User` and
            :py:class`~reviewboard.reviews.models.group.Group`s to this e-mail
            will be sent to.

        cc_field (set):
            The set of :py:class:`~django.contrib.auth.models.User` and
            :py:class`~reviewboard.reviews.models.group.Group`s to be CC'ed on
            the e-mail.

        template_name_base (unicode):
            The name of the template to use to generate the e-mail without its
            extension. The plain-text version of the e-mail will append
            ``.txt`` to this and and the rich-text version of the e-mail will
            append ``.html``.

        context (dict, optional):
            Optional additional template rendering context.

        extra_headers (dict, optional):
            Optional additional headers to include.

    Returns:
        EmailMessage:
        The prepared e-mail message.
    """
    user_email = build_email_address_for_user(user)
    to_field = recipients_to_addresses(to_field, review_request.id)
    cc_field = recipients_to_addresses(cc_field, review_request.id) - to_field

    if not user.should_send_own_updates():
        to_field.discard(user_email)
        cc_field.discard(user_email)

    if not to_field and not cc_field:
        # This e-mail would have no recipients, so we won't send it.
        return None

    if not context:
        context = {}

    context.update({
        'user': user,
        'site_url': _get_server_base_url(),
        'review_request': review_request,
    })
    local_site = review_request.local_site

    if local_site:
        context['local_site_name'] = local_site.name

    text_body = render_to_string('%s.txt' % template_name_base, context)
    html_body = render_to_string('%s.html' % template_name_base, context)
    server_url = get_server_url(local_site=local_site)

    headers = MultiValueDict({
        'X-ReviewBoard-URL': [server_url],
        'X-ReviewRequest-URL': [
            build_server_url(review_request.get_absolute_url(),
                             local_site=local_site)
        ],
        'X-ReviewGroup': [', '.join(
            review_request.target_groups.values_list('name', flat=True)
        )],
    })

    if extra_headers:
        if not isinstance(extra_headers, MultiValueDict):
            extra_headers = MultiValueDict(
                (key, [value])
                for key, value in six.iteritems(extra_headers)
            )

        headers.update(extra_headers)

    if review_request.repository:
        headers['X-ReviewRequest-Repository'] = review_request.repository.name

    latest_diffset = review_request.get_latest_diffset()

    if latest_diffset:
        modified_files = set()

        for filediff in latest_diffset.files.all():
            if not filediff.is_new:
                modified_files.add(filediff.source_file)

            if not filediff.deleted:
                modified_files.add(filediff.dest_file)

        # The following code segment deals with the case where the client adds
        # a significant amount of files with large names. We limit the number
        # of headers; when more than 8192 characters are reached, we stop
        # adding filename headers.
        current_header_length = 0

        for filename in modified_files:
            current_header_length += (HEADER_ADDITIONAL_CHARACTERS_LENGTH +
                                      len(filename))

            if current_header_length > MAX_FILENAME_HEADERS_LENGTH:
                logging.warning(
                    'Unable to store all filenames in the '
                    'X-ReviewBoard-Diff-For headers when sending e-mail for '
                    'review request %s: The header size exceeds the limit of '
                    '%s. Remaining headers have been omitted.',
                    review_request.display_id,
                    MAX_FILENAME_HEADERS_LENGTH)
                break

            headers.appendlist('X-ReviewBoard-Diff-For', filename)

    if settings.DEFAULT_FROM_EMAIL:
        sender = build_email_address(full_name=user.get_full_name(),
                                     email=settings.DEFAULT_FROM_EMAIL)
    else:
        sender = None

    return EmailMessage(subject=subject.strip(),
                        text_body=text_body.encode('utf-8'),
                        html_body=html_body.encode('utf-8'),
                        from_email=user_email,
                        sender=sender,
                        to=list(to_field),
                        cc=list(cc_field),
                        in_reply_to=in_reply_to,
                        headers=headers)
    def notify_review_or_reply(self, user, review, pre_text, fallback_text,
                               event_name, first_comment=None, **kwargs):
        """Notify chat application for any new posted reviews or replies.

        This performs the common work of notifying configured channels
        when there's a review or a reply.

        Args:
            user (django.contrib.auth.models.User):
                The user who posted the review or reply.

            review (reviewboard.reviews.models.Review):
                The review or reply that was posted.

            pre_text (unicode, optional):
                Text to show before the message attachments.

            fallback_text (unicode, optional):
                Text to show in the fallback text, before the review URL and
                after the review request ID.

            event_name (unicode):
                The name of the event triggering this notification.

            first_comment (reviewboard.reviews.models.BaseComment, optional):
                The first comment in a review, to generate the body message
                from. This is optional, and will be computed if needed.

            **kwargs (dict):
                Other keyword arguments to pass to :py:meth:`notify`.
        """
        review_request = review.review_request
        review_url = build_server_url(review.get_absolute_url())
        fallback_text = '#%s: %s: %s' % (review_request.display_id,
                                         fallback_text, review_url)

        if review.body_top:
            body = review.body_top

            # This is silly to show twice.
            if review.ship_it and body == 'Ship It!':
                body = ''
        else:
            if not first_comment:
                for comment_cls in (Comment, FileAttachmentComment,
                                    ScreenshotComment, GeneralComment):
                    try:
                        first_comment = (
                            comment_cls.objects
                            .filter(review=review)
                            .only('text')
                        )[0]
                        break
                    except IndexError:
                        pass

            if first_comment:
                body = first_comment.text

        self.notify(title=self.get_review_request_title(review_request),
                    title_link=review_url,
                    fallback_text=fallback_text,
                    pre_text=pre_text,
                    body=body,
                    local_site=review.review_request.local_site,
                    review_request=review_request,
                    event_name=event_name,
                    **kwargs)