Repository: encode/django-rest-framework

Issue:
OpenAPI AutoSchema does not handle lazy strings / ugettext properly

## Steps to reproduce

1. create a `UserSerializer` as instance of `ModelSerializer` and use it in a viewset (just like in quickstart tutorial)
2. run `manage.py  generateschema`

## Expected behavior

`generateschema` returns a valid OpenAPI schema

## Actual behavior

The generated schema's description fields contains unserialized lazy strings

```yaml
openapi: 3.0.2
info:
  title: ''
  version: TODO
paths:
  /users/:
    get:
      operationId: ListUsers
      parameters: []
      responses:
        '200':
          content:
            application/json:
              schema:
                required:
                - username
                properties:
                  url:
                    type: string
                    readOnly: true
                  username:
                    type: string
                    description: &id003 !!python/object/apply:django.utils.functional._lazy_proxy_unpickle
                    - &id001 !!python/name:django.utils.translation.ugettext ''
                    - !!python/tuple
                      - Required. 150 characters or fewer. Letters, digits and @/./+/-/_
                        only.
                    - {}
                    - &id002 !!python/name:builtins.str ''
```

# Suggested solution
Force description to text in [`schemas.openapi.AutoSchema._map_serializer()`](https://github.com/encode/django-rest-framework/blob/master/rest_framework/schemas/openapi.py#L372)

# Workaround

This problem can be worked around with a custom schema generator:

```python
from django.utils.encoding import force_text
from rest_framework.schemas.openapi import AutoSchema

class FixedAutoSchema(AutoSchema):
    def _map_serializer(self, serializer):
        result = super()._map_serializer(serializer)
        for prop, value in result['properties'].items():
            if 'description' in value:
                value['description'] = force_text(value['description'])
        return result

# in settings.py
DEFAULT_SCHEMA_CLASS = 'myapp.FixedAutoSchema'
```


Discussion:
Hi @chrisv2. Thanks. (The `force_text()` does the business yes?)
`django.utils.encoding.force_text` solves this, yes. That's what I use as workaround.

I'd do a PR but I'm not really familiar with DRFs unittests so that may take a while.
Hey @chrisv2. 

Look at https://github.com/encode/django-rest-framework/commit/2138f558cee33db9dbcff393517173a42a363421

If you added a case in `TestFieldMapping` which passed _map_field a `CharField` instance with a lazy `help_text` that asserted that we got a `str` there back, I think it would be enough.

If you'd like to have a go at that it would be super to have you onboard! 🙂
Thank's, I'll try.
Awesome. If you open a PR early, I'm happy to assist if you get stuck somewhere. 

Resolving patch:
diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py
--- a/rest_framework/schemas/openapi.py
+++ b/rest_framework/schemas/openapi.py
@@ -377,7 +377,7 @@ def _map_serializer(self, serializer):
             if field.default and field.default != empty:  # why don't they use None?!
                 schema['default'] = field.default
             if field.help_text:
-                schema['description'] = field.help_text
+                schema['description'] = str(field.help_text)
             self._map_field_validators(field.validators, schema)
 
             properties[field.field_name] = schema


Test patch:
diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py
--- a/tests/schemas/test_openapi.py
+++ b/tests/schemas/test_openapi.py
@@ -1,6 +1,7 @@
 import pytest
 from django.conf.urls import url
 from django.test import RequestFactory, TestCase, override_settings
+from django.utils.translation import gettext_lazy as _
 
 from rest_framework import filters, generics, pagination, routers, serializers
 from rest_framework.compat import uritemplate
@@ -52,6 +53,15 @@ def test_list_field_mapping(self):
             with self.subTest(field=field):
                 assert inspector._map_field(field) == mapping
 
+    def test_lazy_string_field(self):
+        class Serializer(serializers.Serializer):
+            text = serializers.CharField(help_text=_('lazy string'))
+
+        inspector = AutoSchema()
+
+        data = inspector._map_serializer(Serializer())
+        assert isinstance(data['properties']['text']['description'], str), "description must be str"
+
 
 @pytest.mark.skipif(uritemplate is None, reason='uritemplate not installed.')
 class TestOperationIntrospection(TestCase):