Repository: encode/django-rest-framework

Issue:
null=True is not respected by AutoSchema when the field is on read_only_fields
Hello,

Take this ModelSerializer for example:

```python
class MyModel(models.Model):
    my_field = models.DateTimeField(null=True, blank=True)

class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = ["my_field"]
        read_only_fields = ["my_field"]
```

If you generate this serializer schema you will notice that the `null=True` was ignored by the generated schema

``` python
>>> inspector = AutoSchema()
>>> schema = inspector.map_serializer(MyModelSerializer())
>>> schema['properties']['my_field']

{'type': 'string', 'format': 'date-time', 'readOnly': True}
```

Removing the from the `read_only_fields` fixes the problem and the schema goes back to:

```python
{'type': 'string', 'format': 'date-time', 'nullable': True}
```
null=True is not respected by AutoSchema when the field is on read_only_fields
Hello,

Take this ModelSerializer for example:

```python
class MyModel(models.Model):
    my_field = models.DateTimeField(null=True, blank=True)

class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = ["my_field"]
        read_only_fields = ["my_field"]
```

If you generate this serializer schema you will notice that the `null=True` was ignored by the generated schema

``` python
>>> inspector = AutoSchema()
>>> schema = inspector.map_serializer(MyModelSerializer())
>>> schema['properties']['my_field']

{'type': 'string', 'format': 'date-time', 'readOnly': True}
```

Removing the from the `read_only_fields` fixes the problem and the schema goes back to:

```python
{'type': 'string', 'format': 'date-time', 'nullable': True}
```


Discussion:
In theory, this should produce both `readOnly = True` and `nullable = True`, which is allowed per the OpenAPI spec.

This is not happening because `null=True` on the model is translated to `allow_null=True` on the serializer field which is stripped out when `read_only=True` is specified.

https://github.com/encode/django-rest-framework/blob/24a938abaadd98b5482bec33defd285625842342/rest_framework/serializers.py#L1327-L1333

It's not entirely clear at first glance why `allow_null` is removed since there doesn't appear to be any assertions explaining why these two wouldn't be allowed at the same time. My only guess is because normally this only applies to handling `null` in input data, but we do have code which also uses this for null coercion in outgoing data as well.
In theory, this should produce both `readOnly = True` and `nullable = True`, which is allowed per the OpenAPI spec.

This is not happening because `null=True` on the model is translated to `allow_null=True` on the serializer field which is stripped out when `read_only=True` is specified.

https://github.com/encode/django-rest-framework/blob/24a938abaadd98b5482bec33defd285625842342/rest_framework/serializers.py#L1327-L1333

It's not entirely clear at first glance why `allow_null` is removed since there doesn't appear to be any assertions explaining why these two wouldn't be allowed at the same time. My only guess is because normally this only applies to handling `null` in input data, but we do have code which also uses this for null coercion in outgoing data as well.

Resolving patch:
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -1326,9 +1326,8 @@ def include_extra_kwargs(self, kwargs, extra_kwargs):
         """
         if extra_kwargs.get('read_only', False):
             for attr in [
-                'required', 'default', 'allow_blank', 'allow_null',
-                'min_length', 'max_length', 'min_value', 'max_value',
-                'validators', 'queryset'
+                'required', 'default', 'allow_blank', 'min_length',
+                'max_length', 'min_value', 'max_value', 'validators', 'queryset'
             ]:
                 kwargs.pop(attr, None)
 


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
@@ -2,6 +2,7 @@
 import warnings
 
 import pytest
+from django.db import models
 from django.test import RequestFactory, TestCase, override_settings
 from django.urls import path
 from django.utils.translation import gettext_lazy as _
@@ -110,6 +111,24 @@ class Serializer(serializers.Serializer):
         assert data['properties']['default_false']['default'] is False, "default must be false"
         assert 'default' not in data['properties']['without_default'], "default must not be defined"
 
+    def test_nullable_fields(self):
+        class Model(models.Model):
+            rw_field = models.CharField(null=True)
+            ro_field = models.CharField(null=True)
+
+        class Serializer(serializers.ModelSerializer):
+            class Meta:
+                model = Model
+                fields = ["rw_field", "ro_field"]
+                read_only_fields = ["ro_field"]
+
+        inspector = AutoSchema()
+
+        data = inspector.map_serializer(Serializer())
+        assert data['properties']['rw_field']['nullable'], "rw_field nullable must be true"
+        assert data['properties']['ro_field']['nullable'], "ro_field nullable must be true"
+        assert data['properties']['ro_field']['readOnly'], "ro_field read_only must be true"
+
 
 @pytest.mark.skipif(uritemplate is None, reason='uritemplate not installed.')
 class TestOperationIntrospection(TestCase):