Plain structure from OneToOne-related models (Django Rest Framework) - django

Models:
class Person(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveSmallIntegerField()
# More Person fields
class Student(models.Model):
person = models.OneToOneField(
Person, on_delete=models.PROTECT, primary_key=True)
year_of_study = models.PositiveSmallIntegerField()
# More Student fields
Serializers:
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
class StudentSerializer(serializers.ModelSerializer):
person = PersonSerializer()
class Meta:
model = Student
fields = '__all__'
Views:
class StudentView(viewsets.ReadOnlyModelViewSet):
renderer_classes = [JSONRenderer]
parser_classes = [JSONParser]
queryset = Student.objects.all()
serializer_class = StudentSerializer
Requesting single Student:
{
"person": {
"id": 1,
"name": "Example Name",
"age": 20
},
"year_of_study": 3
}
But I need to work with plain structure like:
{
"id": 1,
"name": "Example Name",
"age": 20,
"year_of_study": 3
}
Where (in serializer or in view or somewhere else) and how should I do it?
I only need GET requests (using ReadOnlyModelViewSet because of it). But if would also be nice to know how to create/update/delete such structure as well.

you can create serializer like below
class StudentSerializer(serializers.ModelSerializer):
name = serilizer.CharFiled(source="person.name")
person_id = serilizer.IntegerFiled(source="person.id")
age = serilizer.IntegerFiled(source="person.age")
class Meta:
model = Student
exclude = ('person',)

You have person = PersonSerializer() which outputs the person's data. Remove this line and it'll display just person_id.
Now if you want a combination of GETs returning person data but POSTs using the person_id, you can do the following:
person = PersonSerializer(read_only=True)
person_id = serializers.PrimaryKeyRelatedField(
queryset=Person.objects.all(),
source='person',
write_only=True,
)

Related

django rest_framework how to display nested relationship

I'm trying to display foreign related fields like this example and it works
{
"reqid": 10,
"reqdate": "2022-12-05",
"reqdescription": "Aircon Not working",
"officerequestor": "OVCAA ",
"officeid": "PPD ",
"inspection": {
"insdate": "2022-12-06",
"diagnosis": "need to buy prism",
"inspector": "EMP-322 "
}
},
this is my serializers.py
class RequestAdditionDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = Inspection
fields = ['insdate',
'diagnosis',
'inspector'
]
class RequestorSerializer(serializers.ModelSerializer):
inspection = RequestAdditionDetailsSerializer(read_only=True)
class Meta:
model = Request
fields = ['reqid',
'reqdate',
'reqdescription',
'officerequestor',
'officeid',
'inspection'
]
My question is can I do this the other way around like this
{
"inspectid": 5,
"reqid": "10",
"insdate": "2022-12-06",
"diagnosis": "need to buy prism",
"inspector": "EMP-322",
"isinspected": {
"reqdescription": "Aircon Not working",
"reqdate": "2022-12-05",
"officerequestor": "OVCAA"
}
},
this is what I've tried, tbh I don't think this will work is there a solution for this.
if no maybe i'll add additional columns on inspection like reqdescription,reqdate etc.. just to show them
class InspectionAdditionalDetailsViewSerializer(serializers.ModelSerializer):
class Meta:
model = Request
fields = ['reqdescription',
'reqdate',
'officerequestor'
]
class InspectionSerializer(serializers.ModelSerializer):
request_details = InspectionAdditionalDetailsViewSerializer(read_only=True)
class Meta:
model = Inspection
fields = ['inspectid',
'reqid',
'insdate',
'diagnosis',
'inspector',
'isinspected',
'request_details'
]
this is my models.py
class Inspection(models.Model):
inspectid = models.AutoField(primary_key=True)
reqid = models.OneToOneField('Request', models.DO_NOTHING, db_column='reqid', blank=True, null=True)
class Meta:
managed = False
db_table = 'inspection'
class Request(models.Model):
reqid = models.AutoField(primary_key=True)
class Meta:
managed = False
db_table = 'request'
You have defined the OneToOne field name reqid therefore you should use it as serializer key.
Noted that Django will add _id to the field so it will become reqid_id in your database, it's best to name it req or request only to refer to related object.
class InspectionAdditionalDetailsViewSerializer(serializers.ModelSerializer):
class Meta:
model = Request
fields = [
'reqdescription',
'reqdate',
'officerequestor',
]
class InspectionSerializer(serializers.ModelSerializer):
reqid = InspectionAdditionalDetailsViewSerializer(read_only=True)
class Meta:
model = Inspection
fields = [
'inspectid',
'reqid',
'insdate',
'diagnosis',
'inspector',
'isinspected',
]

Django Serializer - Combine fields from two models

I have two models namely:
class B (models.Model):
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=255)
class A (models.Model):
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=255)
b = models.ForeignKey(B,on_delete=models.CASCADE,null=True)
I want a serializer which returns a response like this:
{
"id": 1,
"name": "test",
"b_id": 2,
"b_name": "test
}
the response format you want to get is not possible to my knowledge but you can use nestedSerializers to get both models in one response like
{
"id": 1,
"name": "test",
"b":{
"id":"test",
"name": "test"
}
}
if this is enough for you you can use the following code
serializers.py
class Bserializer(serializers.ModelSerializer):
class Meta:
model = B
fields = "__all__" #or only the fields you want
class Aserializer(serializers.ModelSerializer):
b = Bserializer()
class Meta:
model = B
fields = ["id","name", "b"]
Since b field in A model is a one to many relation so it should be a list of a items like this, also check docs
{
"b_id": 1,
"b_name": "test",
"a": [
'a_id1': 'a_name',
'a_id2': 'a_name'
]
}
If that is what you want you can do it like this:
class BSerializer(serializers.ModelSerializer):
a = serializers.StringRelatedField(many=True)
class Meta:
model = Album
fields = ['id', 'name', 'a']
This is directly from a project im working on now
class CoachLocationsSerializer(serializers.ModelSerializer):
base = LocationSerializer(read_only=True)
locations = LocationSerializer(read_only=True, many=True)
class Meta:
model = Coach
fields = ['base', 'locations']
You can achieve it without using nested serializers, like this:
class ASerializer(serializers.ModelSerializer):
b_name = serializers.CharField(source='b__name')
class Meta:
model = A
fields = ('id', 'name', 'b_id', 'b_name')
Also, note that you don't need to declare b_id in the serializer, since it's already a model field

