Support including raw text fields in the API when using force-text-type.

Review Request #6436 — Created Oct. 14, 2014 and submitted — Latest diff uploaded

Information

Review Board
release-2.0.x
1ce708b...

Reviewers

The force-text-type= parameter in requests is useful for returning text
in a different format, but there are times when the client would need
the raw text along with this.

Now, the client can also send an include-raw-text-fields=1 parameter,
which will embed a raw_text_fields dictionary in the object's payload
containing an entry for each text field. Each entry provides the raw
version of the text. This is optional, in order to prevent the default
response from ballooning in size.

On an implementation level, this is done by removing all the custom
serializers, and instead adding some metadata to the resource's fields
list for each field that supports text types. MarkdownFieldsMixin's
serialize_object then handles embedding these raw fields in the payload
on request.

Tested manually wit and without the new parameter.

Unit tests pass.

reviewboard/webapi/mixins.py
Revision 164448dd7c2073e61afcf801f92fee6cfe1bf7da New Change
12 lines
13
    """Mixes in common logic for Markdown text fields.
13
    """Mixes in common logic for Markdown text fields.
14

    
   
14

   
15
    Any resource implementing this is assumed to have at least one
15
    Any resource implementing this is assumed to have at least one
16
    Markdown-capable text field.
16
    Markdown-capable text field.
17

    
   
17

   
18
    Clients can pass ``?force-text-type=`` with a value of ``plain`` or
18
    Clients can pass ``?force-text-type=`` (for GET) or ``force_text_type=``
19
    ``markdown`` to provide those text fields in the requested format.
19
    (for POST/PUT) with a value of ``plain``, ``markdown`` or ``html`` to

    
   
20
    return the given text fields in the payload using the requested format.
20

    
   
21

   
21
    When ``markdown`` is specified, the Markdown text fields will return valid
22
    When ``markdown`` is specified, the Markdown text fields will return valid
22
    Markdown content, escaping if necessary.
23
    Markdown content, escaping if necessary.
23

    
   
24

   
24
    When ``plain`` is specified, plain text will be returned instead. If
25
    When ``plain`` is specified, plain text will be returned instead. If
25
    the content was in Markdown before, this will unescape the content.
26
    the content was in Markdown before, this will unescape the content.

    
   
27

   

    
   
28
    When ``html`` is specified, the content will be transformed into HTML

    
   
29
    suitable for display.

    
   
30

   

    
   
31
    Clients can also pass ``?include-raw-text-fields=1`` (for GET) or

    
   
32
    ``include_raw_text_fields=`` (for POST/PUT) to return the raw fields

    
   
33
    within a special ``raw_text_fields`` entry in the resource payload.
26
    """
34
    """
27
    TEXT_TYPE_PLAIN = 'plain'
35
    TEXT_TYPE_PLAIN = 'plain'
28
    TEXT_TYPE_MARKDOWN = 'markdown'
36
    TEXT_TYPE_MARKDOWN = 'markdown'
29
    TEXT_TYPE_HTML = 'html'
37
    TEXT_TYPE_HTML = 'html'
30

    
   
38

   
31
    TEXT_TYPES = (TEXT_TYPE_PLAIN, TEXT_TYPE_MARKDOWN, TEXT_TYPE_HTML)
39
    TEXT_TYPES = (TEXT_TYPE_PLAIN, TEXT_TYPE_MARKDOWN, TEXT_TYPE_HTML)
32
    SAVEABLE_TEXT_TYPES = (TEXT_TYPE_PLAIN, TEXT_TYPE_MARKDOWN)
40
    SAVEABLE_TEXT_TYPES = (TEXT_TYPE_PLAIN, TEXT_TYPE_MARKDOWN)
33

    
   
41

   
34 Moved to line 85
    def serialize_text_type_field(self, obj, request=None, **kwargs):
42
    def serialize_object(self, obj, *args, **kwargs):
35
        return self.get_requested_text_type(obj, request)
43
        """Serializes the object, transforming text fields.

    
   
44

   

    
   
45
        This is a specialization of serialize_object that transforms any

    
   
46
        text fields that support text types. It also handles attaching

    
   
47
        the raw text to the payload, on request.

    
   
48
        """

    
   
49
        data = super(MarkdownFieldsMixin, self).serialize_object(

    
   
50
            obj, *args, **kwargs)

    
   
51

   

    
   
52
        requested_text_type = self._get_requested_text_type(obj, **kwargs)

    
   
53

   

    
   
54
        if requested_text_type:

    
   
55
            include_raw_text_fields = \

    
   
56
                self._get_include_raw_text_fields(obj, **kwargs)

    
   
57
            raw_fields = {}
36

    
   
58

   
37
    def serialize_extra_data_field(self, obj, **kwargs):
59
            for field, field_info in six.iteritems(self.fields):
38
        extra_data = {}
60
                if field_info.get('supports_text_types'):

    
   
61
                    value = data.get(field)

    
   
62
                    data[field] = self._normalize_text(obj, value, **kwargs)

    
   
63

   

    
   
64
                    if include_raw_text_fields:

    
   
65
                        raw_fields[field] = value

    
   
66

   

    
   
67
            if 'extra_data' in data:

    
   
68
                raw_extra_data = {}

    
   
69
                extra_data = data['extra_data']
39

    
   
70

   
40
        for key, value in six.iteritems(obj.extra_data):
71
>>>>>>>>        for key, value in six.iteritems(obj.extra_data):
41
            if value and self.get_extra_data_field_supports_markdown(obj, key):
72
                    if (value and
42
                extra_data[key] = self.normalize_text(obj, value, **kwargs)
73
                        self.get_extra_data_field_supports_markdown(obj, key)):
