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))
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, }))
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, }))
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(), }, ), )
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)
def get_absolute_url(self): url = self.file.url if url.startswith('http:') or url.startswith('https:'): return url return build_server_url(url)
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
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 ])
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 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'], })
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]
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)
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'], }))
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
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, }))
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, }))
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.""" 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)
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 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 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))
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 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()))
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 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) )
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('&', '&') text = text.replace('<', '<') text = text.replace('>', '>') 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
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)
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()
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')
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)
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)
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)