Context
I have 2 models: Customer & DeviceGroup.
Currently I have an API endpoint /api/v1/device-groups/?customer_uuid=<customer_uuid> which returns the DeviceGroups that are related to the given Customer like this:
[
{
"group_uuid": "c61361ac-0826-41bb-825a-8aa8e014ae0c",
"device_group_name": "Default",
"color": "0a2f45",
"is_default": true
},
{
"group_uuid": "1a86e8e4-b41b-4f33-aefb-ce984ef96144",
"device_group_name": "Testgroup",
"color": "123456",
"is_default": false
}
]
Goal
I want the array of DeviceGroups be part of an object like this:
"device_groups":
[
{
"group_uuid": "c61361ac-0826-41bb-825a-8aa8e014ae0c",
"device_group_name": "Default",
"color": "0a2f45",
"is_default": true
},
{
"group_uuid": "1a86e8e4-b41b-4f33-aefb-ce984ef96144",
"device_group_name": "Testgroup",
"color": "123456",
"is_default": false
}
]
Models
# models.py
class Customer(models.Model):
customer_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
customer_name = models.CharField(max_length=128, unique=True)
class DeviceGroup(models.Model):
group_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
customer_uuid = models.ForeignKey(Customer, on_delete=models.DO_NOTHING)
device_group_name = models.CharField(max_length=20)
color = models.CharField(max_length=10)
is_default = models.BooleanField(default=False)
Serializer
# serializers.py
class DeviceGroupSerializer(serializers.ModelSerializer):
class Meta:
model = DeviceGroup
fields = ('group_uuid', 'device_group_name', 'color', 'is_default')
View
# views.py
class DeviceGroupCustomerViewSet(viewsets.ModelViewSet):
serializer_class = DeviceGroupSerializer
def get_queryset(self):
return DeviceGroup.objects.filter(customer_uuid=self.request.GET['customer_uuid'])
I tried creating a new serializer but it did not solve my problem:
class TestSerializer(serializers.ModelSerializer):
device_groups = DeviceGroupSerializer(many=True, read_only=True)
class Meta:
model = DeviceGroup
fields = ('device_groups', 'group_uuid', 'device_group_name', 'color', 'is_default')
What do I need to change in order to get my desired output?
you can update your views like
def list(self, request):
queryset = DeviceGroup.objects.filter(customer_uuid=self.request.GET['customer_uuid'])
serializer = UserSerializer(queryset, many=True)
return Response({'device_groups': serializer.data})
this will get the desired output..
Just modify your new serializer named TestSerializer in the following way.
class TestSerializer(serializers.Serializer):
device_groups = serializers.SerializerMethodField(read_only=True)
def get_device_groups(self, model):
return DeviceGroupSerializer(model).data
The response will be a paginated response. If you want to disable it just mention pagination_class as None in your ModelViewset class.
To achieve this fairly easily without loosing the pagination, I would do this:
from rest_framework.pagination import PageNumberPagination
class DeviceGroupPagination(PageNumberPagination):
def get_paginated_response(self, data):
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('device_groups', data)
]))
Then in the views
class DeviceGroupCustomerViewSet(viewsets.ModelViewSet):
serializer_class = DeviceGroupSerializer
pagination_class = DeviceGroupPagination
...
So now, in place of results, you will have device_groups
Related
I'm using DRF for a simple API, and I was wondering if there's a way to achieve this behavior:
I've got two models similar to the following:
class Table(models.Model):
name = models.CharField(max_length=100)
...
class Column(models.Model):
original_name = models.CharField(max_length=100)
name = models.CharField(max_length=100, blank=True, null=True)
...
table = models.ForeignKey(Table, on_delete=models.CASCADE, related_name="columns")
And their serializers as follows:
class ColumnSerializer(serializers.HyperlinkedModelSerializer):
table = serializers.HyperlinkedRelatedField(
read_only=True, view_name="table-detail"
)
class Meta:
model = Column
fields = ["url", "name", "table"]
class TableSerializer(serializers.HyperlinkedModelSerializer):
dataset = serializers.HyperlinkedRelatedField(
read_only=True, view_name="dataset-detail"
)
tags = serializers.SlugRelatedField(
many=True, slug_field="name", queryset=Tag.objects.all()
)
columns = ColumnSerializer(many=True, read_only=True)
class Meta:
model = Table
fields = [
"url",
"name",
...
"columns",
]
This returns me an output similar to
{
...
"results": [
{
"url": "http://0.0.0.0:8001/api/tables/1/",
"name": "some-name",
"columns": [
{
"url": "http://0.0.0.0:8001/api/columns/1/",
"name": "id",
"table": "http://0.0.0.0:8001/api/tables/1/"
},
...
}
which is totally fine. But what I'd really want to do is, if a Column has name=None, it's filtered out from every API ViewSet. I've managed to do it on the ColumnViewSet by doing queryset = queryset.filter(name__isnull=False), but I can't do it for the TableViewSet or others that might show a Column list.
I've tried tinkering with the ColumnSerializer, but the best I could get from it was to show nulls on the Column list.
I wonder if there's a way of hiding those.
EDIT 1: Adding my ViewSets
class TableViewSet(viewsets.ModelViewSet):
serializer_class = TableSerializer
def get_queryset(self):
queryset = Table.objects.all().order_by("name")
# some query_params filtering
return queryset
class ColumnViewSet(viewsets.ModelViewSet):
serializer_class = ColumnSerializer
def get_queryset(self):
queryset = Column.objects.all().order_by("id")
queryset = queryset.filter(name__isnull=False)
# some query_params filtering
return queryset
You can work with a Prefetch object [Django-doc] to filter the related object collection, so:
from django.db.models import Prefetch
class TableViewSet(viewsets.ModelViewSet):
serializer_class = TableSerializer
def get_queryset(self):
queryset = Table.objects.prefetch_related(
Prefetch('columns', Column.objects.filter(name__isnull=False))
).order_by('name')
# some query_params filtering
return queryset
Hello I have the following structure:
class Category(models.Model):
model.py
"""Class to represent the category of an Item. Like plants, bikes..."""
name = models.TextField()
description = models.TextField(null=True)
color = models.TextField(null=True)
# This will help to anidate categories
parent_category = models.ForeignKey(
'self',
on_delete=models.SET_NULL,
null=True,
)
Then I serialize it:
serializers.py:
class CategorySerializer(serializers.ModelSerializer):
"""Serializer for Category."""
class Meta: # pylint: disable=too-few-public-methods
"""Class to represent metadata of the object."""
model = Category
fields = ['id', 'name', 'description', 'color', 'parent_category']
And I Create my endpint
views.py:
class CategoryViewset(viewsets.ModelViewSet): # pylint: disable=too-many-ancestors
"""API Endpoint to return the list of categories"""
queryset = Category.objects.all()
serializer_class = CategorySerializer
pagination_class = None
Well this seems to work as expected to make a post request, for example sending this:
{
"name": "Plants",
"description": null,
"color": "#ef240d",
"parent_category": 1
}
But when I make a request of this I want to see the parent category and not have to do two requests. So I found from other questions that I could use an external library:
serializer.py
from rest_framework_recursive.fields import RecursiveField
class CategorySerializer(serializers.ModelSerializer):
"""Serializer for Category."""
parent_category = RecursiveField(many=False)
class Meta: # pylint: disable=too-few-public-methods
"""Class to represent metadata of the object."""
model = Category
fields = ['id', 'name', 'description', 'color', 'parent_category', 'category_name']
And then It seems to work:
{
"id": 2,
"name": "Flowers",
"description": null,
"color": "#ef240a",
"parent_category": {
"id": 1,
"name": "Plants",
"description": "something",
"color": "#26def2",
"parent_category": null,
},
},
But when I try to post now it will not work as It seems to expect an object instead of just the ID which is what I would have available in my frontend:
{
"parent_category": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
}
}
Is it possible to mix somehow this two approaches in my ModelSerializer?
You can customise your ModelViewSet to use two serializers instead of one. For example
class CategoryViewset(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
pagination_class = None
def create(self, request):
new_category = CategoryCreateSerializer(data=request.data)
if new_category.is_valid:
return Response(CategoryRetrieveSerializer(new_category).data)
I am trying to get a Json of elements with their related elements
I had two tables, Service and Room. One service have many rooms. I would like to get the service where have room_id = x.
Models
class Service(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = True
db_table = 'Service'
class Room(models.Model):
name = models.CharField(max_length=255, blank=True, null=True)
service = models.ForeignKey(Service, models.DO_NOTHING, blank=True,
null=True)
class Meta:
managed = True
db_table = 'Room'
Serializer
class ServiceSerializer(serializers.ModelSerializer):
room_set = RoomSerializer(many=True, read_only=True)
class Meta:
model = Service
fields = ('name','room_set')
class RoomSerializer(serializers.ModelSerializer):
class Meta:
model = Room
fields = '__all__'
View
queryset = Service.objects.filter(room__id=1)
serializer = ServiceSerializer(queryset, many=True)
return JsonResponse(serializer.data, safe=False)
I expect a Json like this:
{
"name": "Hotel1",
"room_set": [
{
"id": 1,
"name": "Room1"
},
But I get this:
{
"name": "Hotel1",
"room_set": [
{
"id": 1,
"name": "Room1",
},
{
"id": 2,
"name": "Room2",
},
{
"id": 3,
"name": "Room3",
}
}
Is it possible to get a json like the one I'm expecting?
You can patch the set by adding a custom Prefetch object [Django-doc] with a filtered queryset, like:
from django.db.models import Prefetch
queryset = Service.objects.filter(
room__id=1
).prefetch_related(
Prefetch('room_set', queryset=Room.objects.filter(id=1), to_attr='room_set1')
)
serializer = ServiceSerializer(queryset, many=True)
return JsonResponse(serializer.data, safe=False)
and let the Serializer parse the new related manager:
class ServiceSerializer(serializers.ModelSerializer):
room_set = RoomSerializer(many=True, read_only=True, source='room_set1')
class Meta:
model = Service
fields = ('name','room_set1')
class RoomSerializer(serializers.ModelSerializer):
class Meta:
model = Room
fields = '__all__'
You can pass the room id via the serializer context and filter accordingly inside a SerializerMethodField()
class ServiceSerializer(serializers.ModelSerializer):
rooms = serializers.SerializerMethodField()
class Meta:
model = Service
fields = ('name','rooms')
get_rooms(self,service):
room_id = self.get_context('room')
if room_id:
queryset = service.rooms_set.filter(id=room_id)
return RoomSerializer(queryset,many=True).data
return RoomSerializer(service.rooms_set.all(),many=True).data
serializer = ServiceSerializer(queryset, many=True,context={'room':1})
return JsonResponse(serializer.data, safe=False)
This's how to do it via the serializer and it's highly customizable , Willem Van Onsem's answer is brief enough , but it also requires two queries the same as of mine.
I have two model, Person and Appointment. A Person have many Appointment.
models.py
class Person(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __str__(self):
return self.name
class Appointment(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='appointments')
date = models.DateField(unique=True)
class Meta:
ordering = 'date',
def __str__(self):
return str(self.date)
and my serializers.py file is:
from rest_framework import serializers
from appointment.models import Person, Appointment
class AppointmentSerializer(serializers.ModelSerializer):
class Meta:
model = Appointment
fields = ('date',)
class PersonSerializer(serializers.ModelSerializer):
appointments = AppointmentSerializer(many=True)
class Meta:
model = Person
fields = ('name', 'email', 'appointments',)
def create(self, validated_data):
appointments_data = validated_data.pop('appointments')
person = Person.objects.create(**validated_data)
for appointment_data in appointments_data:
Appointment.objects.create(person=person, **appointment_data)
return person
and finally my viewsets is:
class PersonViewSet(viewsets.ModelViewSet):
serializer_class = PersonSerializer
queryset = Person.objects.all()
filter_backends = [DjangoFilterBackend]
filter_fields = ['name', 'appointments']
This return persons_list as my expectation:
[
{
"name": "Arif Hasan",
"email": "arif#gmail.com",
"appointments": [
{
"date": "2016-10-10"
},
{
"date": "2016-10-17"
},
{
"date": "2016-11-07"
}
]
},
{
"name": "Atanu Shome",
"email": "atanu#gmail.com",
"appointments": [
{
"date": "2016-11-13"
}
]
}
]
But i can't create new appointment here.
Can't filter person by date range.
You should use tuples for specifying filter_backends, filter_fields.
Also you want to filter by date field so you have to specify appointments__date in your filter_fields insted of just appointments.
You should have your viewset defined as follows:
class PersonViewSet(viewsets.ModelViewSet):
serializer_class = PersonSerializer
queryset = Person.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_fields = ('name', 'appointments__date',)
Check : Django filtering
I'm trying to perform a create in Django Rest Framework using a writable nested serializer.
With the code bellow I can create a ScriptQuestion but I can't add a RecordedInterview into it. Django says OrderedDict is None.
What am I doing wrong?
Thanks in advance
#models.py
class ScriptQuestion(models.Model):
interview = models.ManyToManyField(RecordedInterview)
...
class RecordedInterview(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
...
The serializers
#serializers.py
class InterviewTitleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = RecordedInterview
fields = ('id', 'title')
extra_kwargs = {
'title': { 'read_only': True }
}
class QuestionDetailSerializer(serializers.HyperlinkedModelSerializer):
interview = InterviewTitleSerializer(many=True)
class Meta:
model = ScriptQuestion
fields = ('id', 'title', 'prep_time', 'answer_time', 'interview')
depth = 1
def create(self, validated_data):
interview_data = validated_data.pop('interview')
question = ScriptQuestion.objects.create(**validated_data)
for item in interview_data:
item = interview_data['id']
question.interview.add(item)
return question
Here is my view
#views.py
class CreateQuestion(generics.CreateAPIView):
queryset = ScriptQuestion.objects.all()
serializer_class = QuestionDetailSerializer
And the json
{
"title": "Question Test Json",
"prep_time": "1",
"answer_time":"1",
"interview": [
{
"id": "a450aeb0-8446-47b0-95bd-5accbb8b4afa"
}
]
}
If I do manually, I can add the RecordedInterview into the ScriptQuestion:
#serializers.py
def create(self, validated_data):
interview_data = validated_data.pop('interview')
question = ScriptQuestion.objects.create(**validated_data)
item = 'a450aeb0-8446-47b0-95bd-5accbb8b4afa'
question.interview.add(item)
return question
I cannot add a comment because of low reputation. SO adding as an answer. I think you should use 'serializers.ModelSerializer' instead of 'serializers.HyperLinkedModelSerializer'
Oh, I could make it.
For someone in the future, just add "id = serializers.CharField()" in the Serializer
class InterviewTitleSerializer(serializers.ModelSerializer):
id = serializers.CharField()
class Meta:
model = RecordedInterview
fields = ('id', 'title')
extra_kwargs = {'title': { 'read_only': True }}