示例#1
0
def file_attachment_comments(context, file_attachment):
    """Returns a JSON array of current comments for a file attachment."""
    comments = []
    user = context.get('user', None)

    for comment in file_attachment.get_comments():
        review = comment.get_review()

        if review and (review.public or review.user == user):
            comments.append({
                'comment_id': comment.id,
                'text': normalize_text_for_edit(user, comment.text,
                                                comment.rich_text),
                'rich_text': comment.rich_text,
                'user': {
                    'username': escape(review.user.username),
                    'name': escape(review.user.get_full_name() or
                                   review.user.username),
                },
                'url': comment.get_review_url(),
                'localdraft': review.user == user and not review.public,
                'review_id': review.id,
                'issue_opened': comment.issue_opened,
                'issue_status': escape(
                    BaseComment.issue_status_to_string(comment.issue_status)),
            })

    return json.dumps(comments)
示例#2
0
    def get_data_attributes(self):
        """Return any data attributes to include in the element.

        By default, this returns nothing.

        Returns:
            dict:
            The data attributes to include in the element.
        """
        attrs = super(BaseTextAreaField, self).get_data_attributes()

        if self.enable_markdown:
            if self.request:
                user = self.request.user
            else:
                user = None

            attrs.update({
                'allow-markdown':
                True,
                'raw-value':
                normalize_text_for_edit(
                    user, self.value,
                    self.should_render_as_markdown(self.value)),
            })

        return attrs
示例#3
0
    def serialize_comment(self, comment):
        """Serializes a comment.

        This will provide information on the comment that may be useful
        to the JavaScript code.

        Subclasses that want to add additional data should generally
        augment the result of this function and not replace it.
        """
        review = comment.get_review()
        user = self.request.user

        return {
            'comment_id': comment.pk,
            'text': normalize_text_for_edit(user, comment.text,
                                            comment.rich_text),
            'rich_text': comment.rich_text,
            'html': markdown_render_conditional(comment.text,
                                                comment.rich_text),
            'user': {
                'username': review.user.username,
                'name': review.user.get_full_name() or review.user.username,
            },
            'url': comment.get_review_url(),
            'localdraft': review.user == user and not review.public,
            'review_id': review.pk,
            'review_request_id': review.review_request_id,
            'issue_opened': comment.issue_opened,
            'issue_status': comment.issue_status_to_string(
                comment.issue_status),
        }
示例#4
0
def _normalize_text_for_edit(context, text, rich_text, escape_js=False):
    text = normalize_text_for_edit(context["request"].user, text, rich_text, escape_html=not escape_js)

    if escape_js:
        text = escapejs(text)

    return text
示例#5
0
def file_attachment_comments(context, file_attachment):
    """Returns a JSON array of current comments for a file attachment."""
    comments = []
    user = context.get('user', None)

    for comment in file_attachment.get_comments():
        review = comment.get_review()

        if review and (review.public or review.user == user):
            comments.append({
                'comment_id': comment.id,
                'text': normalize_text_for_edit(user, comment.text,
                                                comment.rich_text),
                'rich_text': comment.rich_text,
                'user': {
                    'username': escape(review.user.username),
                    'name': escape(review.user.get_full_name() or
                                   review.user.username),
                },
                'url': comment.get_review_url(),
                'localdraft': review.user == user and not review.public,
                'review_id': review.id,
                'issue_opened': comment.issue_opened,
                'issue_status': escape(
                    BaseComment.issue_status_to_string(comment.issue_status)),
            })

    return json.dumps(comments)