how to write serializer for joined model?

i have applied join on two tables with following query,
VIEWS.PY
class performance(viewsets.ModelViewSet):
queryset = Leads.objects.select_related('channelId'
).values("channelId__channelName").annotate(tcount=Count('channelId'))
serializer_class = teamwise_lead_performance_serializer
but i am unable to catch response using this serializers,
SERIALIZER.PY
class channel_serializer(serializers.ModelSerializer):
class Meta:
model = Channels
fields = ['channelName']
class performance_serializer(serializers.ModelSerializer):
tcount = serializers.IntegerField()
channel = channel_serializer(many=True, read_only=True)
class Meta:
model = Leads
fields = ['tcount', 'channel']
actual results:
[
{
"tcount": 88
},
{
"tcount": 25
},
{
"tcount": 31
},
...
]
expected results:
[
{
"channelName": "abc",
"tcount": 88
},
{
"channelName": "def",
"tcount": 25
},
{
"channelName": "ghi",
"tcount": 31
},
...
]
i have tried the following:
How to join two models in django-rest-framework
Models.py
class Channels(models.Model):
id = models.IntegerField(primary_key=True)
channelName = models.CharField(max_length=20, default=None)
class Meta:
db_table = "table1"
class Leads(models.Model):
id = models.IntegerField(primary_key=True)
channelId = models.ForeignKey(Channels, on_delete=models.CASCADE, db_column='channelId')
class Meta:
db_table = "table2"
why is it not getting the channelName in response?
what am i doing wrong here?
Thank you for your suggestions
Edit
When I try Mehren's answer, I get the following error:
KeyError when attempting to get a value for field channelName on serializer performance_serializer. The serializer field might be named incorrectly and not match any attribute or key on the dict instance.Original exception text was: 'channelId'.
I managed to get it working with the following:
class performance_serializer(serializers.ModelSerializer):
tcount = serializers.IntegerField()
channelName = serializers.CharField(source='channelId__channelName')
class Meta:
model = Leads
fields = ['tcount', 'channelName']
class performance(viewsets.ModelViewSet):
queryset = Leads.objects.select_related('channelId'
).values("channelId__channelName").annotate(tcount=Count('channelId'))
serializer_class = performance_serializer
That being said, I would strongly encourage you to follow both PEP and Django naming conventions.
Here is what your code would look like following said conventions:
class Channel(models.Model):
id = models.IntegerField(primary_key=True)
channel_name = models.CharField(max_length=20, default=None)
class Lead(models.Model):
id = models.IntegerField(primary_key=True)
channel = models.ForeignKey(Channel, on_delete=models.CASCADE)
class PerformanceSerializer(serializers.ModelSerializer):
channel_count = serializers.IntegerField()
channel_name = serializers.CharField(source='channel__channel_name')
class Meta:
model = Lead
fields = ['channel_count', 'channel_name']
class PerformanceViewSet(viewsets.ModelViewSet):
queryset = Lead.objects.select_related('channel'
).values("channel__channel_name").annotate(channel_count=Count('channel'))
serializer_class = PerformanceSerializer
The main takeaway from this is to not change the default name of your ForeignKey columns! It makes working with related models much more confusing, and is possibly the reason for your problem in the first place (although I couldn't prove it).
If you want to only get the channelName, then it's better to use
channelName = serializers.CharField(source='channelId.channelName')
Also, please fix your syntax. You are not following the pep8 standards.
EDIT
class PerformanceSerializer(serializers.ModelSerializer):
tcount = serializers.IntegerField()
channelName = serializers.CharField(source='channelId.channelName')
class Meta:
model = Leads
fields = ['tcount', 'channelName']
EDIT
queryset = Leads.objects.select_related('channelId').values("channelId__channelName").annotate(tcount=Count('channelId'))
Remove the .values("channelId__channelName") part from your view

How to serialize foreign key from model X in model Y, where model X has the relation to model Y?

I need to serialize my Contracts view in my Registration view.
I understand how to do it if there was a foreign key in the Registration model relating the Contract model, but in this case there is a relation from the Contract model to the Registration model.
I need to do this in a bigger project, this is just a simple boiler plate.
Basically, I want my output to be this:
[
{
"id": 1,
"client": "John Doe",
"contract": {
"id": 1,
"client": "John Doe",
"name": "New Identity",
"registration": 1
}
},
{
"id": 2,
"client": "Jane Doe",
"contract": {
"id": 2,
"client": "Jane Doe",
"name": "Identity theft",
"registration": 2
}
}
]
Models:
class Client(models.Model):
name = models.CharField(max_length=250)
address = models.CharField(max_length=250)
email = models.EmailField()
class Registration(models.Model):
client = models.ForeignKey(Client, on_delete=models.CASCADE)
class Contract(models.Model):
name = models.CharField(max_length=150)
client = models.ForeignKey(Client, on_delete=models.CASCADE)
registration = models.ForeignKey(Registration, on_delete=models.CASCADE)
Viewsets:
class ClientViewSet(viewsets.ModelViewSet):
queryset = Client.objects.all()
serializer_class = ClientSerializer
class RegistrationViewSet(viewsets.ModelViewSet):
queryset = Registration.objects.all()
queryset = queryset.select_related("client")
serializer_class = RegistrationSerializer
class ContractViewSet(viewsets.ModelViewSet):
queryset = Contract.objects.all()
queryset = queryset.select_related("registration").prefetch_related(
"client"
)
serializer_class = ContractSerializer
Serializers:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = "__all__"
class ContractSerializer(serializers.ModelSerializer):
client = NameSerializer()
class Meta:
model = Contract
fields = "__all__"
class RegistrationSerializer(serializers.ModelSerializer):
client = NameSerializer()
class Meta:
model = Registration
fields = "__all__"
You can still query contracts using: register_obj.contract_set.all() which will return all the rows that have the foreign key pointing to the selected register_obj.
And your serializer should be like this:
class RegistrationSerializer(serializers.ModelSerializer):
client = NameSerializer()
contracts = SerializerMethodField()
class Meta:
model = Registration
fields = "__all__"
def get_contracts(self, obj):
# if you need all the contracts in a list:
return ContractSerializer(obj.contract_set.all(), many=True).data
# or if you just need the first one:
return ContractSerializer(obj.contract_set.first()).data
# you can also filter the query

Django REST Framework: define fields in nested object?

I got events that happen at locations:
class Event(models.Model):
title = models.CharField(max_length=200)
date_published = models.DateTimeField('published date',default=datetime.now, blank=True)
date_start = models.DateTimeField('start date')
date_end = models.DateTimeField('end date')
def __unicode__(self):
return self.title
description = models.TextField()
price = models.IntegerField(null=True, blank=True)
tags = TaggableManager()
location = models.ForeignKey(Location, blank=False)
class Location(models.Model):
location_title = models.CharField(max_length=200)
location_date_published = models.DateTimeField('published date',default=datetime.now, blank=True)
location_latitude = models.CharField(max_length=200)
location_longitude = models.CharField(max_length=200)
location_address = models.CharField(max_length=200)
location_city = models.CharField(max_length=200)
location_zipcode = models.CharField(max_length=200)
location_state = models.CharField(max_length=200)
location_country = models.CharField(max_length=200)
location_description = models.TextField()
def __unicode__(self):
return u'%s' % (self.location_title)
I can get the results of all via:
class EventSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field()
class Meta:
model = Event
depth = 2
fields = ('url','id','title','date_start','date_end','description', 'price', 'location')
Which outputs:
{
"url": "http://localhost:8000/api/event/3/",
"id": 3,
"title": "Testing",
"date_start": "2013-03-10T20:19:00Z",
"date_end": "2013-03-10T20:19:00Z",
"description": "fgdgdfg",
"price": 10,
"location": {
"id": 2,
"location_title": "Mighty",
"location_date_published": "2013-03-10T20:16:00Z",
"location_latitude": "37.767475",
"location_longitude": "-122.406878",
"location_address": "119 Utah St, San Francisco, CA 94103, USA",
"location_city": "San Francisco",
"location_zipcode": "94103",
"location_state": "California",
"location_country": "United States",
"location_description": "Some place"
}
},
However, I don't want it to grab all fields, as I don't need all of them. How can I define what fields should be retrieved from my nested object? Thanks!
Serializers can be nested, so do something like this...
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = (...)
class EventSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field()
location = LocationSerializer()
class Meta:
model = Event
fields = ('url','id','title','date_start','date_end','description', 'price', 'location')
I have been to this and did not get a perfect solution, But I did something you may check for it.
This method will not create nested serializers
**class LocationSerializer(serializers.ModelSerializer):**
class Meta:
model = Location
fields = (...) #does not matter
exclude = (...) #does not matter
class EventSerializer(serializers.ModelSerializer):**
loc_field_1 = serializers.CharField(required=False,*source='location.loc_field_1'*)
loc_field_2 = serializers.CharField(required=False,*source='location.loc_field_2'*)
***#ADD YOUR DESIRE FIELD YOU WANT TO ACCESS FROM OTHER SERIALIZERS***
class Meta:
model = Event
fields =('url','id','title','date_start','date_end','description', 'price', 'location')
I found this question when I was trying to figure out how to exclude certain fields from a serializer only when it was being nested. Looks like Tasawer Nawaz had that question as well. You can do that by overriding get_field_names. Here's an example based on Tom Christie's answer:
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = (...)
exclude_when_nested = {'location_title', 'location_date_published'} # not an official DRF meta attribute ...
def get_field_names(self, *args, **kwargs):
field_names = super(LinkUserSerializer, self).get_field_names(*args, **kwargs)
if self.parent:
field_names = [i for i in field_names if i not in self.Meta.exclude_when_nested]
return field_names
class EventSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field()
location = LocationSerializer()
class Meta:
model = Event
fields = ('url','id','title','date_start','date_end','description', 'price', 'location')