Django rest framework Api documentation Swagger 2.0 - django

I am having a hard time configuring Swagger UI. Here are the very explanatory docs: https://django-rest-swagger.readthedocs.io/en/latest/
YAML docstrings are deprecated. Does somebody know how to configure Swagger UI (query parameters, etc) from within the python code?
If it's impossible for some strange reason. Is there any working alternative or is it the best for me to just go and write, api documentation by hand?

OK, found it. This is not ideal solution - but needed this for frontend (web and mobile) devs - and it do the job.
Basically the newest DRF and Swagger uses from rest_framework.schemas import SchemaGenerator for providing the docs for Swagger.
So I needed to little extend it:
# -*- coding: utf-8 -*-
import urlparse
import coreapi
from rest_framework.schemas import SchemaGenerator
class ParamsSchemaGenerator(SchemaGenerator):
def get_link(self, path, method, callback, view):
"""
Return a `coreapi.Link` instance for the given endpoint.
"""
fields = self.get_path_fields(path, method, callback, view)
fields += self.get_serializer_fields(path, method, callback, view)
fields += self.get_pagination_fields(path, method, callback, view)
fields += self.get_filter_fields(path, method, callback, view)
fields += self.get_docs_fields(path, method, callback, view) # this is the extended line;
if fields and any([field.location in ('form', 'body') for field in fields]):
encoding = self.get_encoding(path, method, callback, view)
else:
encoding = None
if self.url and path.startswith('/'):
path = path[1:]
return coreapi.Link(
url=urlparse.urljoin(self.url, path),
action=method.lower(),
encoding=encoding,
fields=fields
)
# and this is fully custom additional docs method;
def get_docs_fields(self, path, method, callback, view):
fields = []
if hasattr(view, 'docs_fields'):
for field in view.docs_fields:
field = coreapi.Field(
name=field.get('name'),
location=field.get('query'),
required=field.get('required'),
type=field.get('type'),
description=field.get('description')
)
fields.append(field)
return fields
Then i need to define a function that will return the schemas with the generator defined above:
# -*- coding: utf-8 -*-
# monkey patching FTW!
from rest_framework import exceptions
from rest_framework.permissions import AllowAny
from rest_framework.renderers import CoreJSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_swagger import renderers
from kolomnie.core.schema.generator import ParamsSchemaGenerator
def get_params_swagger_view(title=None, url=None):
"""
Returns schema view which renders Swagger/OpenAPI.
(Replace with DRF get_schema_view shortcut in 3.5)
"""
class SwaggerSchemaView(APIView):
_ignore_model_permissions = True
exclude_from_schema = True
permission_classes = [AllowAny]
renderer_classes = [
CoreJSONRenderer,
renderers.OpenAPIRenderer,
renderers.SwaggerUIRenderer
]
def get(self, request):
generator = ParamsSchemaGenerator(title=title, url=url)
schema = generator.get_schema(request=request)
if not schema:
raise exceptions.ValidationError(
'The schema generator did not return a schema Document'
)
return Response(schema)
return SwaggerSchemaView.as_view()
This is how i put it in the urls:
if settings.DEBUG:
api_views = get_params_swagger_view(title='Some API')
And now little more magic, I defined a mixin for the view which stores the documentation fields:
# -*- coding: utf-8 -*-
class DocsMixin(object):
"""
This mixin can be used to document the query parameters in GET
if there's no other way to do it. Please refer to the: ParamsSchemaGenerator.get_links
for more information;
"""
docs_fields = []
And this is how I use it:
class BaseSearchResultsView(generics.GenericAPIView, SearchDocs):
....
Where SearchDocs is this:
class SearchDocs(DocsMixin):
"""
Documents the get query in search;
"""
docs_fields = [
{
'name': 'q',
'location': 'query',
'required': False,
'type': 'string',
'description': 'The base query for the search;',
},
...
Just find out that I do not need mixin :) Just docs_fields defined on the view.
Probably this will not fulfill all your needs - but think it's a good start :)
Happy coding!