示例#6
0
def file_attachment_comments(context, file_attachment):
    """Returns a JSON array of current comments for a file attachment."""
    comments = []
    user = context.get("user", None)

    for comment in file_attachment.get_comments():
        review = comment.get_review()

        if review and (review.public or review.user == user):
            comments.append(
                {
                    "comment_id": comment.id,
                    "text": normalize_text_for_edit(user, comment.text, comment.rich_text),
                    "rich_text": comment.rich_text,
                    "user": {
                        "username": escape(review.user.username),
                        "name": escape(review.user.get_full_name() or review.user.username),
                    },
                    "url": comment.get_review_url(),
                    "localdraft": review.user == user and not review.public,
                    "review_id": review.id,
                    "issue_opened": comment.issue_opened,
                    "issue_status": escape(BaseComment.issue_status_to_string(comment.issue_status)),
                }
            )

    return json.dumps(comments)
示例#7
0
def _normalize_text_for_edit(context, text, rich_text, escape_js=False):
    text = normalize_text_for_edit(context['request'].user, text, rich_text,
                                   escape_html=not escape_js)

    if escape_js:
        text = escapejs(text)

    return text
    def test_normalize_text_for_edit_plain_text_no_escape(self):
        """Testing normalize_text_for_edit with plain text and not
        escaping to HTML
        """
        user = User.objects.create_user('test', '*****@*****.**')
        Profile.objects.create(user=user, default_use_rich_text=False)

        text = normalize_text_for_edit(user, text='< "test" **foo**',
                                       rich_text=True, escape_html=False)
        self.assertEqual(text, '< "test" **foo**')
        self.assertFalse(isinstance(text, SafeText))
    def test_normalize_text_for_edit_plain_text_default_plain_text(self):
        """Testing normalize_text_for_edit with plain text and
        user defaults to plain text
        """
        user = User.objects.create_user('test', '*****@*****.**')
        Profile.objects.create(user=user, default_use_rich_text=False)

        text = normalize_text_for_edit(user, text='< "test" **foo**',
                                       rich_text=True)
        self.assertEqual(text, '< "test" **foo**')
        self.assertTrue(isinstance(text, SafeText))
示例#10
0
    def test_normalize_text_for_edit_plain_text_no_escape(self):
        """Testing normalize_text_for_edit with plain text and not
        escaping to HTML
        """
        user = User.objects.create_user('test', '*****@*****.**')
        Profile.objects.create(user=user, default_use_rich_text=False)

        text = normalize_text_for_edit(user, text='< "test" **foo**',
                                       rich_text=True, escape_html=False)
        self.assertEqual(text, '< "test" **foo**')
        self.assertFalse(isinstance(text, SafeText))
示例#11
0
    def test_normalize_text_for_edit_plain_text_default_plain_text(self):
        """Testing normalize_text_for_edit with plain text and
        user defaults to plain text
        """
        user = User.objects.create_user('test', '*****@*****.**')
        Profile.objects.create(user=user, default_use_rich_text=False)

        text = normalize_text_for_edit(user, text='< "test" **foo**',
                                       rich_text=True)
        self.assertEqual(text, '< "test" **foo**')
        self.assertTrue(isinstance(text, SafeText))
示例#12
0
    def get_data_attributes(self):
        attrs = super(BaseTextAreaField, self).get_data_attributes()

        if self.enable_markdown:
            if self.request:
                user = self.request.user
            else:
                user = None

            attrs.update({
                'allow-markdown': True,
                'raw-value': normalize_text_for_edit(
                    user, self.value,
                    self.should_render_as_markdown(self.value)),
            })

        return attrs
示例#13
0
    def get_data_attributes(self):
        attrs = super(BaseTextAreaField, self).get_data_attributes()

        if self.enable_markdown:
            if self.request:
                user = self.request.user
            else:
                user = None

            attrs.update({
                'allow-markdown': True,
                'raw-value': normalize_text_for_edit(
                    user, self.value,
                    self.should_render_as_markdown(self.value)),
            })

        return attrs
