Description
Discussed in #8402
Since:
- there's no response from the discussion thread,
- I think this is a valid issue, and
- I've found a possibly proper solution
I'll just put this as an issue and create a PR.
Originally posted by hashlash March 11, 2022
The Issue
I have a model with SafeString as its help_text (inspired from Django's password validation help text):
from django.db import models
from django.utils.html import format_html, format_html_join
def list_help_text_html(help_texts):
help_items = format_html_join(
"", "<li>{}</li>", ((help_text,) for help_text in help_texts)
)
return format_html("<ul>{}</ul>", help_items) if help_items else ""
class ExampleModel(models.Model):
field = models.TextField(
help_text=list_help_text_html([
'info a',
'info b',
]),
)
I followed the DRF's doc about Generating a dynamic schema with SchemaView and A minimal example with Swagger UI, but the Swagger UI fails to render the generated schema.
Parser error on line 59
unknown tag !<tag:yaml.org,2002:python/object/new:django.utils.safestring.SafeString>
The generated schema (full version):
openapi: 3.0.2
info:
# redacted
paths:
# redacted
components:
schemas:
Example:
type: object
properties:
id:
# redacted
field:
type: string
description: !!python/object/new:django.utils.safestring.SafeString
- <ul><li>info a</li><li>info b</li></ul>
required:
- field
Here's a repo for demonstrating the bug. The Swagger UI can be accessed on /swagger-ui/
.
Package versions:
django==3.2.12
djangorestframework==3.13.1
pyyaml==6.0
uritemplate==4.1.1
Findings
I found that the schema generation for the help_text lies on:
django-rest-framework/rest_framework/schemas/openapi.py
Lines 289 to 290 in a53e523
and:
django-rest-framework/rest_framework/schemas/openapi.py
Lines 537 to 538 in a53e523
But since SafeString
is a subclass of str
and it override the __str__()
method to return itself, those DRF's method will still return a SafeString
object.
class SafeString(str, SafeData):
...
def __str__(self):
return self
Workaround
I've with 3 possible workarounds, and so far I think the third is the best:
-
Adding the
SafeString
with an empty string (implementation, demo)>>> from django.utils.safestring import SafeString >>> s = SafeString('abc') >>> s + '' 'abc' >>> type(s + '') <class 'str'>
-
Use
str.__str__()
(implementation, demo)>>> from django.utils.safestring import SafeString >>> s = SafeString('abc') >>> str.__str__(s) 'abc' >>> type(str.__str__(s)) <class 'str'>
-
Use
represent_str()
to representSafeString
asstr
(implementation, demo)class OpenAPIRenderer(BaseRenderer): ... def render(self, data, media_type=None, renderer_context=None): class Dumper(yaml.Dumper): ... Dumper.add_representer(SafeString, Dumper.represent_str) return yaml.dump(data, default_flow_style=False, sort_keys=False, Dumper=Dumper).encode('utf-8')
PS: I also asked about the "string conversion" on Django's forum.