Repository: encode/django-rest-framework

Issue:
OpenAPI schema generation does not handle callable defaults
## Checklist

- [x] I have verified that that issue exists against the `master` branch of Django REST framework.
- [x] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [x] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [x] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.)
- [x] I have reduced the issue to the simplest possible case.
- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

## Steps to reproduce

On any serializer used by a GenericAPIView, set the default for a field to any callable.

``` python
class OhNoSerializer(serializers.Serializer):
    something = serializers.FloatField(default=float)

class OhNoAPIView(CreateAPIView):
    serializer_class = OhNoSerializer
    ...
```

## Expected behavior

Output of `./manage.py generateschema`:

``` yaml
paths:
  /api/oh/no:
    post:
      operationId: CreateOhNo
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              required:
              - something
              properties:
                something:
                  type: number
                  default: 0.0
```

## Actual behavior

With `./manage.py generateschema`:

``` yaml
paths:
  /api/oh/no:
    post:
      operationId: CreateOhNo
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              required:
              - something
              properties:
                something:
                  type: number
                  default: !!python/name:builtins.float ''
```

With `./manage.py generateschema --format openapi-json`:

```
Traceback (most recent call last):
  File "./manage.py", line 22, in <module>
    execute_from_command_line(sys.argv)
  File "/home/lucidiot/.virtualenvs/drf/lib/python3.6/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
    utility.execute()
  File "/home/lucidiot/.virtualenvs/drf/lib/python3.6/site-packages/django/core/management/__init__.py", line 375, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/lucidiot/.virtualenvs/drf/lib/python3.6/site-packages/django/core/management/base.py", line 323, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/lucidiot/.virtualenvs/drf/lib/python3.6/site-packages/django/core/management/base.py", line 364, in execute
    output = self.handle(*args, **options)
  File "/home/lucidiot/dev/django-rest-framework/rest_framework/management/commands/generateschema.py", line 42, in handle
    output = renderer.render(schema, renderer_context={})
  File "/home/lucidiot/dev/django-rest-framework/rest_framework/renderers.py", line 1064, in render
    return json.dumps(data, indent=2).encode('utf-8')
  File "/home/lucidiot/dev/django-rest-framework/rest_framework/utils/json.py", line 25, in dumps
    return json.dumps(*args, **kwargs)
  File "/usr/lib/python3.6/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/usr/lib/python3.6/json/encoder.py", line 201, in encode
    chunks = list(chunks)
  File "/usr/lib/python3.6/json/encoder.py", line 430, in _iterencode
    yield from _iterencode_dict(o, _current_indent_level)
  File "/usr/lib/python3.6/json/encoder.py", line 404, in _iterencode_dict
    yield from chunks
  File "/usr/lib/python3.6/json/encoder.py", line 404, in _iterencode_dict
    yield from chunks
  File "/usr/lib/python3.6/json/encoder.py", line 404, in _iterencode_dict
    yield from chunks
  [Previous line repeated 7 more times]
  File "/usr/lib/python3.6/json/encoder.py", line 437, in _iterencode
    o = _default(o)
  File "/usr/lib/python3.6/json/encoder.py", line 180, in default
    o.__class__.__name__)
TypeError: Object of type 'type' is not JSON serializable
```

--------------

How to handle defaults that depend on the request, like `CurrentUserDefault`, or user-defined defaults that can depend on *anything* ?


Discussion:
This also happens if you use some TimeField or any other non-JSON serializable field, but I think it could be easily avoidable if the DjangoJSONEncoder class is used at https://github.com/encode/django-rest-framework/blob/a73d3c309f0736f46dd46c99e15e1102af26dacb/rest_framework/renderers.py#L1064

```python
from django.core.serializers.json import DjangoJSONEncoder
return json.dumps(data, indent=2, cls=DjangoJSONEncoder).encode('utf-8')
```

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
@@ -393,7 +393,7 @@ def _map_serializer(self, serializer):
                 schema['writeOnly'] = True
             if field.allow_null:
                 schema['nullable'] = True
-            if field.default and field.default != empty:  # why don't they use None?!
+            if field.default and field.default != empty and not callable(field.default):  # why don't they use None?!
                 schema['default'] = field.default
             if field.help_text:
                 schema['description'] = str(field.help_text)


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
@@ -571,6 +571,22 @@ def test_serializer_hstorefield(self):
         properties = response_schema['items']['properties']
         assert properties['hstore']['type'] == 'object'
 
+    def test_serializer_callable_default(self):
+        path = '/'
+        method = 'GET'
+        view = create_view(
+            views.ExampleGenericAPIView,
+            method,
+            create_request(path),
+        )
+        inspector = AutoSchema()
+        inspector.view = view
+
+        responses = inspector._get_responses(path, method)
+        response_schema = responses['200']['content']['application/json']['schema']
+        properties = response_schema['items']['properties']
+        assert 'default' not in properties['uuid_field']
+
     def test_serializer_validators(self):
         path = '/'
         method = 'GET'
diff --git a/tests/schemas/views.py b/tests/schemas/views.py
--- a/tests/schemas/views.py
+++ b/tests/schemas/views.py
@@ -58,6 +58,7 @@ class ExampleSerializer(serializers.Serializer):
     date = serializers.DateField()
     datetime = serializers.DateTimeField()
     hstore = serializers.HStoreField()
+    uuid_field = serializers.UUIDField(default=uuid.uuid4)
 
 
 class ExampleGenericAPIView(generics.GenericAPIView):