Not able to generate a swagger file with multipart/form-data content-type in request
Description
I have a POST request to upload a document where I send the document sent in multipart/form-data. I have tried to describe the form data as such
This is how my request looks in postman
When I try to generate a swagger file. It gives me the following error
drf_yasg.errors.SwaggerGenerationError: cannot add form parameters when the request has a request body; did you forget to set an appropriate parser class on the view?
Minimal Reproduction
#swagger_auto_schema(
operation_id='Create a document',
operation_description='Create a document by providing file and s3_key',
manual_parameters=[
openapi.Parameter('file', openapi.IN_FORM, type=openapi.TYPE_FILE, description='Document to be uploaded'),
openapi.Parameter('s3_key', openapi.IN_FORM, type=openapi.TYPE_STRING, description='S3 Key of the Document '
'(folders along with name)')
],
responses={
status.HTTP_200_OK: openapi.Response(
'Success', schema=openapi.Schema(type=openapi.TYPE_OBJECT, properties={
'doc_id': openapi.Schema(type=openapi.TYPE_STRING, description='Document ID'),
'mime_type': openapi.Schema(type=openapi.TYPE_STRING, description='Mime Type of the Document'),
'version_id': openapi.Schema(type=openapi.TYPE_STRING, description='S3 version ID of the document')
})
)
}
)
In your View class, you need to set the MultiPartParser class, defining what type of media you are using:
from rest_framework.views import APIView
from rest_framework.parsers import MultiPartParser
class MyAPIView(APIView):
parser_classes = [MultiPartParser]
#swagger_auto_schema(
operation_id='Create a document',
operation_description='Create a document by providing file and s3_key',
manual_parameters=[
openapi.Parameter('file', openapi.IN_FORM, type=openapi.TYPE_FILE, description='Document to be uploaded'),
openapi.Parameter('s3_key', openapi.IN_FORM, type=openapi.TYPE_STRING, description='S3 Key of the Document '
'(folders along with name)')
],
responses={
status.HTTP_200_OK: openapi.Response(
'Success', schema=openapi.Schema(type=openapi.TYPE_OBJECT, properties={
'doc_id': openapi.Schema(type=openapi.TYPE_STRING, description='Document ID'),
'mime_type': openapi.Schema(type=openapi.TYPE_STRING, description='Mime Type of the Document'),
'version_id': openapi.Schema(type=openapi.TYPE_STRING, description='S3 version ID of the document')
})
)
}
)
def post(self, request, *args, **kwargs):
# Content of the post method
Related
I have an api made with djangorestframework and I want to add a swagger. It works well. In my api I have some views that require authentication with Basic or JWT so I set this up in my settings. The problem is that in my swagger gui I do not manage to tell which view is not requiring authentication. Do you know how to do that.
#api/urls.py
from drf_yasg import openapi
schema_view = get_schema_view(
openapi.Info(
title="Snippets API",
default_version='v1',
description="Test description",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="contact#snippets.local"),
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=[permissions.AllowAny],
)
urlpatterns = [
path('mesures/', views.get_all),
path('mesure-add/', views.add_mesure),
path('token/', TokenObtainPairView.as_view(), name='obtain_tokens'),
path('token/refresh/', TokenRefreshView.as_view(), name='refresh_token'),
path('api-auth/', include('rest_framework.urls')),
path('register/', views.register_user),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
In my settings.py
# in my settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=int(os.getenv("TOKEN_DAYS_LIFE"))),
}
SWAGGER_SETTINGS = {
'SECURITY_DEFINITIONS': {
'Basic': {
'type': 'basic'
},
'Bearer': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header'
}
}
}
In my views.py
#views.py
#swagger_auto_schema(methods=['post'], request_body=MesureSerializer)
#api_view(['POST'])
#permission_classes([IsAuthenticated])
def add_mesure(request):
serializer = MesureSerializer(data=request.data, context={'request':request})
# this is to provide the user automatically from token auth
# no need to provide it from the post request
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer._errors)
#swagger_auto_schema(methods=['post'], request_body=UserSerializer)
#api_view(['POST'])
def register_user(request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
as you can see my view register_user should not show a authentication in my swagger but this is not the case.
It is ok, I just needed to use the security=[] in #swagger_auto_schema for the specific view I want to tell there is no authentication required.
#swagger_auto_schema(methods=['post'], request_body=UserSerializer, security=[])
I have a simple view which takes 'email' as a query param and I would like to have it documented in the OpenAPI autogenerated schema. So far I tried applying method_decorator together with swagger_auto_schema on the API View class definition, but without success:
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from django.utils.decorators import method_decorator
#method_decorator(name='retrieve', decorator=swagger_auto_schema(manual_parameters=[
openapi.Parameter('email', openapi.IN_QUERY, description="Email to be checked", type=openapi.TYPE_STRING)]))
class EmailCheckView(generics.RetrieveAPIView):
serializer_class = EmailCheckSerializer
def get_queryset(self):
email = self.request.query_params.get('email', None)
if not email:
raise Http404
return User.objects.filter(email=self.kwargs['email'])
the auto generated models contains only information on the body coming from the serializer. Any ideas what's wrong?
DRF: 3.12.2
drf-yasg: 1.20.0
My swagger schema is added in urls.py with:
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
schema_view = get_schema_view(
openapi.Info(
title="My API",
default_version='v1',
description="",
),
public=True,
permission_classes=[permissions.AllowAny],
)
urlpatterns = [
...
path('docs/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
...
]
Change
name='retrieve'
to
name='get'
#method_decorator(
name="get", # change is here
decorator=swagger_auto_schema(
manual_parameters=[
openapi.Parameter(
"email",
openapi.IN_QUERY,
description="Email to be checked",
type=openapi.TYPE_STRING,
)
]
),
)
class EmailCheckView(generics.RetrieveAPIView):
serializer_class = EmailCheckSerializer
def get_queryset(self):
email = self.request.query_params.get("email", None)
if not email:
raise Http404
return User.objects.filter(email=self.kwargs["email"])
Note: I am not sure whether the issue belongs to method_decorator(...) or drf-yasg itself
I have a django-rest-auth project called merchant. within it I have implemented django-restauth and allauth packages with JWT.
Everything works OK. However, I wish to return additional fields in the JWT token and here's my implementation of it.
In app.views.py
def jwt_response_payload_handler(token, user=None, request=None):
return {
'token': token,
'user': User_Serializer(user, context={'request':request}).data
}
serializers.py
class User_Serializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['email', 'username', 'is_staff']
settings.py
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'merchant.coin_app.views.jwt_response_payload_handler',
'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=10),
'JWT_AUTH_HEADER_PREFIX': 'JWT'
}
The payload returned does not contain email/is_staff. Perhaps I am missing something.
The registered handler in the JWT_RESPONSE_PAYLOAD_HANDLER setting option is invoked after the JWT token is generated.
The handler requiring customization is JWT_PAYLOAD_HANDLER which creates the payload object that is tokenized and not JWT_RESPONSE_PAYLOAD_HANDLER.
In your project settings, configure
JWT_AUTH = {
'JWT_PAYLOAD_HANDLER': 'merchant.coin_app.views.jwt_payload_handler',
'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=10),
'JWT_AUTH_HEADER_PREFIX': 'JWT'
}
Then in your view, extend the result of rest_framework_jwt.utils.jwt_payload_handler
import rest_framework_jwt.utils.jwt_payload_handler as base_jwt_payload_handler
def jwt_response_payload_handler(user):
payload = base_jwt_payload_handler(user)
payload['user'] = User_Serializer(user).data
return payload
I'm getting the following error when trying to create a hold using the Google Vault API:
HttpError 500 when requesting
https://vault.googleapis.com/v1/matters/{matterId}/holds?alt=json
returned "Internal error encountered."
from google.oauth2 import service_account
import googleapiclient.discovery
SCOPES = ['https://www.googleapis.com/auth/ediscovery']
SERVICE_ACCOUNT_FILE = './serviceaccount.json'
credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_credentials = credentials.with_subject('delegateuser#example.com')
client = googleapiclient.discovery.build('vault', 'v1', credentials=delegated_credentials)
data = { 'name': 'test', 'accounts': [{'email': 'testuser#example.com' }], 'corpus': 'MAIL', 'query': { 'mailQuery': {'terms': 'to:ceo#company.com'} }}
results = client.matters().holds().create(matterId='{matterId}', body=data).execute()
I've replaced the actual matterId string with {matterId}.
Creating matters, listing matters and listing holds work just fine.
I've tried different combinations of fields to include in the request body but the docs are not clear as to which are required...
It turns out you can't use 'email' in holds().create() - you must use accountId, or the 'id' number for the gmail user.
You can use emails to create holds
https://developers.google.com/vault/guides/holds#create_a_hold_for_mail_on_specific_user_accounts_with_a_search_query
I am trying to display a form with License Type data based on request.user data.
Form works fine when using user as a input variable to the form in the view.
However when I try to upload a file using the same form and use request.File.
I receive following error.
forms.py
from django import forms
class BusinessRequestForm(forms.Form):
business_name = forms.CharField(label='Enter Business Name', widget=forms.Textarea, help_text="Enter the Name of the business you require" )
business_type= forms.ChoiceField(label='Select Your Business Type', choices=BUSINESS_TYPE, help_text="If your business type is not present. Enter details in Additional info" )
license_type =forms.ModelChoiceField(label='Select the license type',queryset=Pricing.objects.none(),help_text="Check Pricing on Main Page Pricing")
additional_detail=forms.CharField(label='Enter any additional details', widget=forms.Textarea, help_text="Give details about your Tax Structure", required=False)
tax_structure=forms.CharField(label='Tax Structure ', widget=forms.Textarea, help_text="Describe Your Tax Structure If Applicable",required=False)
sales_invoice=forms.FileField(help_text="Upload your present Sales Invoice if any",required=False)
purchase_invoice=forms.FileField(help_text="Upload your present Purchase Invoice if any",required=False)
pay_slip=forms.FileField(help_text="Upload your present Pay Slip if any",required=False)
talley_file=forms.FileField(help_text="Upload your present Talley Export if any",required=False)
def __init__(self,input_user,*args,**kwargs):
super(BusinessRequestForm,self).__init__(*args,**kwargs)
select_user=MyProfile.objects.get(user=input_user)
price=Pricing.objects.all().filter(pricing_region=select_user.region).filter(target=select_user.sales_partner)
self.fields['license_type'].queryset=price
models.py
class Business_request(models.Model):
user=models.ForeignKey("auth.User")
business_name=models.CharField("Name of Business Required",max_length=200,help_text="Enter the name of the business")
business_type=models.CharField("Select Business Type",max_length=200,choices=BUSINESS_TYPE,help_text="If your business type is not present,enter in additional details and select the closest type here")
license_type=models.ForeignKey(Pricing)
tax_structure=models.CharField("Tax Structure",max_length=200,help_text="Describe your Tax Structure",blank=True)
additional_details=models.CharField("Enter any additional details",max_length=200,blank=True)
sales_invoice=models.FileField(upload_to='businessReqForm',null=True,blank=True)
purchase_invoice=models.FileField(upload_to='budinessReqForm',null=True,blank=True)
pay_slip=models.FileField(upload_to='budinessReqForm',null=True,blank=True)
talley_file=models.FileField(upload_to='budinessReqForm',null=True,blank=True)
views.py
#login_required
def businessRequestFormView(request):
if request.method == 'POST':
form = BusinessRequestForm(request.FILES,data=request.POST,input_user=request.user,)
if form.is_valid():
business_name=form.cleaned_data['business_name']
business_type=form.cleaned_data['business_type']
license_type=form.cleaned_data['license_type']
additional_details=form.cleaned_data['additional_detail']
tax_structure=form.cleaned_data['tax_structure']
s=Business_request()
s.user=request.user
s.business_name=business_name
s.business_type=business_type
s.license_type=license_type
s.additional_details=additional_details
if request.FILES['sales_invoice']:
sales_invoice=request.FILES['sales_invoice']
s.sales_invoice=sales_invoice
if request.FILES['purchase_invoice']:
purchase_invoice=request.FILES['purchase_invoice']
s.purchase_invoice=purchase_invoice
if request.FILES['pay_slip']:
pay_slip=request.FILES['pay_slip']
s.pay_slip=pay_slip
if request.FILES['talley_file']:
talley_file=request.FILES['talley_file']
s.talley_file=talley_file
s.save()
user=request.user
sender='info#accountingbuddy.org'
subject="AccountingBuddy.Org Business Setup Request Fm %s" % user.first_name
message="Business Name : %s , Business Type: %s , License Type: %s, Additional Details : %s , User %s , Phone %s, Email %s" % (business_name,business_type,license_type, additional_details, request.user,user.myprofile.phone_no,user.email)
recipients = ['keeganpatrao#gmail.com',]
recipients +=[user.email,]
send_mail(subject, message, sender, recipients)
return HttpResponseRedirect(reverse('accountingbuddy:thanks'))
else:
form = BusinessRequestForm(input_user=request.user)
return render(request, 'business_request_form.html', {'form': form})
Error
Environment:
Request Method: POST
Request URL: https://website.com/accountingbuddy/businessrequest/submit/
Django Version: 1.10.7
Python Version: 3.5.2
Installed Applications:
('mezzanine.boot',
'accountingbuddy',
'nova',
'bootstrap3',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.redirects',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.sitemaps',
'mezzanine.conf',
'mezzanine.core',
'mezzanine.generic',
'mezzanine.pages',
'mezzanine.blog',
'mezzanine.forms',
'mezzanine.galleries',
'mezzanine.twitter',
'mezzanine.accounts',
'filebrowser_safe',
'grappelli_safe',
'django.contrib.admin',
'django.contrib.staticfiles',
'django_comments')
Installed Middleware:
('django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'mezzanine.core.request.CurrentRequestMiddleware',
'mezzanine.core.middleware.RedirectFallbackMiddleware',
'mezzanine.core.middleware.TemplateForDeviceMiddleware',
'mezzanine.core.middleware.TemplateForHostMiddleware',
'mezzanine.core.middleware.AdminLoginInterfaceSelectorMiddleware',
'mezzanine.core.middleware.SitePermissionMiddleware',
'mezzanine.pages.middleware.PageMiddleware')
Traceback:
File "/webapps/accounting/accounting_home/myenv/lib/python3.5/site-packages/django/core/handlers/exception.py" in inner
42. response = get_response(request)
File "/webapps/accounting/accounting_home/myenv/lib/python3.5/site-packages/django/core/handlers/base.py" in _legacy_get_response
249. response = self._get_response(request)
File "/webapps/accounting/accounting_home/myenv/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
187. response = self.process_exception_by_middleware(e, request)
File "/webapps/accounting/accounting_home/myenv/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
185. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/webapps/accounting/accounting_home/myenv/lib/python3.5/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
23. return view_func(request, *args, **kwargs)
File "/webapps/accounting/accounting_home/accounting_home/accountingbuddy/views.py" in businessRequestFormView
49. form = BusinessRequestForm(request.FILES,data=request.POST,input_user=request.user,)
Exception Type: TypeError at /accountingbuddy/businessrequest/submit/
Exception Value: __init__() got multiple values for argument 'input_user'