I have a basic setup using the Django Rest Framework. I have two models and a nested serializer setup:
# models.py
from django.db import models
class Plan(models.Model):
name = models.CharField(max_length='100')
def __unicode__(self):
return u'%s' % (self.name)
class Group(models.Model):
plan = models.ForeignKey('plan')
name = models.CharField(max_length='50')
weight = models.SmallIntegerField()
def __unicode__(self):
return u'%s - %s' % (self.name, self.plan.name)
# serializer.py
from plans.models import Plan, Group
from rest_framework import serializers
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('name', 'weight')
class PlanSerializer(serializers.ModelSerializer):
group = GroupSerializer(many=True, read_only=True)
class Meta:
model = Plan
fields = ('name', 'group')
# views.py
from rest_framework import viewsets
from plans.models import Plan
from plans.serializers import PlanSerializer
class PlanViewSet(viewsets.ModelViewSet):
queryset = Plan.objects.all()
serializer_class = PlanSerializer
When I view the serializers relationships in Django's Shell it shows the relationship correctly:
PlanSerializer():
name = CharField(max_length='100')
group = GroupSerializer(many=True, read_only=True):
name = CharField(max_length='50')
weight = IntegerField()
What I end up getting back via cURL is:
[
{
name: Test Plan
}
]
What I expect to get back is:
[
{
name: Test Plan,
group: [
{
name: Test Group,
weight: 1
}
]
}
]
There is no nested data coming through. I'm at a lose for what I've not setup correctly here. Can anyone point me in the correct direction?
The problem comes from your queryset: queryset = Plan.objects.all(). None of the items in this queryset has .group attribute that's why your result is empty. By default Django creates a reverse relation of the plan ForeignKey called group_set (unless you don't rename it via related_name) (this means that every plan item in the queryset have a group_set attribute which is a queryset containing all the groups of this plan). You can use this attribute in order to get a proper serialization. This means to change:
class PlanSerializer(serializers.ModelSerializer):
group_set = GroupSerializer(many=True, read_only=True)
class Meta:
model = Plan
fields = ('name', 'group_set')
If you really want to stick with group (btw this is a very bad name for a list of groups). You can hack it with prefetch_related like so:
queryset = Plan.objects.prefetch_related('group_set', to_attr='group')
this way every plan item will have a group attribute - a queryset containing all the groups for this plan.
Never forget to give related name for the foreign key. for eg
In models
plan = modles.ForeignKey(Plan, related_name="plan")
In Serializers
plan = PlanSerializers(many = True, read_only = True)
Related
These are simplified versions of my models (the user model is just an id and name)
class Convo(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='convo_owner')
users = models.ManyToManyField(User, through='Convo_user')
class Convo_user (models.Model):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
class Comments(models.Model):
name = models.CharField(max_length=255)
content = models.TextField(max_length=1024)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
This is my view
class ConvoViewSet(viewsets.ModelViewSet):
serializer_class = serializers.ConvoSerializer
def get_queryset(self):
return None
def list(self, request):
curr_user = request.user.id
# Collecting the list of conversations
conversations = models.Conversation.object.filter(ConvoUser__user_id=request.user.id)
#Getting list of conversation id's
conv_ids = list(conversations.values_list('id', flat=True).order_by('id'))
#Getting list of relevant comments
comments = models.Comments.objects.filter(conversation_id__in=conv_ids)
return Response(self.get_serializer(conversations, many=True).data)
And my current serializer
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
# access = AccessSerializer(many=True)
# model = models.Comments
# fields = ('id', 'name', 'content', 'convo_id')
class Meta:
model = models.Convo
fields = ('id', 'owner_id')
The current response I get is of the form
[
{
"id": 1,
"owner_id": 32
}, ...
]
But I would like to add a comments field that shows all the properties of comments into the response, so basically everything in the second queryset (called comments) and I'm not sure how to go about this at all. (I retrieve the comments in the way I do because I'm trying to minimize the calls to the database). Would I need to create a new view for comments, make its own serializer and then somehow combine them into the serializer for the convo?
The way you've set up your models, you can access the comments of each Convo through Django's ORM by using convo_object.comments_set.all(), so you could set up your ConvoSerializer to access that instance's comments, like this:
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
comments_set = CommentSerializer(many=True)
class Meta:
model = models.Convo
fields = ('id', 'owner_id', 'comments_set')
and then you define your CommentSerializer like:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = models.Comments
fields = ('id', 'name', 'content')
No data appears because my serializers are using the default database, not sure why but a step forward
EDIT:
Django: Database used for prefetch_related is not the same that the parent query Provided me the correct answer, I was able to choose the database with this method because for some reason inner queries use the default DB
I have these two classes:
Class A:
data
Class B:
a = models.ForeignKey(A)
How can I get an array of A in which each A contains the B's related to them?
I need it because I must return the two tables joined in a JSON.
First set the back relation of a and then migrate the database:
Class A(models.Model):
data
Class B(models.Model):
a = models.ForeignKey(A, related_name='b_relations', null=False, blank=False, on_delete=models.CASCADE)
Now you can access the back relation. You need to call .all() on b_relations because it is lazy loaded.
A.objects.first().b_relations.all()
To get json data I suggest to use django rest framework's ModelSerializer:
class BSerializer(serializers.ModelSerializer):
class Meta:
model = B
fields = (
'pk', # primary key
'a',
)
depth = 0
class ASerializer(serializers.ModelSerializer):
b_relations = BSerializer(many=True, read_only=True))
class Meta:
model = A
fields = (
'pk', # primary key
'b_relations',
)
depth = 0
To get json data:
a_relations: QuerySet = A.objects.all()
serializer = ASerializer(a_relations, many=True)
json_data: Dict = serializer.data
In case you've never used Django Rest Framework: Django Rest Framework - Getting Started
Have fun!
I've a table Orders which contains ID, store_name, order_date, ..... I want to create an endpoint which returns me the JSON which consists of Count of Orders from all the Stores. Something as follows:
[{store_name: 'Target', count: 10}, {store_name: 'Walmart', count: 20}, {store_name: 'Costco', count: 5}]
The query I wrote is:
queryset = Stores.objects.all().values('store_name').annotate(total=Count('store_name'))
When I print queryset, I'm getting what I need as mentioned above.
But when I serialize the data, I get the following:
[{store_name: 'Target'}, {store_name: 'Walmart'}, {store_name: 'Costco'}]
Not sure what am I doing wrong.. I've included my code. (I'm not including import statements)
serializer.py
class StoresSerializer(ModelSerializer):
class Meta:
model = Stores
exclude = ['order_date',]
views.py
class StoresViewSet(ModelViewSet):
queryset = Stores.objects.all().values('store_name').annotate(total=Count('store_name'))
serializer_class = StoresSerializer
What am I missing?
Using one old topic it would go as follows
models.py (simplified version just to demonstrate the idea)
class Store(models.Model):
name = models.CharField(max_length=100)
class Order(models.Model):
store_name = models.ForeignKey(Store)
order_date = models.DateTimeField(auto_now_add=True)
serializer.py
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from .models import Store
class StoresSerializer(ModelSerializer):
orders = serializers.IntegerField()
class Meta:
model = Store
fields = ('name', 'orders')
views.py
class StoresViewSet(viewsets.ModelViewSet):
queryset = Store.objects.all().values('name').annotate(orders=Count('order'))
serializer_class = StoresSerializer
How can we write a function in a ModelViewSet that get a list of distinct record in the database?
Supposed that we have this model.
class Animal(models.Model):
this_id = models.CharField(max_length=25)
name = models.CharField(max_length=25)
species_type = models.CharField(max_length=25)
...
and serializer
class AnimalSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = (
'this_id',
'name',
'species_type',
...,
)
read_only_fields = ('id', 'created_at', 'updated_at')
and ViewSet.
class AnimalViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Animal.objects.all()
serializer_class = AnimalSerializer
I found this link useful such as decorators like #list_route()
but i can't understand it well.
I would like to get list of distinct Animal.species_type record from the ViewSet. Please help.
There are several different options in filtering. You can send the species type via your request /animals?species_type=MusMusculus and reference it when you over ride the get_queryset() method in the view.
In your view
def get_queryset(self):
species = self.request.query_params.get('species_type', None)
if species is not None:
queryset = Animals.objects.all().distinct('species_type')
species = SpeciesSerializer(data=queryset)
return queryset
Serializer
from rest_framework import serializers
class Species(serializers.Serializer):
species_type = serializers.Charfield()
alternatively, you can adopt a django filter framework http://www.django-rest-framework.org/api-guide/filtering/#djangofilterbackend
In my project I use django rest framework. To filter the results I use django_filters backend.
There is my code:
models.py
from django.db import models
class Region(models.Model):
name = models.CharField(max_length=100, blank=True, null=False)
class Town(models.Model):
region = models.ForeignKey(Region)
name = models.CharField(max_length=100, blank=True, null=False')
filters.py
import django_filters
from models import Town
class TownFilter(django_filters.FilterSet):
region = django_filters.CharFilter(name="region__name", lookup_type="contains")
town = django_filters.CharFilter(name="name", lookup_type="contains")
class Meta:
model = Town
fields = ['region', 'town']
views.py
from models import Town
from rest_framework import generics
from serializers import TownSerializer
from filters import TownFilter
class TownList(generics.ListAPIView):
queryset = Town.objects.all()
serializer_class = TownSerializer
filter_class = TownFilter
So, I can write ?region=Region_name&town=Town_name to the end of the request url, and the result will be filtered.
But I want to use only one get param in the request url, which can have region or town name as value. For example ?search=Region_name and ?search=Town_name. How can I do this?
There are a few options, but the easiest way is to just override 'get_queryset' in your API view.
Example from the docs adapted to your use case:
class TownList(generics.ListAPIView):
queryset = Town.objects.all()
serializer_class = TownSerializer
filter_class = TownFilter(generics.ListAPIView)
serializer_class = PurchaseSerializer
def get_queryset(self):
queryset = Town.objects.all()
search_param = self.request.QUERY_PARAMS.get('search', None)
if search_param is not None:
"""
set queryset here or use your TownFilter
"""
return queryset
Another way is to set your search_fields on the list api view class in combination use the SearchFilter class. The problem is that if you're filtering over multiple models, you may have to do some additional implementation here to make sure it's looking at exactly what you want. If you're not doing anything fancy, just put double underscores for region for example: region__name
With dj-rest-filters, you can write your filters with similar syntax as of serializer. For your case, it will be like this
from djfilters import filters
class MyFilter(filters.Filter):
search = filters.CharField()
def filter_search(self, qs, value):
qs = qs.filter(#Your filter logic here)
return qs