Integrate Swagger 0.3.x with Django and Rest Framework - django

Working on rather old project running Django 1.6 there's a need to integrate Swagger to describe REST endpoints.
I've installed compatible version of Django Rest Framework (3.2.5) and Django Rest Swagger (0.3.0), then imported both into INSTALLED APPS and included DRF-Swagger's urls in my url scheme:
...
url(r'^api/v1/$', include('rest_framework_swagger.urls')),
...
When I go to this URL I see that Swagger is working, but I can't understand what should I do next to make it work with my endpoints and show information about them? Should I add something to SWAGGER_SETTINGS to be able to read YAML insertions from methods?

The fastest way to configure this version of swagger to work with DRF is first to install both packages compatible with Django 1.6:
pip install djangorestframework==3.2.5
pip install django-rest-swagger==0.3.0
Then you should add both to installed apps in project settings:
INSTALLED_APPS = (
...
'rest_framework',
'rest_framework_swagger',
)
Optionally you can add SWAGGER_SETTINGS to project settings, but it's not mandatory, here's a link to SWAGGER_SETTINGS.
Then you should add this pattern to your urls:
url(r'^docs/', include('rest_framework_swagger.urls'))
you can make it extend existing path, for example:
url(r'^<your root path>/docs/', include('rest_framework_swagger.urls'))
but the key is that this url should end with exactly docs/ to be able to access swagger. At this point if everything is done correctly you should be able to access Swagger at:
<your root path>/docs/
Last thing you need to do is import and decorate your endpoint View with api_view decorator:
from rest_framework.decorators import api_view
...
#api_view(["GET"])
def my_api_view(request, parameter_a, parameter_b):
"""
Endpoint returns list of open orders
---
parameters:
- name: parameter_a
description: Description for parameter a
required: true
paramType: path
- name: parameter_b
description: Description for parameter b
required: true
paramType: path
"""
...rest of View...
This will tell Swagger that this endpoint is related to DRF and has description to be displayed at <your root path>/docs/

Related

Set up DRF Browsable API Root with all api urls

In my urls.py I have many rest framework urls:
path(
"api/",
include([
path("users/", api_views.users, name="users"),
path("proposals/", api_views.Proposals.as_view(), name="proposals"),
path("requests/", api_views.Requests.as_view(), name="requests"),
#...
])
)
I can visit the individual endpoints in the browser and access the browsable API, but I want to set up a browsable API Root where I can see all the available endpoints. The DRF tutorial has an example in which they list the urls they want in the root, but I have a lot of urls and I want all of them to show up. Is there a way to include all the urls in my "api/" path?
You should try to use drf_yasg package. Here's a documentation for that.

Custom Grouping on OpenAPI endpoints with Django Rest Framework

I have a Django project and I am using Django REST framework. I am using drf-spectacular
for OpenAPI representation, but I think my problem is not tied to this package, it's seems a more generic OpenAPI thing to me (but not 100% sure if I am right to this).
Assume that I have a URL structure like this:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include([
path('v1/', include([
path('auth/', include('rest_framework.urls', namespace='rest_framework')),
path('jwt-auth/token/obtain', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('jwt-auth/token/refresh', CustomTokenRefreshView.as_view(), name='token_refresh'),
path('home/', include("home.urls"))
]))
])),
# OpenAPI endpoints
path('swagger/', SpectacularSwaggerView.as_view(url_name='schema-swagger-json'), name='schema-swagger-ui'),
path('swagger.yaml/', SpectacularAPIView.as_view(), name='schema-swagger-yaml'),
path('swagger.json/', SpectacularJSONAPIView.as_view(), name='schema-swagger-json'),
path('redoc/', SpectacularRedocView.as_view(url_name='schema-swagger-yaml'), name='schema-redoc'),
]
In the corresponding swagger UI view, I get all endpoints grouped under api endpoint, e.g.:
If add more endpoints under v1, all go under the api endpoint.
What I want to achieve is, to have the endpoints in Swagger grouped differently, e.g. by app. So I'd have home, jwt, another_endpoint, instead of just api, so it will be easier to navigate in Swagger (when I add more endpoints, with the current method it's just showing a massive list of URLs, not very user friendly).
I've read that those groups are being extracted from the first path of a URL, in my case this is api, so if I change the URL structure, I could achieve what I need.
But isn't there another way of doing this? I want to keep my URL structure, and customize how I display this with OpenAPI, so in the end I have a swagger that groups the endpoints by app, so it's easier to navigate and find what you are looking for.
you are making it harder than it needs to be. In the global settings you can specify a common prefix regex that strips the unwanted parts. that would clean up both operation_id and tags for you. In your case that would probably be:
SPECTACULAR_SETTINGS = {
'SCHEMA_PATH_PREFIX': r'/api/v[0-9]',
}
that should result in tags: home, jwt-auth, swagger.json, swagger.yaml
the tags on #extend_schema is merely a convenience to deviate from the default where needed. it would be cumbersome to do this for every operation. see the settings for more details:
https://drf-spectacular.readthedocs.io/en/latest/settings.html
for even more elaborate tagging you can always subclass AutoSchema and override get_tags(self) to your liking. cheers!
Turns out that you can control this by changing the tags in a view, as per OpenAPI specification: https://swagger.io/docs/specification/grouping-operations-with-tags/
So, with drf-spectacular, you can use the extend_schema decorator to achieve this, e.g.:
from drf_spectacular.utils import extend_schema
class CustomTokenObtainPairView(TokenObtainPairView):
"""
Takes a set of user credentials and returns an access and refresh JSON web
token pair to prove the authentication of those credentials.
"""
#extend_schema(
operation_id="jwt_obtain",
....
tags=["aTestTag"]
)
def post(self, request, *args, **kwargs):
# whatever
So you have to use this decorator to extend the schema in each view that you want to put into a custom group.

