Unable to make the ImageField as required in Swagger POST API
MounikaKommineni opened this issue ยท 29 comments
My model:
class Image(models.Model):
image=models.ImageField(upload_to='photos')
name=models.CharField(max_length=40,unique=False)
My serializer:
class imagesSerializer(serializers.ModelSerializer):
image = Base64ImageField(required=True)
class Meta:
model = Image
fields = ('id','name','image')
How to make the imagefield as required in swagger post request ?
I noticed this too in generated Angular TypeScript. The image variables were always marked with ?
, meaning they were optional.
I am also facing the same issue even though i specified required=True in serializer class
I'm using the same serializer setup and mine seems to be working fine. Maybe this is an issue of differences in versions. Here are the versions of Django, Django Extra Fields, Django REST Framework and Django REST Swagger that I have used, so you can compare: Django==2.1.7
, django-extra-fields==1.0.0
, djangorestframework==3.9.2
, django-rest-swagger==2.2.0
.
I wonder if it's because of the readOnly
property? Would a readOnly
property make it optional because it's not required when writing to the API? Is this because of how the file is normally returned as a URL rather than the data directly? (I'm just spit balling at this point).
ReadOnly fields cannot be required at the same time, therefore it's not possible to have a field where both required
and read_only
are set to True
in the latest version of Django REST Framework.
What's weird is in my case, I never set read_only
to True
. The serializer is a WritableNestedSerializer
, but I don't see how that would affect the field.
@baranugur Could you also post your Image
Model
?
I wonder why your image
field doesn't say string($uri)
. That is how mine always is (and the OP's).
Also, I'm using drf-yasg == 1.13.0
instead of django-rest-swagger
due to django-rest-swagger
having low maintenance as of late. Perhaps others in the thread could comment on which they are using.
Cross posted to axnsan12/drf-yasg#332
Confirmed this is drf-yasg
specific (intended) behavior:
axnsan12/drf-yasg#332 (comment)
That is indeed the case. The field you linked breaks the contract of FileField by making it accept a string instead of an UploadedFile.
You can work around this with a custom FieldInspector that returns an appropriate Schema object of type string, which should be placed before the FileFieldInspector.
Marking as completed.
@yigitguler There may be a way to work around this (at least in documentation): axnsan12/drf-yasg#332 (comment)
I don't quite understand what needs to be done, but if this issue were left open it might be useful. We may be able to at least document how to make this work with drf-yasg
, which I think would be valuable given it's the best maintained OpenAPI generator available for Django currently.
Do you happen to understand the comment in the link above, and how we could fix it?
We can reopen the issue. However, I think there is nothing we can do to fix this situation. Developers of drf-yasg assumed that only multipart requests can contain files. Which is not true for our use case.
Using Custom Schema Generation may be a method to solve this.
EDITED : Fix the class checking algorithm. My previous answer does not work on FileField or FieldMixin.
Hi! This is the work around code that I've overridden FieldInspector to mark Base64ImageField, Base64FileField, Base64FieldMixin as required field
NOTE: I've to remove format from the String because the POST, PUT, PATCH... method use SchemaRef which reference the schema from GET method.
from drf_yasg import openapi
from drf_yasg.inspectors import FieldInspector, SwaggerAutoSchema
from drf_yasg.app_settings import swagger_settings
class Base64FileFieldInspector(FieldInspector):
BASE_64_FIELDS = ['Base64ImageField', 'Base64FileField', 'Base64FieldMixin']
def __classlookup(self, cls):
"""List all base class of the given class"""
c = list(cls.__bases__)
for base in c:
c.extend(self.__classlookup(base))
return c
def process_result(self, result, method_name, obj, **kwargs):
if isinstance(result, openapi.Schema.OR_REF):
base_classes = [x.__name__ for x in self.__classlookup(obj.__class__)]
if any(item in Base64FileFieldInspector.BASE_64_FIELDS for item in base_classes):
schema = openapi.resolve_ref(result, self.components)
schema.pop('readOnly', None)
schema.pop('format', None) # Remove $url format from string
return result
class Base64FileAutoSchema(SwaggerAutoSchema):
field_inspectors = [Base64FileFieldInspector] + swagger_settings.DEFAULT_FIELD_INSPECTORS
class FormAttachmentViewSet(viewsets.ModelViewSet):
queryset = .......
serializer_class = ..........
swagger_schema = Base64FileAutoSchema
@WasinTh Looks pretty neat -- would you mind opening a PR to this repo's README? I think it would be good to document this for others and for others to test out.
@johnthagen sure this is the pull request on README.md file
#100
How about make it some example code to testprojet, this repo example does not have about flieupload or imageupload,it would be make good to use
In Swagger API, I'm using the following setup to show the field as required without any problems:
class PDFBase64FileField(Base64FileField):
ALLOWED_TYPES = ['pdf']
class Meta:
swagger_schema_fields = {
'type': 'string',
'title': 'File Content',
'description': 'Content of the file base64 encoded',
'read_only': False # <-- FIX
}
def get_file_extension(self, filename, decoded_file):
try:
PyPDF2.PdfFileReader(io.BytesIO(decoded_file))
except PyPDF2.utils.PdfReadError as e:
logger.warning(e)
else:
return 'pdf'
And to use "string" format instead of an URL, you will need to create the field with use_url=False.
@alicertel No problem! I got it from the code at FieldInspector.add_manual_fields
from yasg. It seems to work pretty well and it's less work than creating a new FieldInspector..
@WasinTh Would you like to update your PR #100 with the suggested solution #66 (comment) ?
@WasinTh Would you like to update your PR #100 with the suggested solution #66 (comment) ?
@alicertel Sure. I've pushed the updated answer on those PRs.
you should change your parser like this :
from rest_framework import permissions, viewsets
from rest_framework.mixins import (CreateModelMixin, DestroyModelMixin,
ListModelMixin, RetrieveModelMixin)
from rest_framework.parsers import FormParser, MultiPartParser
from .models import Customer
from .permissions import CustomerPermission
from .serializer import CustomerSerializer
class CustomerViewSet(CreateModelMixin, ListModelMixin, RetrieveModelMixin,
DestroyModelMixin, viewsets.GenericViewSet):
permission_classes = [CustomerPermission]
queryset = Customer.objects.all()
serializer_class = CustomerSerializer
parser_classes = (FormParser, MultiPartParser)
after that you can check your swagger :
I hope this can help you
If you want create no class views but use drf:
register_parameters = [openapi.Parameter(
'date_of_birth', in_=openapi.IN_FORM, description='Add date like 2000-12-12', type=openapi.FORMAT_DATE,
required=True)]
@swagger_auto_schema(methods=['post'], request_body=RegisterSerializer, manual_parameters=register_parameters)
@ api_view(['POST', ])
@ parser_classes([parsers.MultiPartParser, parsers.FormParser])
def user_register_serializer(request):
if request.method == 'POST':
new_data = {
'response': f"Email send to {request.data['email']}",
}
serializer = RegisterSerializer(data=request.data)
if serializer.is_valid():
new_data.update(serializer.data)
new_data.pop('password')
serializer.save()
user_email_send(request)
else:
new_data = serializer.errors
return Response(new_data)
using manual_parameters you can override form in swagger and add new functions like default etc to image.