示例#14
0
def reviewable_page_model_data(context):
    """Output JSON-serialized data for a RB.ReviewablePage model.

    The data will be used by :js:class:`RB.ReviewablePage` in order to
    populate the review request and editor with the necessary state.

    Args:
        context (django.template.RequestContext):
            The current template context.

    Returns:
        unicode:
        The resulting JSON-serialized data. This consists of keys that are
        meant to be injected into an existing dictionary.
    """
    request = context['request']
    user = request.user
    review_request = context['review_request']
    review_request_details = context['review_request_details']
    draft = context['draft']
    close_description = context['close_description']
    close_description_rich_text = context['close_description_rich_text']

    if review_request.local_site:
        local_site_prefix = 's/%s/' % review_request.local_site.name
    else:
        local_site_prefix = ''

    # Build data for the RB.ReviewRequest
    if review_request.status == review_request.PENDING_REVIEW:
        state_data = 'PENDING'
    elif review_request.status == review_request.SUBMITTED:
        state_data = 'CLOSE_SUBMITTED'
    elif review_request.status == review_request.DISCARDED:
        state_data = 'CLOSE_DISCARDED'
    else:
        raise ValueError('Unexpected ReviewRequest.status value "%s"' %
                         review_request.status)

    review_request_data = {
        'id':
        review_request.display_id,
        'localSitePrefix':
        local_site_prefix,
        'branch':
        review_request_details.branch,
        'bugsClosed':
        review_request_details.get_bug_list(),
        'closeDescription':
        normalize_text_for_edit(user=user,
                                text=close_description,
                                rich_text=close_description_rich_text,
                                escape_html=False),
        'closeDescriptionRichText':
        close_description_rich_text,
        'description':
        normalize_text_for_edit(
            user=user,
            text=review_request_details.description,
            rich_text=review_request_details.description_rich_text,
            escape_html=False),
        'descriptionRichText':
        review_request_details.description_rich_text,
        'hasDraft':
        draft is not None,
        'lastUpdatedTimestamp':
        review_request.last_updated,
        'public':
        review_request.public,
        'reviewURL':
        review_request.get_absolute_url(),
        'state':
        state_data,
        'summary':
        review_request_details.summary,
        'targetGroups': [{
            'name': group.name,
            'url': group.get_absolute_url(),
        } for group in review_request_details.target_groups.all()],
        'targetPeople': [{
            'username':
            target_user.username,
            'url':
            local_site_reverse('user', args=[target_user], request=request)
        } for target_user in review_request_details.target_people.all()],
        'testingDone':
        normalize_text_for_edit(
            user=user,
            text=review_request_details.testing_done,
            rich_text=review_request_details.testing_done_rich_text,
            escape_html=False),
        'testingDoneRichText':
        review_request_details.testing_done_rich_text,
    }

    if user.is_authenticated():
        review_request_visit = context['review_request_visit']

        if review_request_visit.visibility == review_request_visit.VISIBLE:
            visibility_data = 'VISIBLE'
        elif review_request_visit.visibility == review_request_visit.ARCHIVED:
            visibility_data = 'ARCHIVED'
        elif review_request_visit.visibility == review_request_visit.MUTED:
            visibility_data = 'MUTED'
        else:
            raise ValueError(
                'Unexpected ReviewRequestVisit.visibility value "%s"' %
                review_request_visit.visibility)

        review_request_data['visibility'] = visibility_data

    repository = review_request.repository

    if repository:
        scmtool = repository.get_scmtool()

        review_request_data['repository'] = {
            'id': repository.pk,
            'name': repository.name,
            'scmtoolName': scmtool.name,
            'requiresBasedir': not scmtool.diffs_use_absolute_paths,
            'requiresChangeNumber': scmtool.supports_pending_changesets,
            'supportsPostCommit': repository.supports_post_commit,
        }

        if repository.bug_tracker:
            review_request_data['bugTrackerURL'] = \
                local_site_reverse(
                    'bug_url',
                    args=[review_request.display_id, '--bug_id--'],
                    request=request)

    if draft:
        review_request_data['submitter'] = {
            'title': draft.submitter.username,
            'url': draft.submitter.get_absolute_url(),
        }

    # Build the data for the RB.ReviewRequestEditor.
    editor_data = {
        'closeDescriptionRenderedText':
        _render_markdown(close_description, close_description_rich_text),
        'commits':
        None,
        'hasDraft':
        draft is not None,
        'mutableByUser':
        context['mutable_by_user'],
        'showSendEmail':
        context['send_email'],
        'statusMutableByUser':
        context['status_mutable_by_user'],
    }

    if review_request.created_with_history:
        diffset = review_request_details.get_latest_diffset()
        editor_data['commits'] = [
            commit.serialize() for commit in diffset.commits.all()
        ]

    # Build extra data for the RB.ReviewRequest.
    extra_review_request_draft_data = {}

    if draft and draft.changedesc:
        extra_review_request_draft_data.update({
            'changeDescription':
            normalize_text_for_edit(user=user,
                                    text=draft.changedesc.text,
                                    rich_text=draft.changedesc.rich_text,
                                    escape_html=False),
            'changeDescriptionRichText':
            draft.changedesc.rich_text,
        })

        editor_data['changeDescriptionRenderedText'] = _render_markdown(
            draft.changedesc.text, draft.changedesc.rich_text)

        if draft.diffset:
            extra_review_request_draft_data['interdiffLink'] = \
                local_site_reverse(
                    'view-interdiff',
                    args=[
                        review_request.display_id,
                        draft.diffset.revision - 1,
                        draft.diffset.revision
                    ],
                    request=request)

    # Build the file attachments data for the editor data.
    file_attachments_data = []

    for file_attachment in context.get('file_attachments', []):
        if draft:
            caption = file_attachment.draft_caption
        else:
            caption = file_attachment.caption

        file_attachment_data = {
            'id': file_attachment.pk,
            'loaded': True,
            'caption': caption,
            'downloadURL': file_attachment.get_absolute_url(),
            'filename': file_attachment.filename,
            'revision': file_attachment.attachment_revision,
            'thumbnailHTML': file_attachment.thumbnail,
        }

        if file_attachment.attachment_history_id:
            file_attachment_data['attachmentHistoryID'] = \
                file_attachment.attachment_history_id

        if _has_usable_review_ui(user, review_request, file_attachment):
            file_attachment_data['reviewURL'] = \
                local_site_reverse(
                    'file-attachment',
                    args=[review_request.display_id, file_attachment.pk],
                    request=request)

        file_attachments_data.append(file_attachment_data)

    if file_attachments_data:
        editor_data['fileAttachments'] = file_attachments_data

    # Build the file attachment comments data for the editor data.
    file_attachment_comments_data = {}

    for file_attachment in context.get('all_file_attachments', []):
        review_ui = file_attachment.review_ui

        if not review_ui:
            # For the purposes of serialization, we'll create a dummy ReviewUI.
            review_ui = FileAttachmentReviewUI(file_attachment.review_request,
                                               file_attachment)

        # NOTE: We're setting this here because file attachments serialization
        #       requires this to be set, but we don't necessarily have it set
        #       by this time. We should rethink parts of this down the road,
        #       but it requires dealing with some compatibility issues for
        #       subclasses.
        review_ui.request = request

        file_attachment_comments_data[file_attachment.pk] = \
            review_ui.serialize_comments(file_attachment.get_comments())

    if file_attachment_comments_data:
        editor_data['fileAttachmentComments'] = file_attachment_comments_data

    # And we're done! Assemble it together and chop off the outer dictionary
    # so it can be injected correctly.
    return json_dumps_items({
        'checkForUpdates': True,
        'reviewRequestData': review_request_data,
        'extraReviewRequestDraftData': extra_review_request_draft_data,
        'editorData': editor_data,
    })