43
            else:
74
                        extra_data[key] = self._normalize_text(obj, value,
44
                extra_data[key] = value
75
                                                               **kwargs)
45

    
   
76

   
46
        return extra_data
77
                        if include_raw_text_fields:

    
   
78
                            raw_extra_data[key] = value

    
   
79

   

    
   
80
            if include_raw_text_fields:

    
   
81
                data['raw_text_fields'] = raw_fields

    
   
82

   

    
   
83
        return data

    
   
84

   

    
   
85 Moved from line 34
    def serialize_text_type_field(self, obj, request=None, **kwargs):

    
   
86
        return self._get_requested_text_type(obj, request)
47

    
   
87

   
48
    def get_extra_data_field_supports_markdown(self, obj, key):
88
    def get_extra_data_field_supports_markdown(self, obj, key):
49
        """Returns whether a particular field in extra_data supports Markdown.
89
        """Returns whether a particular field in extra_data supports Markdown.
50

    
   
90

   
51
        If the field supports Markdown text, the value will be normalized
91
        If the field supports Markdown text, the value will be normalized
52
        based on the requested ?force-text-type= parameter.
92
        based on the requested ?force-text-type= parameter.
53
        """
93
        """
54
        return False
94
        return False
55

    
   
95

   
56
    def get_requested_text_type(self, obj, request=None):
96
    def _get_requested_text_type(self, obj, request=None, **kwargs):
57
        """Returns the text type requested by the user.
97
        """Returns the text type requested by the user.
58

    
   
98

   
59
        If the user did not request a text type, or a valid text type,
99
        If the user did not request a text type, or a valid text type,
60
        this will fall back to the proper type for the given object.
100
        this will fall back to the proper type for the given object.
61
        """
101
        """

    
   
102
        if request and hasattr(request, '_rbapi_requested_text_type'):

    
   
103
            text_type = request._rbapi_requested_text_type

    
   
104
        else:
62
        if request:
105
>>>>        if request:
63
            if request.method == 'GET':
106
>>>>            if request.method == 'GET':
64
                text_type = request.GET.get('force-text-type')
107
>>>>                text_type = request.GET.get('force-text-type')
65
            else:
108
>>>>            else:
66
                text_type = request.POST.get('force_text_type')
109
>>>>                text_type = request.POST.get('force_text_type')
67
        else:
110
>>>>        else:
68
            text_type = None
111
>>>>            text_type = None
69

    
   
112

   
70
        if not text_type or text_type not in self.TEXT_TYPES:
113
>>>>        if not text_type or text_type not in self.TEXT_TYPES:
71
            if obj.rich_text:
114
>>>>            if obj.rich_text:
72
                text_type = self.TEXT_TYPE_MARKDOWN
115
>>>>                text_type = self.TEXT_TYPE_MARKDOWN
73
            else:
116
>>>>            else:
74
                text_type = self.TEXT_TYPE_PLAIN
117
>>>>                text_type = self.TEXT_TYPE_PLAIN
75

    
   
118

   

    
   
119
            if request:

    
   
120
                request._rbapi_requested_text_type = text_type

    
   
121

   
76
        return text_type
122
        return text_type
77

    
   
123

   
78
    def normalize_text(self, obj, text, request=None, **kwargs):
124
    def _get_include_raw_text_fields(self, obj, request=None, **kwargs):

    
   
125
        """Returns whether raw text fields should be returned in the payload.

    
   
126

   

    
   
127
        If ``?include-raw-text-fields=1`` (for GET) or

    
   
128
        ``include_raw_text_fields=`` (for POST/PUT) is passed, this will

    
   
129
        return True. Otherwise, it will return False.

    
   
130
        """

    
   
131
        include_raw_text = False

    
   
132

   

    
   
133
        if request:

    
   
134
            if request.method == 'GET':

    
   
135
                include_raw_text = request.GET.get('include-raw-text-fields')

    
   
136
            else:

    
   
137
                include_raw_text = request.POST.get('include_raw_text_fields')

    
   
138

   

    
   
139
        return include_raw_text in ('1', 'true')

    
   
140

   

    
   
141
    def _normalize_text(self, obj, text, request=None, **kwargs):
79
        """Normalizes text to the proper format.
142
        """Normalizes text to the proper format.
80

    
   
143

   
81
        This considers the requested text format, and whether or not the
144
        This considers the requested text format, and whether or not the
82
        object is set for having rich text.
145
        object is set for having rich text.
83
        """
146
        """
84
        text_type = self.get_requested_text_type(obj, request)
147
        text_type = self._get_requested_text_type(obj, request)
85

    
   
148

   
86
        if text_type == self.TEXT_TYPE_PLAIN and obj.rich_text:
149
        if text_type == self.TEXT_TYPE_PLAIN and obj.rich_text:
87
            text = markdown_unescape(text)
150
            text = markdown_unescape(text)
88
        elif text_type == self.TEXT_TYPE_MARKDOWN and not obj.rich_text:
151
        elif text_type == self.TEXT_TYPE_MARKDOWN and not obj.rich_text:
89
            text = markdown_escape(text)
152
            text = markdown_escape(text)
32 lines
reviewboard/webapi/resources/base_comment.py
reviewboard/webapi/resources/base_review.py
reviewboard/webapi/resources/change.py
reviewboard/webapi/resources/review_reply.py
reviewboard/webapi/resources/review_request.py
reviewboard/webapi/resources/review_request_draft.py
reviewboard/webapi/tests/mixins_comment.py
reviewboard/webapi/tests/mixins_review.py
reviewboard/webapi/tests/test_review_request_draft.py
Loading...