Django Swagger Integration - python

I saw swagger documentation of Flask and Django. In Flask I can design and document my API hand-written.(Include which fields are required, optional etc. under parameters sections).
Here's how we do in Flask
class Todo(Resource):
"Describing elephants"
#swagger.operation(
notes='some really good notes',
responseClass=ModelClass.__name__,
nickname='upload',
parameters=[
{
"name": "body",
"description": "blueprint object that needs to be added. YAML.",
"required": True,
"allowMultiple": False,
"dataType": ModelClass2.__name__,
"paramType": "body"
}
],
responseMessages=[
{
"code": 201,
"message": "Created. The URL of the created blueprint should be in the Location header"
},
{
"code": 405,
"message": "Invalid input"
}
]
)
I can chose which parameters to include, and which not. But how do I implement the same in Django? Django-Swagger Document in
not good at all. My main issue is how do I write my raw-json in Django.
In Django it automates it which does not allows me to customize my json. How do I implement the same kind of thing on Django?
Here is models.py file
class Controller(models.Model):
id = models.IntegerField(primary_key = True)
name = models.CharField(max_length = 255, unique = True)
ip = models.CharField(max_length = 255, unique = True)
installation_id = models.ForeignKey('Installation')
serializers.py
class ActionSerializer(serializers.ModelSerializer):
class Meta:
model = Controller
fields = ('installation',)
urls.py
from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns
from modules.actions import views as views
urlpatterns = patterns('',
url(r'(?P<installation>[0-9]+)', views.ApiActions.as_view()),
)
views.py
class ApiActions(APIView):
"""
Returns controllers List
"""
model = Controller
serializer_class = ActionSerializer
def get(self, request, installation,format=None):
controllers = Controller.objects.get(installation_id = installation)
serializer = ActionSerializer(controllers)
return Response(serializer.data)
My questions are
1) If I need to add a field say xyz, which is not in my models how do I add it?
2) Quiet similar to 1st, If i need to add a field which accepts values b/w 3 provided values,ie a dropdown. how do I add it?
3) How I add an optional field? (since in case of PUT request, I might only update 1 field and rest leave it blank, which means optional field).
4) Also how do I add a field that accepts the json string, as this api does?
Thanks
I can do all of these things in Flask by hardcoding my api. But in Django, it automates from my models, which does not(as I believe) gives me the access to customize my api. In Flask, I just need to write my API with hands and then integrate with the Swagger. Does this same thing exist in Django?
Like I just need to add the following json in my Flask code and it will answer all my questions.
# Swagger json:
"models": {
"TodoItemWithArgs": {
"description": "A description...",
"id": "TodoItem",
"properties": {
"arg1": { # I can add any number of arguments I want as per my requirements.
"type": "string"
},
"arg2": {
"type": "string"
},
"arg3": {
"default": "123",
"type": "string"
}
},
"required": [
"arg1",
"arg2" # arg3 is not mentioned and hence 'opional'
]
},

Django-rest-framework does have a lot of useful utility classes such as serializers.ModelSerializer which you are using. However these are optional. You can create totally custom API endpoints.
I suggest that you follow the django rest tutorial here. Part one starts with a custom view like this
from django.forms import widgets
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.Serializer):
pk = serializers.Field() # Note: `Field` is an untyped read-only field.
title = serializers.CharField(required=False,
max_length=100)
code = serializers.CharField(widget=widgets.Textarea,
max_length=100000)
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES,
default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES,
default='friendly')
def restore_object(self, attrs, instance=None):
"""
Create or update a new snippet instance, given a dictionary
of deserialized field values.
Note that if we don't define this method, then deserializing
data will simply return a dictionary of items.
"""
if instance:
# Update existing instance
instance.title = attrs.get('title', instance.title)
instance.code = attrs.get('code', instance.code)
instance.linenos = attrs.get('linenos', instance.linenos)
instance.language = attrs.get('language', instance.language)
instance.style = attrs.get('style', instance.style)
return instance
# Create new instance
return Snippet(**attrs)
Note in particular that every API field is specified manually and populated by code here. So they do not have to correspond with model fields.
Your questions
1. Custom field xyz :
As I addressed above, just create a custom serialiser and add a line
class SnippetSerializer(serializers.Serializer):
xyz = serializers.CharField(required=False, max_length=100)
...
2. For options in a list, what you're looking for is a "choice" field.
See the Django documention on choice as Swagger is just the same.
3. How do I make a field optional?
Set the kwarg required=False - note that it's set above for field xyz in my example.
4. Best way to accept a JSON string
Two ways to do this.
Just accept a text string and use a JSON parser in the restore_object code
Define a serialiser that consumes / creates the JSON code and refer to it by name as described here

