Schema from drf-yasg for nested serializers - django

I built nested serializer where ModelSerializer include another serializer as field. Everything works well but in swagger docs in example body parameters I don't see openning_time field. What can I change to obtain openning_time field in docs? I tried with swagger_auto_schema but got error:
drf_yasg.errors.SwaggerGenerationError: specify the body parameter as a Schema or Serializer in request_body
serializers.py
class WarehouseSerializer(serializers.ModelSerializer):
openning_time = OpenningTimeSerializer(many=True, read_only=True)
class Meta:
model = Warehouse
fields = ['pk', 'name', 'action_available', 'openning_time', 'workers']
views.py
class WarehouseApi(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
queryset = Warehouse.objects.all()
serializer_class = WarehouseSerializer
permission_classes = [IsAuthenticated, ]
warehouse_param_config = openapi.Parameter(
'openning_time', in_=openapi.IN_BODY, description='Description', type=openapi.TYPE_OBJECT)
#swagger_auto_schema(manual_parameters=[warehouse_param_config])
def update(self, request, *args, **kwargs):
return super().update(request, *args, **kwargs)
There is screen from swagger docs and i wanted add information about openning_time which is represented as list of dictionaries as below:
[
{
"weekday": 4,
"from_hour": "12:22:00",
"to_hour": "13:13:00"
},
{
"weekday": 5,
"from_hour": "16:00:00",
"to_hour": "23:00:00"
}
]

use decorator like
#swagger_auto_schema(request_body=WarehouseSerializer)
refer documentation
Custom schema generation

Related

Create Multiple Instances at Once Django Rest Framework

There seems to be a lot of documentation out there on this but none of it seems to work for me. I am trying to build an API View that creates one or many objects at the same time.
Something like passing in the following:
[
{
"response": "I have no favourites",
"user": 1,
"update": "64c5fe6f-cb65-493d-8ef4-126db0195c33",
"question": "297b46b4-714b-4434-b4e6-668ff926b38e"
},
{
"response": "Good",
"user": 1,
"update": "64c5fe6f-cb65-493d-8ef4-126db0195c33",
"question": "13916052-690e-4638-bb7c-908c38dcd75e"
}
]
My current Viewset
class FeedbackViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
and Serializer:
class ContributionSerializer(serializers.ModelSerializer):
class Meta:
model = Contribution
fields = '__all__'
I have tried setting FeedbackSerializer(many=True) but this then tells me its not callable. Further, I have tried a ListCreateAPIView but this tells me it expects a dictionary but not a list.
you have the correct idea with many=True. You just need to put it in the correct location... so in the ViewSet:
class FeedbackViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
def get_serializer(self, *args, **kwargs):
# add many=True if the data is of type list
if isinstance(kwargs.get("data", {}), list):
kwargs["many"] = True
return super(FeedbackViewSet, self).get_serializer(*args, **kwargs)
There are other ways to achieve the same behaviour, but I think this is pretty clean!
Override the create(...) method
from rest_framework.response import Response
from rest_framework import status
class FeedbackViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=True) # not that `many=True` id mandatory since you are dealing with a list of of inputs
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data,
status=status.HTTP_201_CREATED,
headers=headers
)

convert django api to django-rest-framework api with 200 response code

