Let's say I have some models:
class A(models.Model):
...
class B(models.Model):
my_reference_to_a = models.ForeignKey(A)
b_field_1 = ...
b_field_2 = ...
class C(models.Model):
my_reference_to_b = models.ForeignKey(B)
c_field_1 = ...
...
In my serializer for C, I want to include all of the fields in C, all the fields in B, as well as the reference to A in B (but not the reference to B in C), so the JSON API output would be something like this:
{
"data": [{
"type": "C",
"id": "1",
"attributes": {
"b_field_1": "...",
"b_field_2": "...",
"c_field_1": "..."
},
"relationships": {
"a": {
"data": {
"type": "A",
"id": "1"
}
}
}
}],
...
}
How would I go about this? I've already tried doing something like this inside my serializer for C:
A = ASerializer(source='my_reference_to_b.my_reference_to_a')
But that doesn't work, as DRF doesn't seem to support dotted paths for sources. I've also tried supplying a method that returns the proper model (the model is valid inside the method) as the source, but that outputs the reference in the JSON as:
"a": {
"data": null
}
On my A model, I also have a reference to another model, D, that is not explicitly stated in A, but is instead defined in D as a OneToMany relationship (Many D models to one A model) with a resource_name on the ForeignKey declared in D, and trying to reference this in C to include that relationship in the JSON doesn't work, either. I get this error (trying to reference it by doing D = DSerializer(source='B.D')):
'RelatedManager' object has no attribute 'B'
Any help would be greatly appreciated.
I figured it out. Just answering my own question in case anyone lands on this page and they need help.
You need to use the SerializerMethodResourceRelatedField from the Django Rest Framework JSON API. I had tried the regular ResourceRelatedField without it working, looking through the source code showed me that ResourceRelatedField doesn't support dotted paths. Instead, use SerializerMethodResourceRelatedField with a source pointing to a method that returns the desired relation.
# Model
from django.db import models
class Album(models.Model):
album_name = models.CharField(max_length=100)
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks',on_delete=models.DO_NOTHING)
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
class Meta:
unique_together = ('album', 'order')
ordering = ['order']
def __unicode__(self):
return '%d: %s' % (self.order, self.title)
# View
from rest_framework import generics,viewsets
from api.models import Album
from api.serializers import AlbumSerializer
class TracksView(generics.ListAPIView):
queryset = Album.objects.all()
serializer_class = AlbumSerializer
class TracksView(generics.CreateAPIView):
queryset = Album.objects.all()
serializer_class = AlbumSerializer
# serializers
from rest_framework import serializers
from api.models import Album
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.StringRelatedField(many=True,)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
Related
This question has been asked before but I cannot use any of the answers to my case.
I'm trying to have the equivalent of this, to show the results on the API.
SELECT denom_name,retail_name,retail_adr
FROM denomination d INNER JOIN Retailer r
ON r.id = d.retailer.id
These are my models (models.py):
class Retailer(models.Model):
retail_name = models.CharField(max_length=30)
retail_addr = models.CharField(max_length=300,null=True)
def __str__(self):
return self.retail_name
class Denomination(models.Model):
denom_name = models.CharField(max_length=1000)
retailer = models.ForeignKey(Retailer, on_delete=models.CASCADE)
I've created a viewset on the views.py
class DenomRetailViewset(viewsets.ModelViewSet):
queryset = Denomination.objects.select_related('Retailer')
serializer_class = DenomRetailSerializer
But here lies the issue, at least one of them.
I'm creating the serializer through the serializer.py
class DenomRetailSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model= retailer,denomination
fields = ('denom_name','retail_name','retail_adr')
But as you can see, the serializer cannot accept two models. And beside, I have doubts about the viewset, queryset = Denomination.objects.select_related('Retailer').
Any tips are more than welcomed as I'm starting to lose my sanity.
Thanks.
Use source--DRF doc argument
class DenomRetailSerializer(serializers.HyperlinkedModelSerializer):
retail_name = serializers.CharField(source='retailer.retail_name')
retail_adr = serializers.CharField(source='retailer.retail_adr')
class Meta:
model = Denomination
fields = ('denom_name', 'retail_name', 'retail_adr')
Also, it should be .select_related('retailer') instead of .select_related('Retailer')
You can use "depth" in this case:
class DenomRetailSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model= denomination
fields = ('denom_name','retail')
depth = 1
The response will return an inner join like that:
{
"id": 1,
"denom_name ": "...",
"retail": {
"id" : 1,
"retail_name" : "...",
"retail_addr" : "..."
}
}
I have 2 models
class Tag(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Question(models.Model):
ques_id = models.IntegerField(default=0)
name = models.CharField(max_length=255)
Tag_name = models.ManyToManyField(Tag)
class Meta:
ordering = ['ques_id']
def __str__(self):
return self.name
searlizers.py
class TagSerializers(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
class QuestionSerializers(serializers.ModelSerializer):
class Meta:
model = Question
fields = '__all__'
This is my searilzers class.
I want the response like
{
"id": 1,
"name": "QUES 1",
"tags": [{
"id": 1,
"name": "Abcd"
}]
}
what will be query to get Fetch 10 questions, given some input tag ids
e.g Tag_id = 1 or 2 or 3.
You need to add tags field as another serializer data to your QuestionSerializer.
Your QuestionSerializer code should look like that:
class QuestionSerializers(serializers.ModelSerializer):
Tag_name = TagSerializer(many=True)
class Meta:
model = Question
fields = '__all__'
If you want exactly tags name in response, you can specify SerializerMethodField like that:
class QuestionSerializer(serializers.ModelSerializer):
tags = serializers.SerializerMethodField()
get_tags(self, instance):
return TagSerializer(instance.Tag_name, many=True).data
class Meta:
model = Question
fields = ('ques_id', 'name', 'tags')
First: I would suggest that you refactor your Question Model, since it has a ques_id, and I think it is considered a duplicate (since Django already creates an id field by default)
Then You need to change your ManyToManyField's name to tags, and makemigrations, then migrate
class Question(models.Model):
name = models.CharField(max_length=255)
tags = models.ManyToManyField(Tag)
class Meta:
ordering = ['id']
def __str__(self):
return self.name
# run make migrations
python manage.py makemigrations <<<YOUR QUESTIONS APP NAME>>>
# It will prompt you to check if you change the many to many relationship say yes
Did you rename question.Tag_name to question.tags (a ManyToManyField)? [y/N] y
# Then Run migrate
python manage.py migrate
Second: Update your QuestionSerializers to make the tags field serialize the relation
class QuestionSerializers(serializers.ModelSerializer):
tags = TagSerializers(many=True)
class Meta:
model = Question
fields = '__all__'
This way you make your code cleaner. And you are good to go.
Answer Updated (Filtering, and Pagination)
Now if you wanted to filter questions based on provided tag ids.
You need to use PageNumberPagination for your view, and for filtering use DjangoFilterBackend.
I recommend you to make them the default of DRF settings.
Make sure you have django-filter installed.
# In your settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
'PAGE_SIZE': 10
}
Now create your custom filter
# Inside filters.py
import re
import django_filters
from questions.models import Question
class QuestionsFilterSet(django_filters.FilterSet):
tags = django_filters.CharFilter(field_name='tags__id', method='tags_ids_in')
def tags_ids_in(self, qs, name, value):
ids_list = list(map(lambda id: int(id), filter(lambda x: x.isdigit(), re.split(r'\s*,\s*', value))))
return qs.filter(**{f"{name}__in": ids_list}).distinct()
class Meta:
model = Question
fields = ('tags',)
Now use ListAPIView to list your questions
class QuestionsListAPIView(ListAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializers
filter_class = QuestionsFilterSet
Now if you hit
http://<PATH_TO_VIEW>?tags=1,2,3
You will receive all Questions that have tag id 1, 2, or 3.
and all of them will be paginated 10 results at a time.
You just need to write a field tags like this in your QuestionSerializers
class QuestionSerializers(serializers.ModelSerializer):
tags = TagSerializers(source='Tag_name', many=True)
class Meta:
model = Question
fields = ('id', 'name', 'tags')
It will give you response like this
{
"id": 1,
"name": "QUES 1",
"tags": [{
"id": 1,
"name": "Abcd"
}]
}
Your query to fetch on the basis of tags will be like this
Question.objects.filter(Tag_name__in=[1, 2, 4])
I want to show the name of a field and not the ID of it, so I tried a couple of different methods but with nothing worked for me. I couldn't figure out why or find a suitable solution for this, so here it goes:
Basically, my views returns a null field when in fact it is not null. Code bellow.
tags = TagListSerializer() # Returns null
tags = serializers.Field(source='tags.name') # Returns null
tags = TagListSerializer # Doesn't change anything
Comercial inherits from Project which is abstract:
class Comercial(Project):
name = models.CharField()
Project is abstract:
class Project(models.Model):
tags = models.ManyToManyField(Tag, blank=True, editable=True)
The Tag Model:
class Tag(models.Model):
name = models.CharField(null=False)
The Comercial View which searches by tag:
class ListComercialAPIView(ListAPIView):
serializer_class = ComercialListSerializer
pagination_class = StandardResultsSetPagination
def get_queryset(self):
tag = self.request.GET.get('tag')
if(tag!=None):
return Comercial.objects.filter(tags__name=tag)
else:
return Comercial.objects.all()
The Tag Serializer:
class TagListSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('__all__')
And finally the ComercialSerlialzier:
class ComercialListSerializer(serializers.ModelSerializer):
tags = TagListSerializer()
class Meta:
model = Comercial
fields = ('name', 'tags')
At listing the Comercial object I expected something like this:
{
"name": "Example Comercial Name"
"tags": {
"name":"Example Tag Name"}
}
But all i get is:
{
"name": "Example Comercial Name"
"tags": {
"name": null
}
Thanks in advance!
Well, adding many=True to the serializer solved my problem...dumb me
class ComercialListSerializer(serializers.ModelSerializer):
tags = TagListSerializer(many=True)
I'm trying to get data as json format. I've one ManyToMany field which is returning just id. But I need that contents too. Here is my models.py
class Pricing(models.Model):
name = models.CharField(max_length = 100)
price = models.CharField(max_length = 100)
def __str__(self):
return self.name+' and '+self.price
class Service(models.Model):
name = models.CharField(max_length=100)
price = models.ManyToManyField(Pricing, blank=True)
def __str__(self):
return self.name
And also the views.py which is returning json format data
def all_service_json(request, name):
data = serializers.serialize("json", Service.objects.filter(name__icontains=name))
return HttpResponse(data)
Now Getting the output like below
[
{
"model": "myapp.service",
"pk": 2,
"fields":
{
"name": "Service name",
"price": [1, 2]
}
}
]
But want like below
[
{
"model": "myapp.service",
"pk": 2,
"fields":
{
"name": "Service name",
"price":
{
1: "Price 1",
2: "Price 2"
}
}
}
]
Creating ModelSerializer objects from within Django Rest Framework will let you display nested object data:
http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
# myapp/serializers.py
...
from rest_framework import serializers
class PricingSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = Pricing
class ServiceSerializer(serializers.ModelSerializer):
price = PricingSerializer(read_only=True, many=True)
class Meta:
fields = '__all__'
model = Service
# myapp/views.py
def all_service_json(request, name):
services = Service.objects.filter(name__icontains=name)
data = ServiceSerializer(services, many=True).data
return HttpResponse(data)
As #robert mentioned using nested serializers will fix your issue.
But note that by default nested serializers are read-only. So If you
want to support write operations to a nested serializer field you'll
need to add create() and/or update() methods in order to explicitly
specify how the child relationships should be saved.
Writable Service Serializer
class ServiceSerializer(serializers.ModelSerializer):
price = PricingSerializer(many=True)
class Meta:
fields = '__all__'
model = Service
# sample create
def create(self, validated_data):
prices_data = validated_data.pop('price')
service = Service.objects.create(**validated_data)
for price_data in prices_data:
Price.objects.create(service=service, **price_data)
return service
# add update here
myapp/views.py
def all_service_json(request, name):
services = Service.objects.filter(name__icontains=name)
serializer = ServiceSerializer(services)
return HttpResponse(serializer.data)
So in your case all you have to do is to add depth = 1 and you will get nested representations.
Docs
The default ModelSerializer uses primary keys for relationships, but
you can also easily generate nested representations using the depth
option:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'account_name', 'users', 'created']
depth = 1
I just start learning Django from last 8 hours and I stuck into this situation where many to many relationship is returning id instead of child data. I wrote some custom code. For me it solve my problem, hope this helps someone.
from django.core.serializers import serialize
import json
def modelToDict(model):
jsn = serialize("json", model) # convert to json
mydict = json.loads(jsn) # again convert to dictionary
return mydict
def all_service_json(request, name):
data = Service.objects.filter(name__icontains=name)
dictdata = modelToDict(data)
for i in range(len(dictdata)):
price = modelToDict(data[i].price.all())
dictdata[i]['fields']['price'] = price
return HttpResponse(json.dumps(`dictdata`), content_type="application/json")
I am trying to create a reference app in DRF 3 to demonstrate a nested serializer that can create/update models. The sample code below bombs with "*create() argument after ** must be a mapping, not list*" when trying to create the nested models. It is also no clear to me how I'd handle the .update() as in some cases I just want to be establish additional relationships (Persons).
The sample models:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
persons = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
And the serializers and viewsets:
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from app.models import Group, Person
class PersonSerializer(ModelSerializer):
class Meta:
model = Person
class GroupSerializer(ModelSerializer):
persons = PersonSerializer(many=True)
def create(self, validated_data):
persons = validated_data.pop('persons')
group = Group.objects.create(**validated_data)
if persons: # Bombs without this check
Person.objects.create(group=group, **persons) # Errors here
return group
class Meta:
model = Group
class PersonModelViewSet(ModelViewSet):
serializer_class = PersonSerializer
queryset = Person.objects.all()
class GroupModelViewSet(ModelViewSet):
serializer_class = GroupSerializer
queryset = Group.objects.all()
I am trying to POST some JSON that inserts a Group with two (related) Persons:
{
"persons": [
{ "name" : "name 1" },
{ "name" : "name 2" }
],
"name": "group name 1"
}
I have no clue if there is an easier way, but the only way I managed to get this to work is to reference the 'through' model "memberships" in the Group serializer and write custom code for .create() and .update(). This seems like a lot of work to just set M2M FK's. If someone knows a better way I'd love to hear it.
class GroupMembershipSerializer(ModelSerializer):
class Meta:
model = Membership
fields = ('person',)
class GroupCreateSerializer(ModelSerializer):
memberships = GroupMembershipSerializer(many=True, required=False)
def create(self, validated_data):
person_data = validated_data.pop('memberships')
group = Group.objects.create(**validated_data)
for person in person_data:
d=dict(person)
Membership.objects.create(group=group, person=d['person'])
return group
def update(self, instance, validated_data):
person_data = validated_data.pop('memberships')
for item in validated_data:
if Group._meta.get_field(item):
setattr(instance, item, validated_data[item])
Membership.objects.filter(group=instance).delete()
for person in person_data:
d=dict(person)
Membership.objects.create(group=instance, person=d['person'])
instance.save()
return instance
class Meta:
model = Group
class GroupCreateModelViewSet(ModelViewSet):
serializer_class = GroupCreateSerializer
queryset = Group.objects.all()
So you can create a new Group with related Person(s) using:
{
"name" : "Group 1",
"memberships" : [
{ "person" : 1 },
{ "person" : 2 }
]
}
Use PrimaryKeyRelatedField shown here:
http://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield
class GroupSerializer(serializers.ModelSerializer):
persons = serializers.PrimaryKeyRelatedField(
many=True, queryset=Person.objects.all())
class Meta:
model = Group
fields = ('name', 'persons')
Create each person first, for example. Person with ID 1, Name = "Bob". Person with ID 2, Name = "Tim". Then post them to the REST Endpoint using their primary keys So:
# Group create() REST endpoint data to POST
{'name': 'my group', 'persons': [1, 2]}
Now the people that you had created prior, are part of that Group.