Related

Return custom JSON response from ListAPIView Django 3.0 Rest Framework

I have created an API using DRF That is able to list and view particular records based on the URL pattern specified. For example:
for the request:
curl -v http://127.0.0.1:8000/get_details/120001/
I am able to get a response:
[
{
"subject": "Data Structures",
"course": "CSE"
},
{
"subject": "Thermodynamics",
"course": "Chemistry"
},
{
"subject": "Organic Chemistry",
"course": "Chemistry"
},
{
"subject": "Optics",
"course": "Physics"
}
]
Where '120001' is the user_id the database is searched against.
But the I want the response in the following format:
{'Chemistry': ['Thermodynamics', 'Organic Chemistry'], 'CSE': ['Data Structures'], 'Physics': ['Optics']}
(content wise, I am not considering indentation and other factors)
While I am able to write code for the logic of how to create and populate this dictionary, I am unable to figure out how to return this as response and from where.
I am using generics.ListAPIView as the view class.
Here is my model (models.py):
class Subject(models.Model):
user_id = models.CharField(null = False, max_length=10)
subject = models.CharField(max_length=50)
course = models.CharField(max_length=50)
def __str__(self):
return self.subject
Serializer (serializers.py):
class SubjectSerializer(serializers.ModelSerializer):
class Meta:
model = Subject
fields = ['subject', 'course']
and, views.py (for the first output in default format):
class SubjectView(generics.ListAPIView):
serializer_class = SubjectSerializer
def get_queryset(self):
username = self.kwargs['user_id']
return Subject.objects.filter(user_id = username).only('subject','course')
I have written a logic to create the dictionary to send as response (as described in my desired output) by extracting values using Subject.objects.values(....) and then looping through the results to create my dictionary but I just don't get where (that is, which function) to write it in and return from.
Is there any function provided by the generics.ListAPIView class that can allow me to do this? And if not, then what other alternative approach can I try?
I am an absolute beginner at Django and any help will be appreciated. Also, it will be of great help if anyone can suggest me a practical guide/tutorial/playlist from where I can learn DRF through code examples to speed up my learning process.
Thank you!
You need to override to_representation method of Serializer
from docs
There are some cases where you need to provide extra context to the
serializer in addition to the object being serialized.
class SubjectSerializer(serializers.ModelSerializer):
class Meta:
model = Subject
fields = ['subject', 'course']
def to_representation(self, instance):
data = super(SubjectSerializer, self).to_representation(instance)
# manipulate data here
return data

how to easily pass from django views to django-rest-framework views