How to have a local instance or React hit a local instance of Django

I have a django api (djangorestframekwork, django2.1, python 3.6) that I run locally - http://127.0.0.1:8000/api/cards/b361d7e2-6873-4890-8f87-702d9c89c5ad. This api seems to work well. I can hit it via the web api or use curl/requests to add to or view the database.
Now, I want to be able to have my React project hit this api. I can have my react project hit a different api and it returns the data, but when I replace that URI with my own URI it breaks.
App.js - there is one line commented out. Switch that out with the other to switch between the public and private api.
import React from 'react'
import keyforge from '../api/keyforge'
import localhost from '../api/localhost'
import SearchBar from './SearchBar'
class App extends React.Component {
onSearchSubmit = async (term) => {
const response = await localhost.get("api/cards/" + term)
//const response = await keyforge.get("api/decks/" + term)
console.log(response)
}
render () {
return (<div className='ui container'>
<SearchBar onSubmit={this.onSearchSubmit} />
</div>)
}
}
export default App
keyforge.js - this one works!
import axios from 'axios'
const proxyurl = 'https://cors-anywhere.herokuapp.com/'
const url = 'https://www.keyforgegame.com/'
export default axios.create({
// baseURL: 'https://www.keyforgegame.com/api/decks/'
baseURL: proxyurl + url
})
localhost.js - this one does not work
import axios from 'axios'
const proxyurl = 'https://cors-anywhere.herokuapp.com/'
const url = 'http://127.0.0.1:8000/'
export default axios.create({
baseURL: proxyurl + url
})
Error message:
GET https://cors-anywhere.herokuapp.com/http://127.0.0.1:8000/api/cards/b361d7e2-6873-4890-8f87-702d9c89c5ad 404 (Not Found)
Uncaught (in promise) Error: Request failed with status code 404
at createError (createError.js:17)
at settle (settle.js:19)
at XMLHttpRequest.handleLoad (xhr.js:78)
On my computer - http://127.0.0.1:8000/api/cards/b361d7e2-6873-4890-8f87-702d9c89c5ad takes me to the Django api page for that specifc element. If I get rid of the cors-anywhere url I get this error -
Access to XMLHttpRequest at 'http://127.0.0.1:8000/api/cards/b361d7e2-6873-4890-8f87-702d9c89c5ad' from origin 'http://127.0.0.1:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I have this cors-anywhere url prepended to it to solve the No Access-Control-Allow-Origin error that you see when I hit this to the public api. Since I get the same error when I hit my own private api, I am using the same solution (e.g. the cors-anywhere url prepeneded to my url.
Thoughts?
Probably because you didn't enabled CORS on your django rest project. There is a tool called django-cors-headers, Install it via pip install django-cors-headers, add it's midlleware and enable it in your settings.py file.
pip install django-cors-headers
then add it to installed apps:
INSTALLED_APPS = (
...
'corsheaders',
...
)
add it's middleware:
MIDDLEWARE_CLASSES = (
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
)
and finally add this variable in the end of your settings.py file:
CORS_ORIGIN_ALLOW_ALL = True
Find out more about it here: django-cors-headers on github
Your keyforge.js file works because you're hitting an already hosted website which has CORS enabled at it's backend as well. You need to add CORS at your Django backend as well. From what you've said, it seems that you've only added it at the front end. Probably something like could help you.
How can I enable CORS on Django REST Framework

Django Rest Framework include_docs_urls adding _0 to action

