I am trying to get the right query parameters shown in the swagger documentation produced by django-rest-swagger
from rest_framework.filters import BaseFilterBackend
from rest_framework.compat import coreapi, coreschema
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from enum import Enum
class MyChoices(Enum):
ONE = 'ONE'
TWO = 'TWO'
class KeyFilter(BaseFilterBackend):
key_param = 'key'
default_key = 'psc'
key_title = _('Key')
key_description = _('Specify the aggregation key.')
def filter_queryset(self, request, queryset, view):
key = request.query_params.pop(self.key_param, [self.default_key])[0]
return ConsumptionAggregate(queryset).aggregate(key)
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
return [
coreapi.Field(
name=self.key_param,
required=False,
location='query',
schema=coreschema.Enum(
MyChoices,
title=force_text(self.key_title),
description=force_text(self.key_description)
)
)
]
But that is not being displayed as a dropdown.
How can we have choices being rendered as dropdowns?
Try:
schema=coreschema.Enum(
('ONE', 'TWO'),
title=force_text(self.key_title),
description=force_text(self.key_description)
)
Related
I use drf_yasg swagger for my Django API.
I would like to know how to easily disable the schema and model.
screenshot
here is my code:
from .models import Articles
from .serializers import ArticlesSerializer
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from rest_framework.authentication import SessionAuthentication,TokenAuthentication, BasicAuthentication
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.parsers import JSONParser
from django.utils.decorators import method_decorator
from django.contrib.auth import authenticate, login, logout
from rest_framework.decorators import api_view
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
#swagger_auto_schema(methods=['get'], operation_description="description", manual_parameters=[
openapi.Parameter('category', openapi.IN_QUERY, "category1, category2, category3", type=openapi.TYPE_STRING),
openapi.Parameter('name', openapi.IN_QUERY, "full name", type=openapi.TYPE_STRING),
], responses={
200: openapi.Response('Response', ArticlesSerializer),
}, tags=['Articles'])
# desactivate POST methode on swagger
#swagger_auto_schema(method='POST', auto_schema=None)
#api_view(['GET','POST'])
def articles(request):
"""
List all articles.
"""
if request.user.is_authenticated:
if request.method == 'GET':
articles = Articles.objects.all()
serializer = ArticlesSerializer(Articles, many=True)
return JsonResponse(serializer.data, safe=False)
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = ArticlesSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
return JsonResponse({"status":"403", "message":"User not authenticated"})
If i add this
class UserList(APIView):
swagger_schema = None
i got error:
AssertionError: `method` or `methods` can only be specified on #action or #api_view views
Code Edited:
the articles function is pretty simple nothing related to the API, only Python code.
Here the views Class is also pretty simple.
Class Views:
from django.db import models
class Articles(models.Model):
STATUS = (
(1, 'PENDING'),
(2, 'COMPLETED'),
(3, 'DECLINED'),
(0, 'BANNED'),
)
name = models.CharField(max_length=100)
...
status = models.PositiveSmallIntegerField(
choices = STATUS,
default = 1,
)
I only have half of the answer, to disable Models I added this to my setting.py
SWAGGER_SETTINGS = {
'DEFAULT_FIELD_INSPECTORS': [
'drf_yasg.inspectors.CamelCaseJSONFilter',
'drf_yasg.inspectors.InlineSerializerInspector',
'drf_yasg.inspectors.RelatedFieldInspector',
'drf_yasg.inspectors.ChoiceFieldInspector',
'drf_yasg.inspectors.FileFieldInspector',
'drf_yasg.inspectors.DictFieldInspector',
'drf_yasg.inspectors.SimpleFieldInspector',
'drf_yasg.inspectors.StringDefaultFieldInspector',
],
}
Credit goes to this guys, and here is the documentation for more details.
I finally figured it out.
I just had to overwrite the responses parameter with either plain text, markdown or html.
#swagger_auto_schema(methods=['get'], operation_description="Get article information ", manual_parameters=[
openapi.Parameter('id', openapi.IN_QUERY, "Article Id", type=openapi.TYPE_STRING),
], responses={ 200: '**Example:** \
<div class="highlight-code"><pre>{ <br>\
"category": "string", <br>\
"image": "string",<br>\
"link": "string",<br>\
"birth_date": "string",<br>\
}</pre></div>'},
tags=['Get Articles'])
To have the same css effect (background black) you can add this custom CSS in the file ...\site-packages\drf_yasg\static\drf-yasg\style.css
.swagger-ui .markdown .highlight-code pre{
color: #fff;
font-weight: 400;
white-space: pre-wrap;
background: #41444e;
padding: 15px;
}
I forked customer app , to add a tab in http://oscar/accounts/...(example products)
to edit/show catalogue Views (Dashboard>Catalogue)
Error that I get to use that view is
django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
I used views with the same method for payment checkout, but this one runs into errors.
# yourappsfolder.customer.apps.py
import oscar.apps.customer.apps as apps
from oscar.apps.dashboard.catalogue import apps as dapps
from django.views import generic
from django.conf.urls import url
from oscar.core.loading import get_class
from .views import ProductListView
class CustomerConfig(apps.CustomerConfig):
name = 'yourappsfolder.customer'
def ready(self):
super().ready()
self.extra_view =ProductListView
def get_urls(self):
urls = super().get_urls()
urls += [
url(r'products/',self.extra_view.as_view(),name='Products'),
]
return self.post_process_urls(urls)
This is the view I copied from oscar.apps.dashboard.catalogue
# yourappsfolder.customer.views
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
from django.views import generic
from oscar.apps.dashboard.catalogue.views import ProductListView as UserProductListView
class ProductListView(UserProductListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['form'] = self.form
ctx['productclass_form'] = self.productclass_form_class()
return ctx
def get_description(self, form):
if form.is_valid() and any(form.cleaned_data.values()):
return _('Product search results')
return _('Products')
def get_table(self, **kwargs):
if 'recently_edited' in self.request.GET:
kwargs.update(dict(orderable=False))
table = super().get_table(**kwargs)
table.caption = self.get_description(self.form)
return table
def get_table_pagination(self, table):
return dict(per_page=20)
def filter_queryset(self, queryset):
"""
Apply any filters to restrict the products that appear on the list
"""
return filter_products(queryset, self.request.user)
def get_queryset(self):
"""
Build the queryset for this list
"""
queryset = Product.objects.browsable_dashboard().base_queryset()
queryset = self.filter_queryset(queryset)
queryset = self.apply_search(queryset)
return queryset
def apply_search(self, queryset):
"""
Search through the filtered queryset.
We must make sure that we don't return search results that the user is not allowed
to see (see filter_queryset).
"""
self.form = self.form_class(self.request.GET)
if not self.form.is_valid():
return queryset
data = self.form.cleaned_data
if data.get('upc'):
# Filter the queryset by upc
# For usability reasons, we first look at exact matches and only return
# them if there are any. Otherwise we return all results
# that contain the UPC.
# Look up all matches (child products, products not allowed to access) ...
matches_upc = Product.objects.filter(upc__iexact=data['upc'])
# ... and use that to pick all standalone or parent products that the user is
# allowed to access.
qs_match = queryset.filter(
Q(id__in=matches_upc.values('id')) | Q(id__in=matches_upc.values('parent_id')))
if qs_match.exists():
# If there's a direct UPC match, return just that.
queryset = qs_match
else:
# No direct UPC match. Let's try the same with an icontains search.
matches_upc = Product.objects.filter(upc__icontains=data['upc'])
queryset = queryset.filter(
Q(id__in=matches_upc.values('id')) | Q(id__in=matches_upc.values('parent_id')))
if data.get('title'):
queryset = queryset.filter(title__icontains=data['title'])
return queryset
You have a circular import - move the import of the list view into the ready() method of the app config:
class CustomerConfig(apps.CustomerConfig):
name = 'yourappsfolder.customer'
def ready(self):
super().ready()
from .views import ProductListView
self.extra_view =ProductListView
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
I have a view function which renders json. I am able to specify which columns I want in my json but I don't know how to change the name of the key fields. Like the field "pk" should be "id".
I am using this autocomplete control (http://loopj.com/2009/04/25/jquery-plugin-tokenizing-autocomplete-text-entry/) and it requires the json to have certain fields.
from django.http import HttpResponse
from django.shortcuts import render_to_response
from iCookItThisWay.recipes import models
from django.core import serializers
from django.utils import simplejson
def index(request, template_name):
meal_types = []
q = ''
if 'q' in request.GET and request.GET['q']:
q = request.GET['q']
if len(q) > 0:
meal_types = models.MealType.objects.filter(name__istartswith=q)
json_serializer = serializers.get_serializer("json")()
sdata = json_serializer.serialize(meal_types, ensure_ascii=False, fields = ('id', 'name'))
return HttpResponse(simplejson.dumps(sdata), mimetype='application/json')
Could you also please point me to some documentation. I feel that I am crap at finding documentation.
Instead of using the serializer, you can build a dict manually and convert it to json via .dumps()
meal_types = models.MealType.objects.filter(name__istartswith=q)
results = []
for meal_type in meal_types:
results.append(
{'id': meal_type.id,
'name': meal_type.name})
return HttpResponse(simplejson.dumps(results), mimetype='application/json')
You could also build the results with a list comprehension, since there are only a couple
of fields:
results = [{'id': mt.id, 'name': mt.name} for mt in meal_types]
I did a recaptcha integration using the following django snippet
settings.py
RECAPTCHA_PUBLIC_KEY = '<your public key>'
RECAPTCHA_PRIVATE_KEY = '<your private key>'
#widgets.py
from django import forms
from django.utils.safestring import mark_safe
from django.conf import settings
from recaptcha import captcha
class ReCaptcha(forms.widgets.Widget):
recaptcha_challenge_name = 'recaptcha_challenge_field'
recaptcha_response_name = 'recaptcha_response_field'
def render(self, name, value, attrs=None):
return mark_safe(u'%s' % captcha.displayhtml(settings.RECAPTCHA_PUBLIC_KEY))
def value_from_datadict(self, data, files, name):
return [data.get(self.recaptcha_challenge_name, None),
data.get(self.recaptcha_response_name, None)]
#fields.py
from django.conf import settings
from django import forms
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext_lazy as _
from marcofucci_utils.widgets import ReCaptcha
from recaptcha import captcha
class ReCaptchaField(forms.CharField):
default_error_messages = {
'captcha_invalid': _(u'Invalid captcha')
}
def __init__(self, *args, **kwargs):
self.widget = ReCaptcha
self.required = True
super(ReCaptchaField, self).__init__(*args, **kwargs)
def clean(self, values):
super(ReCaptchaField, self).clean(values[1])
recaptcha_challenge_value = smart_unicode(values[0])
recaptcha_response_value = smart_unicode(values[1])
check_captcha = captcha.submit(recaptcha_challenge_value,
recaptcha_response_value, settings.RECAPTCHA_PRIVATE_KEY, {})
if not check_captcha.is_valid:
raise forms.util.ValidationError(self.error_messages['captcha_invalid'])
return values[0]
#forms.py
class RegistrationForm(forms.Form):
...
recaptcha = marcofucci_fields.ReCaptchaField()
...
But I have the forms defined in the django forms wizard and it calls the clean method on the field twice, even if the captcha is included in the last form.
As in the following:
from registration.forms import RegistrationWizard,RegistrationForm,ProfileForm
url(r'^register/$',
RegistrationWizard([RegistrationForm,ProfileForm]),
name='register_wizard'),
How do I circumvent this situation. What is the need to call the clean on the last form twice?
If a form is bound, it will allways call clean when renderering (even if you don't call form.is_valid).
You might want to consider using this instead:
django snippet 1644