Related

How to view API data within a GET method that's created using POST method in Django (without a model)?

I have created a DRF API that allows me to submit an image using the POST method via POSTMAN. The image is not stored in the model. After it's submission, I want to view the image's name in the browser using the Django Rest Framework. After reading sources in the net, I found out that people used the GET method in order to view all the data in a model. However, I don't have a model (don't require it for now) so how can I implement this requirement?
The result should be something like this:
{
"image_name": <"name_of_the_image_stored">
}
This is what I had done so far:
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import ImgSerializer
from rest_framework import status
from rest_framework.parsers import FileUploadParser
class ImageAPI(APIView):
parser_classes = (FileUploadParser,)
def post(self, request):
#key is 'image' when uploading in POSTMAN
file = self.request.data
data = file['image']
if data:
uploaded_file = data
fs = FileSystemStorage(location=settings.PRIVATE_STORAGE_ROOT)
filename = fs.save(uploaded_file.name, uploaded_file)
data = [{"image_name": filename}]
serializer = ImgSerializer(data, many = True).data
return Response(serializer, status = 200)
else:
return Response("Please upload an image", status = 400)
def get(self, request):
#What should I do here in order to view the submitted image above?
serializers.py:
from rest_framework import serializers
class ImgSerializer(serializers.Serializer):
image_name = serializers.CharField()
urls.py:
from upload.views import ImageAPI
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [
path("api/", ImageAPI.as_view(), name = "api"),
]
urlpatterns = format_suffix_patterns(urlpatterns)
First of all parser_classes should be an attribute of ImageAPI class, as I can see you've created it as a local variable, which won't do what you want. According to docs the request.data property should be a dictionary with a single key file containing the uploaded file. And regarding viewing the saved image, here you can find some ways to do that.
This is an example:
...
def get(self, request, *args, **kwargs):
# here I assume you've sent the name of the image using query params,
# but there are other better ways to do that
image_name = request.GET.get('image')
# here you should read the file from your storage
image_file = <read_image_file_by_given_name(image_name)>
return HttpResponse(image_file, content_type='image/png')

Set some views which use Django Rest Framework API