I created a nice Django app, anything worked perfectly. Unfortunely I have now to QUICKLY migrate everything to django-rest-framework and to a VUE frontend. I'm in "charge" just of the backend side, someone else will write the frontend code.
The problem is that I have really complex Django views and I can't figure out how to change them correctly and easily and, most important, how to test them without a frontend.
Here is an example of my view. I created an app that will search on google for some query and then extract the main text from each link. On the same page where you insert the query will be displayed the first 10 links, if you want to scrape more, you have to go to the next page.
This means that on the same view I have the first QueryForm. If a string is passed the scraping starts, if you want to scrape more, a query_id is passed.
def query_create(request):
form = QueryForm(request.POST)
user_p = UserProfile.objects.filter(user=request.user).first()
context={'form1':form1}
if request.method == 'POST' :
if form.is_valid():
query_obj = form.save(commit=False)
query_obj.user = user_p
query_string=query_obj.query
search_engine=query_obj.search_engine
query_obj.save()
data=multi_scraping.scrape(query_obj.search_dict, 0, 10,query_string)
queryset=[]
for index, row in data.iterrows():
link = Link(scraping_id=str(query_obj),
isBlocked=row['isBlocked'],
published_Date=row['published'],
modified_date=row['update'],
language=row['language'],
search_engine=row['search_engine'],
source=row['source'],
source_url=row['source_url'],
title=row['title'],
transcript=row['transcript'],
url=row['url']
)
link.save()
queryset.append(link)
context = {
"object_list": queryset,
"query_id": query_obj.query_id,
"p":query_obj.p,
"user":user_p
}
return render(request, "news/home.html", context)
if(request.POST.get('query_id', None) is not None):
query_id=request.POST.get('query_id', None)
query_obj=Query.objects.get(pk=query_id)
query_string=query_obj.query
query_obj.p=query_obj.p+10
query_obj.save()
data = multi_scraping.scrape(search_dict, query_obj.p, query_obj.p+10, query_string)
queryset = []
for index, row in data.iterrows():
link = Link(scraping_id=str(query_obj),
isBlocked=row['isBlocked'],
published_Date=row['published'],
modified_date=row['update'],
language=row['language'],
search_engine=row['search_engine'],
source=row['source'],
source_url=row['source_url'],
title=row['title'],
transcript=row['transcript'],
url=row['url']
)
link.save()
queryset.append(link)
context = {
"object_list": queryset,
"query_id": query_obj.query_id,
"p": query_obj.p,
"user": user_p
}
return render(request, "news/home.html", context)
Have you got any idea how to help?
anything would be appreciated!!!!!
Since, you are going to use vue.js as your front-end with a
django backend so probably you need to make rest api's for the same. Now django comeup with great features which includes rest
api imeplentation, but then you have to create your api views which
have to be serialized, so that the resultant rest api have either Json
or xml format. In your case, you have to convert the views, so
firstly need to convert your views to django rest serialized format.
So that you can handle the request which are coming from the frontend.
Let's suppose below is your model :
class User(models.Model):
username = models.CharField(max_length=200)
created_at = models.DateTimeField(default=timezone.now)
So now, in order to get all the users data, you have to serialize the user object:
class UserSerializers(serializers.ModelSerializer):
class Meta:
model = User
fields='__all__'
Your output json data look like this:
[
{
"id": 37,
"username": "kevin morgan",
"created_at": "2020-02-02T16:26:16.779900Z"
},
{
"id": 38,
"username": "john smith",
"created_at": "2020-02-02T16:43:04.242829Z"
}
]
You can read here for more info

Make Django REST API accept a list

I'm working on a functionality where I need to be able to post a list consisting of properties to the API. A property has a name, value and unit. Now I have two questions:
How exactly should my list look for the API to accept it as a correct list from the get go? Should I parse the list as Objects? Or is a plain list fine?
I am using the Django REST framework and I have made the API using this tutorial (works perfectly). But how do I make Django accept multiple objects/a list? I have read that it is as simple as adding many = True to where you instantiate the serializer, which I do here:
(for some reason the code won't format unless I put text here)
class PropertyViewSet(viewsets.ModelViewSet):
queryset = Property.objects.all()
serializer_class = PropertySerializer
So I tried doing serializer = PropertySerializer(queryset, many=True), which broke the API view. So I think I have to create a new serializer and view just for this (am I right)? But how do I make sure that my API knows which one to use at the right time?
If anyone could clarify this that would be great, thanks.
If you need to create the object, here is how I did it:
# Mixin that allows to create multiple objects from lists.
class CreateListModelMixin(object):
def get_serializer(self, *args, **kwargs):
""" if an array is passed, set serializer to many """
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
And then in the view that you would like to use it in just do:
class PropertyCreateView(CreateListModelMixin, generics.CreateAPIView):
serializer_class = PropertySerializer
permission_classes = (IsAuthenticated, )
And that's already it (make sure to put in the mixin as an argument BEFORE the view; like I did it).
Now the body of your postrequest would look like this:
{
[
{
"name": "<some_name>"
"value": "<some_value>"
"unit": "<some_unit>"
},
{
"name": "<some_name>"
"value": "<some_value>"
"unit": "<some_unit>"
},
{
"name": "<some_name>"
"value": "<some_value>"
"unit": "<some_unit>"
},
...
]
}
The cool thing about it, this way you can also just post a single object like this:
{
"name": "<some_name>"
"value": "<some_value>"
"unit": "<some_unit>"
}
I hope this helps! :)
ModelViewSet class provides a create() method which only allows you to create one object at a time. see docs
if you want to POST a list of objects and insert each object to the database, you would have to create a custom view. for e.g.
from rest_framework.decorators import api_view
from django.shortcuts import redirect
from .models import MyModel
#api_view(['POST'])
def insert_list(request):
if request.method == 'POST':
for obj in request.POST['list']: #assuming you are posting a 'list' of objects
MyModel.objects.create(name=obj.name, value=obj.value, unit=obj.unit)
return redirect('url of MyObject List View')
to use your custom APIview alongside the viewset, just add your custom APIview to a different url not used by the viewset url e.g. if your viewset uses r'^myModel/$' then use r'^myModel/insert_list/$' for the createlist custom APIview