示例#15
0
def comment_counts(user, all_comments, filediff, interfilediff=None):
    """
    Returns an array of current comments for a filediff, sorted by line number.

    Each entry in the array has a dictionary containing the following keys:

    =========== ==================================================
    Key                Description
    =========== ==================================================
    comment_id         The ID of the comment
    text               The plain or rich text of the comment
    rich_text          The rich text flag for the comment
    line               The first line number
    num_lines          The number of lines this comment spans
    user               A dictionary containing "username" and "name" keys
                       for the user
    url                The URL to the comment
    localdraft         True if this is the current user's draft comment
    review_id          The ID of the review this comment is associated with
    ==============================================================
    """
    comment_dict = {}

    if interfilediff:
        key = (filediff.pk, interfilediff.pk)
    else:
        key = (filediff.pk, None)

    comments = all_comments.get(key, [])

    for comment in comments:
        review = comment.get_review()

        if review and (review.public or review.user_id == user.pk):
            key = (comment.first_line, comment.num_lines)

            comment_dict.setdefault(key, []).append({
                'comment_id': comment.id,
                'text': normalize_text_for_edit(user, comment.text,
                                                comment.rich_text),
                'html': markdown_render_conditional(comment.text,
                                                    comment.rich_text),
                'rich_text': comment.rich_text,
                'line': comment.first_line,
                'num_lines': comment.num_lines,
                'user': {
                    'username': review.user.username,
                    'name': (review.user.get_full_name() or
                             review.user.username),
                },
                'url': comment.get_review_url(),
                'localdraft': (review.user == user and
                               not review.public),
                'review_id': review.id,
                'issue_opened': comment.issue_opened,
                'issue_status': BaseComment.issue_status_to_string(
                    comment.issue_status),
                'reply_to_id': comment.reply_to_id,
            })

    comments_array = []

    for key, value in six.iteritems(comment_dict):
        comments_array.append({
            'linenum': key[0],
            'num_lines': key[1],
            'comments': value,
        })

    comments_array.sort(key=cmp_to_key(
        lambda x, y: cmp(x['linenum'],
                         y['linenum'] or cmp(x['num_lines'],
                                             y['num_lines']))))

    return comments_array
