Skip to content

Commit

Permalink
support for DictField child type (#142) and models.JSONField (Django>…
Browse files Browse the repository at this point in the history
…=3.1)
  • Loading branch information
tfranzel committed Aug 26, 2020
1 parent 889f27b commit 0dbd9d1
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 2 deletions.
2 changes: 1 addition & 1 deletion docs/customization.rst
Expand Up @@ -37,7 +37,7 @@ discovered in the introspection.
def retrieve(self, request, *args, **kwargs)
# your code
.. note:: For simple responses, you might go through the hassle of writing an explicit serializer class.
.. note:: For simple responses, you might not go through the hassle of writing an explicit serializer class.
In those cases, you can simply specify the request/response with a call to
:py:func:`inline_serializer <drf_spectacular.utils.inline_serializer>`.
This lets you conveniently define the endpoint's schema inline without actually writing a serializer class.
Expand Down
11 changes: 10 additions & 1 deletion drf_spectacular/openapi.py
Expand Up @@ -374,6 +374,9 @@ def _map_model_field(self, model_field, direction):
return self._map_serializer_field(field, direction)
elif isinstance(model_field, models.ForeignKey):
return self._map_model_field(model_field.target_field, direction)
elif hasattr(models, 'JSONField') and isinstance(model_field, models.JSONField):
# fix for DRF==3.11 with django>=3.1 as it is not yet represented in the field_mapping
return build_basic_type(OpenApiTypes.OBJECT)
elif hasattr(models, model_field.get_internal_type()):
# be graceful when the model field is not explicitly mapped to a serializer
internal_type = getattr(models, model_field.get_internal_type())
Expand Down Expand Up @@ -553,9 +556,15 @@ def _map_serializer_field(self, field, direction):
if anyisinstance(field, [serializers.BooleanField, serializers.NullBooleanField]):
return append_meta(build_basic_type(OpenApiTypes.BOOL), meta)

if anyisinstance(field, [serializers.JSONField, serializers.DictField, serializers.HStoreField]):
if isinstance(field, serializers.JSONField):
return append_meta(build_basic_type(OpenApiTypes.OBJECT), meta)

if anyisinstance(field, [serializers.DictField, serializers.HStoreField]):
content = build_basic_type(OpenApiTypes.OBJECT)
if not isinstance(field.child, _UnvalidatedField):
content['additionalProperties'] = self._map_serializer_field(field.child, direction)
return append_meta(content, meta)

if isinstance(field, serializers.CharField):
return append_meta(build_basic_type(OpenApiTypes.STR), meta)

Expand Down
19 changes: 19 additions & 0 deletions tests/test_fields.py
Expand Up @@ -4,6 +4,7 @@
from decimal import Decimal

import pytest
from django import __version__ as DJANGO_VERSION
from django.core.files.base import ContentFile
from django.core.files.storage import FileSystemStorage
from django.db import models
Expand Down Expand Up @@ -61,6 +62,13 @@ class AllFields(models.Model):
field_regex = models.CharField(max_length=50)
field_bool_override = models.BooleanField()

if DJANGO_VERSION >= '3.1':
field_json = models.JSONField()
else:
@property
def field_json(self):
return {'A': 1, 'B': 2}

@property
def field_model_property_float(self) -> float:
return 1.337
Expand Down Expand Up @@ -128,6 +136,15 @@ def get_field_method_object(self, obj) -> dict:

field_model_property_float = serializers.ReadOnlyField()

field_dict_int = serializers.DictField(
child=serializers.IntegerField(),
source='field_json',
)

# there is a JSON model field for django>=3.1 that would be placed automatically. for <=3.1 we
# need to set the field explicitly. defined here for both cases to have consistent ordering.
field_json = serializers.JSONField()

class Meta:
fields = '__all__'
model = AllFields
Expand Down Expand Up @@ -213,6 +230,8 @@ def test_model_setup_is_valid():
field_regex='12345asdfg-a',
field_bool_override=True,
)
if DJANGO_VERSION >= '3.1':
m.field_json = {'A': 1, 'B': 2}
m.field_file.save('hello.txt', ContentFile("hello world"), save=True)
m.save()
m.field_m2m.add(aux)
Expand Down
9 changes: 9 additions & 0 deletions tests/test_fields.yml
Expand Up @@ -158,6 +158,13 @@ components:
type: number
format: float
readOnly: true
field_dict_int:
type: object
additionalProperties:
type: integer
field_json:
type: object
additionalProperties: {}
field_int:
type: integer
field_float:
Expand Down Expand Up @@ -246,6 +253,7 @@ components:
- field_datetime
- field_decimal
- field_decimal_uncoerced
- field_dict_int
- field_duration
- field_email
- field_file
Expand All @@ -256,6 +264,7 @@ components:
- field_int
- field_ip
- field_ip_generic
- field_json
- field_list
- field_m2m
- field_method_float
Expand Down

0 comments on commit 0dbd9d1

Please sign in to comment.