Serialize queryset based on individual field values using Django Rest Framework

Goal
If an object has revealed=true it serializes into:
{
"id":1,
"info":"top secret info",
"revealed":true
}
If an object has revealed=false the info field is null:
{
"id":2,
"info":null,
"revealed":false
}
So for a queryset of objects:
[
{
"id":1,
"info":"top secret info 1",
"revealed":true
},
{
"id":2,
"info":null,
"revealed":false
},
{
"id":3,
"info":"top secret info 3",
"revealed":true
}
]
Is it possible to achieve this inside of a Django Rest Framework Model Serializer class?
class InfoSerializer(serializers.ModelSerializer):
class Meta:
model = Info
fields = ('id', 'info', 'revealed')
Background
The DRF docs discuss some advanced serializer usage, and this other post dives into an example. However it doesn't seem to cover this particular issue.
Ideas
A hacky solution would be to iterate over the serialized data afterwards, and remove the info field for every object that has revealed=false. However 1) it involves an extra loop and 2) would need to be implemented everywhere the data is serialized.
I suggest you make the info field appear on all records, but leave it null when revealed is false. If that's acceptable, you should be able to make it happen with a SerializerMethodField.
Alternatively, you could add a revealed_info attribute to the model class, and expose that through the serializer.
#property
def revealed_info(self):
return self.info if self.revealed else None

In django-tastypie, can choices be displayed in schema?

I am trying to figure out whether I can represent model field choices to clients consuming a tastypie API.
I have a django (1.4.1) application for which I am implementing a django-tastypie (0.9.11) API. I have a Model and ModelResource similar to the following:
class SomeModel(models.Model):
QUEUED, IN_PROCESS, COMPLETE = range(3)
STATUS_CHOICES = (
(QUEUED, 'Queued'),
(IN_PROCESS, 'In Process'),
(COMPLETE, 'Complete'),
)
name = models.CharFIeld(max_length=50)
status = models.IntegerField(choices=STATUS_CHOICES, default=QUEUED)
class SomeModelResource(ModelResource):
class Meta:
queryset = SomeModel.objects.all()
resource_name = 'some_model'
When I look at objects in the API, the name and status fields are displayed as follows:
{
...
"objects":[
{
"name": "Some name 1",
"status": 0
},
{
"name": "Some name 2",
"status": 2
}]
}
I know I can alter SomeModelResource with hydrate/dehydrate methods to display the string values for status as follows, which would have more value to clients:
{
...
"objects":[
{
"name": "Some name 1",
"status": "Queued"
},
{
"name": "Some name 2",
"status": "Complete"
}]
}
But how would the client know the available choices for the status field without knowing the inner workings of SomeModel?
Clients creating objects in the system may not provide a status as the default value of QUEUED is desirable. But clients that are editing objects need to know the available options for status to provide a valid option.
I would like for the choices to be listed in the schema description for SomeModelResource, so the client can introspect the available choices when creating/editing objects. But I am just not sure whether this is something available out of the box in tastypie, or if I should fork tastypie to introduce the capability.
Thanks for any feedback!
You can add the choices to the schema by overriding the method in your resource. If you would want to add the choices to any field (maybe to use with many resources), you could create the method as follows:
def build_schema(self):
base_schema = super(SomeModelResource, self).build_schema()
for f in self._meta.object_class._meta.fields:
if f.name in base_schema['fields'] and f.choices:
base_schema['fields'][f.name].update({
'choices': f.choices,
})
return base_schema
I haven't tested the above code but I hope you get the idea. Note that the object_class will be set only if you use the tastypie's ModelResource as it is being get from the provided queryset.
A simpler solution is to hack the choices information into your help_text blurb.
In our example we were able to do:
source = models.CharField(
help_text="the source of the document, one of: %s" % ', '.join(['%s (%s)' % (t[0], t[1]) for t in DOCUMENT_SOURCES]),
choices=DOCUMENT_SOURCES,
)
Easy peasy, automatically stays up to date, and is pretty much side-effect free.

Categories

Resources