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
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 araw_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.
Diff Revision 2 (Latest)
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 |
---|