Here is my code which sends 200 api response after doing some stuffs.
It is working but, I wants to convert this as django-rest-framework way with DRF template.
def refresh_dashboard(request):
profile = request.session["profile"]
refresh_profile_events(profile)
# refresh_profile_events.apply_async(args=[profile])
return JsonResponse({"code": 200, "msg": "success"})
Below code i am using but it is giving below error.
Please have a look how we can achive this
class RefreshAPIView(RetrieveAPIView):
permission_classes = (IsAuthenticated,)
def get_object(self):
user = self.request.user
return type('DashboardData', (), {"code": 200})()
Error:
assert self.serializer_class is not None, (
AssertionError: 'RefreshAPIView' should either include a `serializer_class` attribute, or override the `get_serializer_class()` method.
You are missing the serializer concept there.
class RefreshAPIView(RetrieveAPIView):
queryset = YourClass.objects.all()
serializer_class = RefreshAPIViewSerializer
permission_classes = (IsAuthenticated,)
A seializer looks like this
class MoviesReadSerializer(serializers.ModelSerializer):
class Meta:
model = Movies
fields = ['id', 'title', 'description', 'categories', 'tags']
I like to suggest you to read about ModelSerializer in Django-rest documentation, check the URL below.
https://www.django-rest-framework.org/api-guide/serializers/#modelserializer

Django - ApiDetailView

I am following this solution on how to get specific fields from a django model:
Select specific fields in Django get_object_or_404
from django.core import serializers as djangoserializer # module 'rest_framework.serializers' has no attribute 'serialize'
class ProjectDetailApiView(APIView):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, slug=None, format=None):
project_instance = get_object_or_404(Project.objects.only('project_title', 'project_post'), slug=slug)
data = djangoserializer.serialize('json', [ project_instance, ], fields=('project_title','project_post'))
user = self.request.user
updated = False
viewed = False
if not user in project_instance.project_views.all():
viewed = True
project_instance.project_views.add(user)
updated = True
data = {
"project": data,
"updated":updated,
"viewed":viewed
}
return Response(data)
Output:
{
"project": "[{\"model\": \"webdata.project\", \"pk\": 4, \"fields\": {\"project_title\": \"Project 4\", \"project_post\": \"Blabla\"}}]",
"updated": true,
"viewed": false
}
Desired Output:
{
"project_title": "Project 4",
"project_post": "Blabla",
"updated": true,
"viewed": false
}
Thank you
Use DRF's Serializer instead of Django's built-in serializer.
# serializers.py
from rest_framework import serializers
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ('project_title', 'project_post', 'updated', 'viewed')
# views.py
class ProjectDetailApiView(APIView):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, slug=None, format=None):
project_instance = get_object_or_404(Project, slug=slug)
serializer = ProjectSerializer(project_instance)
return Response(serializer.data)
The serialization of regular dictionaries is very good in python.
So instead of configuring the serializer - why not just create a python dictionary with the desired data? (That's how I do it for simple things that I need only in one place.)
data = {
"project_title": project_instance.project_title,
"project_post": project_instance.project_post,
"updated":updated,
"viewed":viewed
}
return JSONResponse(data)
You haven't posted the Project model, I'm just supposing from the serializer config that the fields are named project_title and project_post.
This will return a response with status 200, mimetype application/json and the data dict as valid JSON.

Altering response in Django Rest

I have a ListAPIView that returns the json response below:
[
{'name': 'Andrew'},
{'name': 'Daniel'},
]
I want to alter it so that the response would look like:
{
"Users": {
[
{'name': 'Andrew'},
{'name': 'Daniel'},
]
}
}
How could I do that?
EDIT: Below are is my serializer and the View
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('name',)
class UserReadView(ListAPIView):
lookup_field = 'id'
serializer_class = UserSerializer
You can implement list method inside UserReadView and update response body inside it:
class UserReadView(ListAPIView):
lookup_field = 'id'
serializer_class = UserSerializer
def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
return Response({'Users':{response.data}})

Django Rest Framework: Dynamically return subset of fields

Problem
As recommended in the blogpost Best Practices for Designing a Pragmatic RESTful API, I would like to add a fields query parameter to a Django Rest Framework based API which enables the user to select only a subset of fields per resource.
Example
Serializer:
class IdentitySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Identity
fields = ('id', 'url', 'type', 'data')
A regular query would return all fields.
GET /identities/
[
{
"id": 1,
"url": "http://localhost:8000/api/identities/1/",
"type": 5,
"data": "John Doe"
},
...
]
A query with the fields parameter should only return a subset of the fields:
GET /identities/?fields=id,data
[
{
"id": 1,
"data": "John Doe"
},
...
]
A query with invalid fields should either ignore the invalid fields or throw a client error.
Goal
Is this possible out of the box somehow? If not, what's the simplest way to implement this? Is there a 3rd party package around that does this already?
You can override the serializer __init__ method and set the fields attribute dynamically, based on the query params. You can access the request object throughout the context, passed to the serializer.
Here is a copy&paste from Django Rest Framework documentation example on the matter:
from rest_framework import serializers
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
fields = self.context['request'].query_params.get('fields')
if fields:
fields = fields.split(',')
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
class UserSerializer(DynamicFieldsModelSerializer, serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email')
This functionality is available from a 3rd-party package.
pip install djangorestframework-queryfields
Declare your serializer like this:
from rest_framework.serializers import ModelSerializer
from drf_queryfields import QueryFieldsMixin
class MyModelSerializer(QueryFieldsMixin, ModelSerializer):
...
Then the fields can now be specified (client-side) by using query arguments:
GET /identities/?fields=id,data
Exclusion filtering is also possible, e.g. to return every field except id:
GET /identities/?fields!=id
disclaimer: I'm the author/maintainer.
serializers.py
class DynamicFieldsSerializerMixin(object):
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsSerializerMixin, self).__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
class UserSerializer(DynamicFieldsSerializerMixin, serializers.HyperlinkedModelSerializer):
password = serializers.CharField(
style={'input_type': 'password'}, write_only=True
)
class Meta:
model = User
fields = ('id', 'username', 'password', 'email', 'first_name', 'last_name')
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
email=validated_data['email'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name']
)
user.set_password(validated_data['password'])
user.save()
return user
views.py
class DynamicFieldsViewMixin(object):
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
fields = None
if self.request.method == 'GET':
query_fields = self.request.QUERY_PARAMS.get("fields", None)
if query_fields:
fields = tuple(query_fields.split(','))
kwargs['context'] = self.get_serializer_context()
kwargs['fields'] = fields
return serializer_class(*args, **kwargs)
class UserList(DynamicFieldsViewMixin, ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
Configure a new pagination serializer class
from rest_framework import pagination, serializers
class DynamicFieldsPaginationSerializer(pagination.BasePaginationSerializer):
"""
A dynamic fields implementation of a pagination serializer.
"""
count = serializers.Field(source='paginator.count')
next = pagination.NextPageField(source='*')
previous = pagination.PreviousPageField(source='*')
def __init__(self, *args, **kwargs):
"""
Override init to add in the object serializer field on-the-fly.
"""
fields = kwargs.pop('fields', None)
super(pagination.BasePaginationSerializer, self).__init__(*args, **kwargs)
results_field = self.results_field
object_serializer = self.opts.object_serializer_class
if 'context' in kwargs:
context_kwarg = {'context': kwargs['context']}
else:
context_kwarg = {}
if fields:
context_kwarg.update({'fields': fields})
self.fields[results_field] = object_serializer(source='object_list',
many=True,
**context_kwarg)
# Set the pagination serializer setting
REST_FRAMEWORK = {
# [...]
'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'DynamicFieldsPaginationSerializer',
}
Make dynamic serializer
from rest_framework import serializers
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
See:
http://tomchristie.github.io/rest-framework-2-docs/api-guide/serializers
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
# Use it
class MyPonySerializer(DynamicFieldsModelSerializer):
# [...]
Last, use a homemage mixin for your APIViews
class DynamicFields(object):
"""A mixins that allows the query builder to display certain fields"""
def get_fields_to_display(self):
fields = self.request.GET.get('fields', None)
return fields.split(',') if fields else None
def get_serializer(self, instance=None, data=None, files=None, many=False,
partial=False, allow_add_remove=False):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
fields = self.get_fields_to_display()
return serializer_class(instance, data=data, files=files,
many=many, partial=partial,
allow_add_remove=allow_add_remove,
context=context, fields=fields)
def get_pagination_serializer(self, page):
"""
Return a serializer instance to use with paginated data.
"""
class SerializerClass(self.pagination_serializer_class):
class Meta:
object_serializer_class = self.get_serializer_class()
pagination_serializer_class = SerializerClass
context = self.get_serializer_context()
fields = self.get_fields_to_display()
return pagination_serializer_class(instance=page, context=context, fields=fields)
class MyPonyList(DynamicFields, generics.ListAPIView):
# [...]
Request
Now, when you request a resource, you can add a parameter fields to show only specified fields in url.
/?fields=field1,field2
You can find a reminder here : https://gist.github.com/Kmaschta/e28cf21fb3f0b90c597a
You could try Dynamic REST, which has support for dynamic fields (inclusion, exclusion), embedded / sideloaded objects, filtering, ordering, pagination, and more.
If you want something flexible like GraphQL, you can use django-restql. It supports nested data (both flat and iterable).
Example
from rest_framework import serializers
from django.contrib.auth.models import User
from django_restql.mixins import DynamicFieldsMixin
class UserSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'groups')
A regular request returns all fields.
GET /users
[
{
"id": 1,
"username": "yezyilomo",
"email": "yezileliilomo#hotmail.com",
"groups": [1,2]
},
...
]
A request with the query parameter on the other hand returns only a subset of
the fields:
GET /users/?query={id, username}
[
{
"id": 1,
"username": "yezyilomo"
},
...
]
With django-restql you can access nested fields of any level. E.g
GET /users/?query={id, username, date_joined{year}}
[
{
"id": 1,
"username": "yezyilomo",
"date_joined": {
"year": 2018
}
},
...
]
For iterable nested fields, E.g groups on users.
GET /users/?query={id, username, groups{id, name}}
[
{
"id": 1,
"username": "yezyilomo",
"groups": [
{
"id": 2,
"name": "Auth_User"
}
]
},
...
]
Such functionality we've provided in drf_tweaks / control-over-serialized-fields.
If you use our serializers, all you need is to pass ?fields=x,y,z parameter in the query.
For nested data, I am using Django Rest Framework with the package recommended in the docs, drf-flexfields
This allows you to restrict the fields returned on both the parent and child objects. The instructions in the readme are good, just a few things to watch out for:
The URL seems to need the / like this '/person/?expand=country&fields=id,name,country' instead of as written in the readme '/person?expand=country&fields=id,name,country'
The naming of the nested object and its related name need to be completely consistent, which isn't required otherwise.
If you have 'many' e.g. a country can have many states, you'll need to set 'many': True in the Serializer as described in the docs.
The solution suggested at the [DRF-Documentation][1] worked for me, however when I called the serializer from the View with:
class SomeView(ListAPIView):
def get(self, request, *args, **kwargs):
qry=table.objects.filter(column_value=self.kwargs['urlparameter'])
fields=['DBcol1','DBcol2','DBcol3']
serializer=SomeSerializer(qry,many=True,fields=fields)
I had to add many=True, otherwise it was not working.
[1]: https://www.django-rest-framework.org/api-guide/serializers/#example
Another alternative is to make use of GraphWrap: https://github.com/PaulGilmartin/graph_wrap
By adding /graphql to your urlpatterns, you add layer your REST API with a fully compliant GraphQL queryable API.