I have a very basic Django Rest API.
I don't know how to have some HTML views, in the same django project, which uses API (finally keeping API returning JSON only).
I followed this, but it seems to change the API View (in this case, curl will retrieve HTML and not JSON) :
https://www.django-rest-framework.org/api-guide/renderers/#templatehtmlrenderer
Do I need another Django App ? Another Django project ? Some JS ?
EDIT :
Ok, I've seen it's possible, thanks to rrebase.
But I can't retrieve the JSON with Curl, here my views.py
from rest_framework import generics
from rest_framework.renderers import TemplateHTMLRenderer, JSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAdminUser
from . import models
from . import serializers
class UserListView(generics.ListAPIView):
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
template_name = 'profile_list.html'
def get(self, request):
queryset = models.CustomUser.objects.all()
serializer_class = serializers.UserSerializer
return Response({'profiles': queryset})
My models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
def __str__(self):
return self.email
I get an error "Object of type 'CustomUser' is not JSON serializable" when I request the API (http://127.0.0.1:8000/api/v1/users/)
Sorry, it's some different that initial question...
Yes, you can have both. The link you provided to docs has the following:
You can use TemplateHTMLRenderer either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint.
When making an API request, set the ACCEPT request-header accordingly to html or json.
Finally I made some conditions in my view, and it's working
class UserListView(generics.ListAPIView):
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
permission_classes = (IsAdminUser,)
def get(self, request):
queryset = CustomUser.objects.all()
if request.accepted_renderer.format == 'html':
data = {'profiles': queryset}
return Response(data, template_name='profile_list.html')
else:
serializer = UserSerializer(queryset, many=True)
data = serializer.data
return Response(data)

Returning objects using JSONRESPONSE

I'm trying to return an object using Jsonresponse, Sorry im newb
This is my script:
setInterval(function()
{
$.ajax({
url: '/check_notification/',
type: "POST",
dataType: 'json',
success: function (data) {}
});
}, 2000);
in my django views.py:
def check_notification(request):
user = request.user
profile = Person.objects.get(profile=user)
notification = NotificationRecipient.objects.filter(profile=profile)
return JsonResponse(model_to_dict(notification))
You can make a model Serializers for models you want to pass as response. for more information read django rest framework tutorial about serializers an learn how to make json response. Or if you have a simple dictionary you can make json response with this code snippet at the end of your check_notification function. return HttpResponse(json.dumps(your dictionary))
I would recommend you use Django Rest Framework for returning Responses in JSON format as the Serialization of models can be don easily. You can start from here. There you will find something known as a ModelSerializer. Basically you create a serializer.py in your app folder with the following content:
from rest_framework import serializers
from .models import Person, NotificationRecipient
class PersonSerializers(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
class NotificationRecipientSerializers(serializers.ModelSerializer):
class Meta:
model = NotificationRecipient
fields = '__all__'
The above code will serialize your models which means will be converted to json format.
Than in a file named as views_api.py, you can create a class that will be called using by the URL and will have your queryset defined. In your case the class will be defined as:
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Person, NotificationRecipient
from .serializers import NotificationRecipientSerializers
class NotificationAPIView(APIView):
def get(self,request):
user = request.user
profile = Person.objects.get(profile=user)
notification = NotificationRecipient.objects.filter(profile=profile)
return Response(notification)
This will return the response in JSON format. In your urls.py file call the NotificationAPIView as follows:
from django.urls import path
from .import views_api
urlpatterns = [
path('check/notification/', views_api.NotificationAPIView.as_view(), name='notification'),
]
Hope you get a basic understanding of what is going on there. For better understanding go through Django Rest Framework docs.

Testing custom admin actions in django

I'm new to django and I'm having trouble testing custom actions(e.g actions=['mark_as_read']) that are in the drop down on the app_model_changelist, it's the same dropdown with the standard "delete selected". The custom actions work in the admin view, but I just dont know how to call it in my mock request, I know I need to post data but how to say I want "mark_as_read" action to be done on the data I posted?
I want to reverse the changelist url and post the queryset so the "mark_as_read" action function will process the data I posted.
change_url = urlresolvers.reverse('admin:app_model_changelist')
response = client.post(change_url, <QuerySet>)
Just pass the parameter action with the action name.
response = client.post(change_url, {'action': 'mark_as_read', ...})
Checked items are passed as _selected_action parameter. So code will be like this:
fixtures = [MyModel.objects.create(read=False),
MyModel.objects.create(read=True)]
should_be_untouched = MyModel.objects.create(read=False)
#note the unicode() call below
data = {'action': 'mark_as_read',
'_selected_action': [unicode(f.pk) for f in fixtures]}
response = client.post(change_url, data)
This is what I do:
data = {'action': 'mark_as_read', '_selected_action': Node.objects.filter(...).values_list('pk', flat=True)}
response = self.client.post(reverse(change_url), data, follow=True)
self.assertContains(response, "blah blah...")
self.assertEqual(Node.objects.filter(field_to_check=..., pk__in=data['_selected_action']).count(), 0)
A few notes on that, comparing to the accepted answer:
We can use values_list instead of list comprehension to obtain the ids.
We need to specify follow=True because it is expected that a successfull post will lead to a redirect
Optionally assert for a successful message
Check that the changes indeed are reflected on the db.
Here is how you do it with login and everything, a complete test case:
from django.test import TestCase
from django.urls import reverse
from content_app.models import Content
class ContentModelAdminTests(TestCase):
def setUp(self):
# Create some object to perform the action on
self.content = Content.objects.create(titles='{"main": "test tile", "seo": "test seo"}')
# Create auth user for views using api request factory
self.username = 'content_tester'
self.password = 'goldenstandard'
self.user = User.objects.create_superuser(self.username, 'test#example.com', self.password)
def shortDescription(self):
return None
def test_actions1(self):
"""
Testing export_as_json action
App is content_app, model is content
modify as per your app/model
"""
data = {'action': 'export_as_json',
'_selected_action': [self.content._id, ]}
change_url = reverse('admin:content_app_content_changelist')
self.client.login(username=self.username, password=self.password)
response = self.client.post(change_url, data)
self.client.logout()
self.assertEqual(response.status_code, 200)
Just modify to use your model and custom action and run your test.
UPDATE: If you get a 302, you may need to use follow=True in self.client.post().
Note that even if the POST is successful, you still need to test that your action performed the operations intended successfully.
Here's another method to test the action directly from the Admin class:
from django.contrib.auth.models import User
from django.contrib.admin.sites import AdminSite
from django.shortcuts import reverse
from django.test import RequestFactory, TestCase
from django.contrib.messages.storage.fallback import FallbackStorage
from myapp.models import MyModel
from myapp.admin import MyModelAdmin
class MyAdminTestCase(TestCase):
def setUp(self) -> None:
self.site = AdminSite()
self.factory = RequestFactory()
self.superuser = User.objects.create_superuser(username="superuser", is_staff=True)
def test_admin_action(self):
ma = MyModelAdmin(MyModel, self.site)
url = reverse("admin:myapp_mymodel_changelist")
superuser_request = self.factory.get(url)
superuser_request.user = self.superuser
# if using 'messages' in your actions
setattr(superuser_request, 'session', 'session')
messages = FallbackStorage(superuser_request)
setattr(superuser_request, '_messages', messages)
qs = MyModel.objects.all()
ma.mymodeladminaction(superuser_request, queryset=qs)
# check that your action performed the operations intended
...

ImportError for Django Piston handler that isn't tied to a model

I have created a custom handler (CustomHandler) that isn't tied to a model in the ORM and I think it's rigged up correctly, but I'm getting an ImportError: cannot import CustomHandler when trying to import it into my resources.py. Here is my setup:
custom_handlers.py:
from piston.handler import BaseHandler
class CustomHandler(BaseHandler):
allowed_methods = ('GET',)
def read(self, request):
return 'test'
resources.py:
from piston.resource import Resource
from piston.utils import rc
import simplejson as json
from api.authentication import DjangoAuthentication
from api.handlers import CustomHandler # ERROR THROWN HERE
auth = DjangoAuthentication(realm='...')
class JSONResource(Resource):
def determine_emitter(self, request, *args, **kwargs):
"""
Default to the json emitter.
"""
try:
return kwargs['emitter_format']
except KeyError:
pass
if 'format' in request.GET:
return request.GET.get('format')
return 'json'
def form_validation_response(self, e):
"""
Turns the error object into a serializable construct.
"""
resp = rc.BAD_REQUEST
json_errors = json.dumps(
dict(
(k, map(unicode, v))
for (k, v) in e.form.errors.iteritems()
)
)
resp.write(json_errors)
return resp
custom_handler = JSONResource(CustomHandler, authentication=auth)
urls.py:
from django.conf.urls.defaults import patterns, url
from api.resources import custom_handler
urlpatterns = patterns('',
url(r'^things/$', custom_handler),
)
UPDATE: I've tried manually compiling the pys into pycs with no luck. I also read this in Piston's docs:
When you create a handler which is tied to a model, Piston will
automatically register it (via a metaclass.)
But I can't find anything in the docs about creating a handler that isn't tied to a model, specifically how to register it.
Had to add from api.handlers.custom_handlers import CustomHandler to api/handlers/__init__.py