示例#16
0
def comment_counts(user, all_comments, filediff, interfilediff=None):
    """
    Returns an array of current comments for a filediff, sorted by line number.

    Each entry in the array has a dictionary containing the following keys:

    =========== ==================================================
    Key                Description
    =========== ==================================================
    comment_id         The ID of the comment
    text               The plain or rich text of the comment
    rich_text          The rich text flag for the comment
    line               The first line number
    num_lines          The number of lines this comment spans
    user               A dictionary containing "username" and "name" keys
                       for the user
    url                The URL to the comment
    localdraft         True if this is the current user's draft comment
    review_id          The ID of the review this comment is associated with
    ==============================================================
    """
    comment_dict = {}

    if interfilediff:
        key = (filediff.pk, interfilediff.pk)
    else:
        key = (filediff.pk, None)

    comments = all_comments.get(key, [])

    for comment in comments:
        review = comment.get_review()

        if review and (review.public or review.user == user):
            key = (comment.first_line, comment.num_lines)

            comment_dict.setdefault(key, []).append({
                'comment_id': comment.id,
                'text': normalize_text_for_edit(user, comment.text,
                                                comment.rich_text),
                'html': markdown_render_conditional(comment.text,
                                                    comment.rich_text),
                'rich_text': comment.rich_text,
                'line': comment.first_line,
                'num_lines': comment.num_lines,
                'user': {
                    'username': review.user.username,
                    'name': (review.user.get_full_name() or
                             review.user.username),
                },
                'url': comment.get_review_url(),
                'localdraft': (review.user == user and
                               not review.public),
                'review_id': review.id,
                'issue_opened': comment.issue_opened,
                'issue_status': BaseComment.issue_status_to_string(
                    comment.issue_status),
                'reply_to_id': comment.reply_to_id,
            })

    comments_array = []

    for key, value in six.iteritems(comment_dict):
        comments_array.append({
            'linenum': key[0],
            'num_lines': key[1],
            'comments': value,
        })

    comments_array.sort(
        cmp=lambda x, y: (cmp(x['linenum'], y['linenum'] or
                          cmp(x['num_lines'], y['num_lines']))))

    return comments_array