We have urls in our Django (Rest Framework) applications like:
r'^endpoint/(?P<item>[a-z_-]+)/$'
r'^endpoint/(?P<item>[a-z_-]+)/(?P<version>[0-9]+(\.[0-9])?)/$'
Both have POST methods available.
We've been using Swagger for a while to document our API but wanted to look at the coreapi documentation included in Django Rest Framework.
Going through our documentation based on the above structure the coreapi action results in:
# Initialize a client & load the schema document
client = coreapi.Client()
schema = client.get("http://localhost:8081/docs/")
# Interact with the first url
action = ["app", "endpoint > create"]
# Interact with the second url
action = ["app", "endpoint > create_0"]
I can understand where create_0 is coming from, but ideally it would add the keyword name as a suffix instead, e.g. create_version.
Is this possible?
Having two keywords right after each other seems to be the problem.
r'^endpoint/(?P<item>[a-z_-]+)/$'
r'^endpoint/(?P<item>[a-z_-]+)/(?P<version>[0-9]+(\.[0-9])?)/$'
Should be replaced with:
r'^endpoint/(?P<item>[a-z_-]+)/$'
r'^endpoint/(?P<item>[a-z_-]+)/version/(?P<version>[0-9]+(\.[0-9])?)/$'
That will give you:
action = ["endpoint", "item > version > create"]
Which looks much cleaner.

Dropbox and Django SSO using SAML

Summary
I am looking to use Dropbox SSO functionality by using the authentication from a Django site. Note that I'm not looking to use SAML as a backend for my Django site.
Resources
1) Dropbox Custom SSO help page: https://www.dropbox.com/en/help/1921#custom
2) Creating a SAML response: https://robinelvin.wordpress.com/2009/09/04/saml-with-django/
3) Struggled to find any examples from Google of people doing this kind of SSO. Lots of links about people using SAML as a Django backend.
Question
In the dropbox admin settings I can add my X509 certificate and the login link. This means that when you try to login into Dropbox using SSO it nicely forwards you to my Django site's login page using a GET request with a SAMLRequest in the querystring.
However, my understanding is that I now need to, once the user is authenticated on the Django site, fire a POST request back to Dropbox at their SAML login link with a SAMLResponse in the post data. Using the second resource above I believe I can create the SAMLResponse xml but I am unsure how to redirect the user to the dropbox SAML login link with the SAML data from my Django view.
Any help much appreciated.
Managed to get the functionality I needed using django-saml2-idp https://github.com/peopledoc/django-saml2-idp
Good documentation on installing here: https://github.com/peopledoc/django-saml2-idp/blob/master/doc/INSTALL.txt
Settings in the Dropbox Admin console required the X509 certificate and then the login url set to: https://****.com/idp/login
Note that I had issues installing the M2Crypto dependency so used an Ubuntu package via:
sudo apt-get install python-m2crypto
Additionally I'm using Django 1.9.6 so needed to make overrides to the views.py, urls.py, and registry.py files to make them compatible (various import statements needed updating and the urls changed to the new list format rather than using patterns).
Created a Dropbox Processor as follows:
import base64
import zlib
from saml2idp import base
from saml2idp.xml_render import _get_assertion_xml
def get_assertion_dropbox_xml(parameters, signed=False):
return _get_assertion_xml(ASSERTION_DROPBOX, parameters, signed)
ASSERTION_DROPBOX = (
'<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" '
'ID="${ASSERTION_ID}" '
'IssueInstant="${ISSUE_INSTANT}" '
'Version="2.0">'
'<saml:Issuer>${ISSUER}</saml:Issuer>'
'${ASSERTION_SIGNATURE}'
'${SUBJECT_STATEMENT}'
'<saml:Conditions NotBefore="${NOT_BEFORE}" NotOnOrAfter="${NOT_ON_OR_AFTER}">'
'<saml:AudienceRestriction>'
'<saml:Audience>${AUDIENCE}</saml:Audience>'
'</saml:AudienceRestriction>'
'</saml:Conditions>'
'<saml:AuthnStatement AuthnInstant="${AUTH_INSTANT}"'
'>'
'<saml:AuthnContext>'
'<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>'
'</saml:AuthnContext>'
'</saml:AuthnStatement>'
'${ATTRIBUTE_STATEMENT}'
'</saml:Assertion>'
)
class Processor(base.Processor):
def _decode_request(self):
"""
Decodes _request_xml from _saml_request.
"""
self._request_xml = zlib.decompress(base64.b64decode(self._saml_request), -15)
def _format_assertion(self):
self._assertion_xml = get_assertion_dropbox_xml(self._assertion_params, signed=False)
Which you register in your settings.py file as follows:
SAML2IDP_CONFIG = {
'autosubmit': True,
'certificate_file': '/****/certificate.pem',
'private_key_file': '/****/private-key.pem',
'issuer': 'https://www.****.com',
'signing': True,
}
sampleSpConfig = {
'acs_url': 'https://www.dropbox.com/saml_login',
'processor': 'dropbox.Processor',
}
SAML2IDP_REMOTES = {
'sample': sampleSpConfig,
}
Works like a dream. Hope this helps somebody out there.