示例#17
0
def reviewable_page_model_data(context):
    """Output JSON-serialized data for a RB.ReviewablePage model.

    The data will be used by :js:class:`RB.ReviewablePage` in order to
    populate the review request and editor with the necessary state.

    Args:
        context (django.template.RequestContext):
            The current template context.

    Returns:
        unicode:
        The resulting JSON-serialized data. This consists of keys that are
        meant to be injected into an existing dictionary.
    """
    request = context['request']
    user = request.user
    review_request = context['review_request']
    review_request_details = context['review_request_details']
    draft = context['draft']
    close_description = context['close_description']
    close_description_rich_text = context['close_description_rich_text']

    if review_request.local_site:
        local_site_prefix = 's/%s/' % review_request.local_site.name
    else:
        local_site_prefix = ''

    # Build data for the RB.ReviewRequest
    if review_request.status == review_request.PENDING_REVIEW:
        state_data = 'PENDING'
    elif review_request.status == review_request.SUBMITTED:
        state_data = 'CLOSE_SUBMITTED'
    elif review_request.status == review_request.DISCARDED:
        state_data = 'CLOSE_DISCARDED'
    else:
        raise ValueError('Unexpected ReviewRequest.status value "%s"'
                         % review_request.status)

    review_request_data = {
        'id': review_request.display_id,
        'localSitePrefix': local_site_prefix,
        'branch': review_request_details.branch,
        'bugsClosed': review_request_details.get_bug_list(),
        'closeDescription': normalize_text_for_edit(
            user=user,
            text=close_description,
            rich_text=close_description_rich_text,
            escape_html=False),
        'closeDescriptionRichText': close_description_rich_text,
        'description': normalize_text_for_edit(
            user=user,
            text=review_request_details.description,
            rich_text=review_request_details.description_rich_text,
            escape_html=False),
        'descriptionRichText': review_request_details.description_rich_text,
        'hasDraft': draft is not None,
        'lastUpdatedTimestamp': review_request.last_updated,
        'public': review_request.public,
        'reviewURL': review_request.get_absolute_url(),
        'state': state_data,
        'summary': review_request_details.summary,
        'targetGroups': [
            {
                'name': group.name,
                'url': group.get_absolute_url(),
            }
            for group in review_request_details.target_groups.all()
        ],
        'targetPeople': [
            {
                'username': target_user.username,
                'url': local_site_reverse('user',
                                          args=[target_user],
                                          request=request)
            }
            for target_user in review_request_details.target_people.all()
        ],
        'testingDone': normalize_text_for_edit(
            user=user,
            text=review_request_details.testing_done,
            rich_text=review_request_details.testing_done_rich_text,
            escape_html=False),
        'testingDoneRichText': review_request_details.testing_done_rich_text,
    }

    if user.is_authenticated():
        review_request_visit = context['review_request_visit']

        if review_request_visit.visibility == review_request_visit.VISIBLE:
            visibility_data = 'VISIBLE'
        elif review_request_visit.visibility == review_request_visit.ARCHIVED:
            visibility_data = 'ARCHIVED'
        elif review_request_visit.visibility == review_request_visit.MUTED:
            visibility_data = 'MUTED'
        else:
            raise ValueError(
                'Unexpected ReviewRequestVisit.visibility value "%s"'
                % review_request_visit.visibility)

        review_request_data['visibility'] = visibility_data

    repository = review_request.repository

    if repository:
        scmtool = repository.get_scmtool()

        review_request_data['repository'] = {
            'id': repository.pk,
            'name': repository.name,
            'scmtoolName': scmtool.name,
            'requiresBasedir': not scmtool.diffs_use_absolute_paths,
            'requiresChangeNumber': scmtool.supports_pending_changesets,
            'supportsPostCommit': repository.supports_post_commit,
        }

        if repository.bug_tracker:
            review_request_data['bugTrackerURL'] = \
                local_site_reverse(
                    'bug_url',
                    args=[review_request.display_id, '--bug_id--'],
                    request=request)

    if draft:
        review_request_data['submitter'] = {
            'title': draft.submitter.username,
            'url': draft.submitter.get_absolute_url(),
        }

    # Build the data for the RB.ReviewRequestEditor.
    editor_data = {
        'closeDescriptionRenderedText': _render_markdown(
            close_description,
            close_description_rich_text),
        'hasDraft': draft is not None,
        'mutableByUser': context['mutable_by_user'],
        'showSendEmail': context['send_email'],
        'statusMutableByUser': context['status_mutable_by_user'],
    }

    # Build extra data for the RB.ReviewRequest.
    extra_review_request_draft_data = {}

    if draft and draft.changedesc:
        extra_review_request_draft_data.update({
            'changeDescription': normalize_text_for_edit(
                user=user,
                text=draft.changedesc.text,
                rich_text=draft.changedesc.rich_text,
                escape_html=False),
            'changeDescriptionRichText': draft.changedesc.rich_text,
        })

        editor_data['changeDescriptionRenderedText'] = _render_markdown(
            draft.changedesc.text, draft.changedesc.rich_text)

        if draft.diffset:
            extra_review_request_draft_data['interdiffLink'] = \
                local_site_reverse(
                    'view-interdiff',
                    args=[
                        review_request.display_id,
                        draft.diffset.revision - 1,
                        draft.diffset.revision
                    ],
                    request=request)

    # Build the file attachments data for the editor data.
    file_attachments_data = []

    for file_attachment in context.get('file_attachments', []):
        if draft:
            caption = file_attachment.draft_caption
        else:
            caption = file_attachment.caption

        file_attachment_data = {
            'id': file_attachment.pk,
            'loaded': True,
            'caption': caption,
            'downloadURL': file_attachment.get_absolute_url(),
            'filename': file_attachment.filename,
            'revision': file_attachment.attachment_revision,
            'thumbnailHTML': file_attachment.thumbnail,
        }

        if file_attachment.attachment_history_id:
            file_attachment_data['attachmentHistoryID'] = \
                file_attachment.attachment_history_id

        if _has_usable_review_ui(user, review_request, file_attachment):
            file_attachment_data['reviewURL'] = \
                local_site_reverse(
                    'file-attachment',
                    args=[review_request.display_id, file_attachment.pk],
                    request=request)

        file_attachments_data.append(file_attachment_data)

    if file_attachments_data:
        editor_data['fileAttachments'] = file_attachments_data

    # Build the file attachment comments data for the editor data.
    file_attachment_comments_data = {}

    for file_attachment in context.get('all_file_attachments', []):
        review_ui = file_attachment.review_ui

        if not review_ui:
            # For the purposes of serialization, we'll create a dummy ReviewUI.
            review_ui = FileAttachmentReviewUI(file_attachment.review_request,
                                               file_attachment)

        # NOTE: We're setting this here because file attachments serialization
        #       requires this to be set, but we don't necessarily have it set
        #       by this time. We should rethink parts of this down the road,
        #       but it requires dealing with some compatibility issues for
        #       subclasses.
        review_ui.request = request

        file_attachment_comments_data[file_attachment.pk] = \
            review_ui.serialize_comments(file_attachment.get_comments())

    if file_attachment_comments_data:
        editor_data['fileAttachmentComments'] = file_attachment_comments_data

    # And we're done! Assemble it together and chop off the outer dictionary
    # so it can be injected correctly.
    return json_dumps_items({
        'checkForUpdates': True,
        'reviewRequestData': review_request_data,
        'extraReviewRequestDraftData': extra_review_request_draft_data,
        'editorData': editor_data,
    })